Метод расширения

редактировать

В объектно-ориентированном компьютерном программировании метод расширения является метод добавлен к объекту после того, как исходный объект был скомпилирован. Измененный объект часто является классом, прототипом или типом. Методы расширения - это особенности некоторых объектно-ориентированных языков программирования. Синтаксической разницы между вызовом метода расширения и вызовом метода, объявленного в определении типа, нет.

Однако не все языки реализуют методы расширения одинаково безопасным образом. Например, такие языки, как C #, Java (через Manifold) и Kotlin, никоим образом не изменяют расширенный класс, потому что это может нарушить иерархию классов и помешать диспетчеризации виртуальных методов. Вот почему эти языки строго статически реализуют методы расширения и используют статическую диспетчеризацию для их вызова.

, главный разработчик в группе компиляторов C #, говорит: «Методы расширения определенно не являются объектно-ориентированными».

Содержание
  • 1 Поддержка языков программирования
  • 2 Методы расширения как обеспечивающие возможность
    • 2.1 Централизовать общее поведение
    • 2.2 Лучшая слабая связь
    • 2.3 Гибкие интерфейсы прикладного программиста
    • 2.4 Производительность
    • 2.5 Производительность
    • 2.6 Устранение потребности в общем базовом классе
    • 2.7 Консервативное использование
  • 3 Проблема
  • 4 Текущие решения C #
  • 5 Текущие решения VB.NET
  • 6 Методы расширения
  • 7 Конфликты имен в методах расширения и методах экземпляров
  • 8 См. Также
  • 9 Ссылки
  • 10 Внешние ссылки
Поддержка языков программирования

Методы расширения - это особенности многих языков, включая C#, Java через Manifold, Gosu, JavaScript, Oxygene, Ruby, Smalltalk, Kotlin, Visual Basic.NET и Ксоджо. В динамических языках, таких как Python, концепция метода расширения не нужна, потому что классы могут быть расширены без какого-либо специального синтаксиса (подход, известный как «исправление обезьяны», используемый в таких библиотеках, как).

В VB.NET и Oxygene они распознаются по наличию ключевого слова или атрибута «extension». В Xojo ключевое слово «Extends» используется с глобальными методами.

В C # они реализованы как статические методы в статических классах, с первым аргументом расширенного класса, которому предшествует ключевое слово «this».

В Java вы добавляете методы расширения через Manifold, файл jar, который вы добавляете в путь к классам вашего проекта. Подобно C #, метод расширения Java объявляется статическим в классе @Extension, где первый аргумент имеет тот же тип, что и расширенный класс, и аннотируется @This.

. В Smalltalk любой код может добавить метод к любому классу в любое время, отправив сообщение о создании метода (например, methodFor:) в класс, который пользователь хочет расширить. Категория методов Smalltalk условно названа в честь пакета, который предоставляет расширение, и заключена в звездочки. Например, когда код приложения Etoys расширяет классы в основной библиотеке, добавленные методы помещаются в категорию * etoys *.

В Ruby, как и в Smalltalk, нет специальной языковой функции для расширения, поскольку Ruby позволяет повторно открывать классы в любое время с помощью ключевого слова class, в данном случае для добавления новые методы. Сообщество Ruby часто описывает метод расширения как своего рода патч обезьяны. Существует также более новая функция для добавления безопасных / локальных расширений к объектам, называемая Refinements, но, как известно, она используется реже.

В Swift ключевое слово extensionотмечает конструкцию, подобную классу, которая позволяет добавлять методы, конструкторы и поля в существующий класс, включая возможность реализации нового интерфейса / протокола. к существующему классу.

Методы расширения как включающие функцию

Наряду с методами расширения, позволяющими расширять код, написанный другими, как описано ниже, методы расширения также включают шаблоны, которые полезны сами по себе. Основной причиной, по которой были введены методы расширения, был Language Integrated Query (LINQ). Поддержка компилятором методов расширения обеспечивает глубокую интеграцию LINQ со старым кодом точно так же, как и с новым кодом, а также поддерживает синтаксис запроса , который на данный момент является уникальным для основного Microsoft.NET языков.

Console.WriteLine (новый {Math.PI, Math.E}.Where (d =>d>3).Select (d =>Math.Sin (d / 2)). Sum ()); // Вывод: // 1

Централизовать общее поведение

Однако методы расширения позволяют реализовывать функции один раз таким образом, чтобы обеспечить повторное использование без необходимости наследования или накладных расходов на вызовы виртуального метода или потребовать от разработчиков интерфейса интерфейса реализовать тривиальные или ужасно сложные функции.

Особенно полезен сценарий, если функция работает с интерфейсом, для которого нет конкретной реализации или полезная реализация не предоставлена ​​автором библиотеки классов, например как это часто бывает в библиотеках, которые предоставляют разработчикам архитектуру плагина или аналогичные функции.

Рассмотрим следующий код и предположим, что это единственный код, содержащийся в библиотеке классов. Тем не менее, каждый разработчик интерфейса ILogger получит возможность писать отформатированную строку, просто включив оператор using MyCoolLogger, без необходимости реализовывать его один раз и не требуя подкласса реализации ILogger, предоставленной библиотекой классов.

пространство имен MyCoolLogger {открытый интерфейс ILogger {void Write (текст строки); } общедоступный статический класс LoggerExtensions {общедоступный статический void Write (этот регистратор ILogger, строковый формат, аргументы объекта params) {if (logger! = null) logger.Write (string.Format (format, args)); }}}
  • использовать как:
    var logger = new MyLoggerImplementation (); logger.Write ("{0}: {1}", "kiddo sais", "Мам, мам, мам..."); logger.Write ("{0}: {1}", "kiddo sais", "Ma ma ma ma..."); logger.Write ("{0}: {1}", "kiddo sais", "Mama mama mama mama"); logger.Write ("{0}: {1}", "kiddo sais", "Mamma mamma mamma..."); logger.Write ("{0}: {1}", "kiddo sais", "Элизабет Лиззи Лиз..."); logger.Write ("{0}: {1}", "мама говорит", "ЧТО?!? !!!"); logger.Write ("{0}: {1}", "kiddo sais", "привет.");

Лучшая слабая связь

Методы расширения позволяют пользователям библиотек классов воздерживаться от объявления аргумента, переменной или чего-либо еще с типом, полученным из этой библиотеки. Построение и преобразование типов, используемых в библиотеке классов, можно реализовать как методы расширения. После тщательной реализации преобразований и фабрик переключение с одной библиотеки классов на другую можно сделать так же просто, как изменить оператор using, который делает методы расширения доступными для связывания компилятора.

Интерфейсы программиста Fluent

Методы расширения имеют особое применение при реализации так называемых интерфейсов Fluent. Примером является API конфигурации Microsoft Entity Framework, который позволяет, например, писать код, максимально приближенный к обычному английскому языку.

Можно было бы утверждать, что это также возможно без методов расширения, но можно обнаружить, что на практике методы расширения обеспечивают лучший опыт, потому что на иерархию классов накладывается меньше ограничений, чтобы заставить ее работать - и читать - по желанию.

В следующем примере используется Entity Framework и настраивается класс TodoList для хранения в таблице базы данных Lists и определяется первичный и внешний ключи. Код следует понимать примерно так: «У TodoList есть ключевой TodoListID, имя его набора сущностей - Списки, и у него много TodoItem, каждый из которых имеет обязательный TodoList».

открытый класс TodoItemContext: DbContext {общедоступный DbSet TodoItems {получить; задавать; } общедоступный DbSet TodoLists {получить; задавать; } защищенное переопределение void OnModelCreating (DbModelBuilder modelBuilder) {base.OnModelCreating (modelBuilder); modelBuilder.Entity ().HasKey (e =>e.TodoListId).HasEntitySetName («Списки»).HasMany (e =>e.Todos).WithRequired (e =>e.TodoList); }}

Производительность

Рассмотрим, например, IEnumerable и обратите внимание на его простоту - есть только один метод, но он более или менее является основой LINQ. В Microsoft.NET существует множество реализаций этого интерфейса. Тем не менее, очевидно, было бы обременительно требовать, чтобы каждая из этих реализаций реализовывала всю серию методов, которые определены в пространстве имен System.Linq для работы с IEnumerables, даже если у Microsoft есть весь исходный код.. Хуже того, это потребовало бы от всех, кроме Microsoft, рассматривающих возможность использования IEnumerable, также для реализации всех этих методов, что было бы очень непродуктивно, учитывая широкое использование этого очень распространенного интерфейса. Вместо этого, реализуя один метод этого интерфейса, LINQ можно использовать более или менее немедленно. Особенно в большинстве случаев метод IEnumerable GetEnumerator делегируется реализации GetEnumerator частной коллекции, списка или массива.

открытый класс BankAccount: IEnumerable {частный список >кредитов; // предполагается все отрицательные частные списания >дебет; // предполагается, что все положительные общедоступные IEnumerator GetEnumerator () {var query = from dc in debits.Union (credits) orderby dc.Item1 / * Date * / select dc.Item2; / * Сумма * / foreach (количество переменных в запросе) yield return amount; }} // учитывая экземпляр BankAccount с именем ba и использование System.Linq поверх текущего файла, // теперь можно написать ba.Sum (), чтобы получить баланс счета, ba.Reverse (), чтобы увидеть самые последние сначала транзакции, // ba.Average (), чтобы получить среднюю сумму за транзакцию, и так далее - без записи арифметического оператора

Performance

Тем не менее, дополнительные реализации функции, предоставляемые методом расширения могут быть добавлены для повышения производительности или для работы с реализованными по-разному реализациями интерфейса, такими как предоставление компилятору реализации IEnumerable специально для массивов (в System.SZArrayHelper), который он будет автоматически выбирать для вызовов методов расширения для ссылок, типизированных для массивов, поскольку их аргумент будет более конкретным (это значение T), чем метод расширения с тем же именем, который работает с экземплярами интерфейса IEnumerable (это значение IEnumerable).

Устранение потребности в общем базовом классе

С универсальными классами методы расширения позволяют реализовать поведение, доступное для всех экземпляров универсального типа, не требуя, чтобы они были производными от общего базового класса, и без ограничения параметров типа определенной ветвью наследования. Это большая победа, поскольку ситуации, в которых этот аргумент выполняется, требуют неуниверсального базового класса только для реализации общей функции, которая затем требует, чтобы общий подкласс выполнял упаковку и / или преобразование всякий раз, когда используемый тип является одним из аргументов типа..

Консервативное использование

Следует отметить, что методы расширения предпочтительнее других средств достижения повторного использования и правильного объектно-ориентированного проектирования. Методы расширения могут `` загромождать '' функции автоматического завершения редакторов кода, таких как IntelliSense Visual Studio, поэтому они должны быть либо в собственном пространстве имен, чтобы разработчик мог выборочно импортировать их, либо они должны быть определены для типа, который достаточно специфичен для чтобы метод отображался в IntelliSense только тогда, когда он действительно актуален и с учетом вышеизложенного, учтите, что их может быть трудно найти, если разработчик ожидает их, но пропустит их из IntelliSense из-за отсутствия оператора using, поскольку разработчик, возможно, не связал метод с классом, который его определяет, или даже с пространством имен, в котором он живет, - а скорее с типом, который он расширяет, и пространством имен, в котором находится этот тип.

Проблема

В программировании ситуации возникают там, где необходимо добавить функциональность к существующему классу - например, путем добавления нового метода. Обычно программист изменяет исходный код существующего класса, но это вынуждает программиста перекомпилировать все двоичные файлы с этими новыми изменениями и требует, чтобы программист мог изменить класс, что не всегда возможно, например, при использовании классов из сторонней сборки . Обычно это обходится одним из трех способов, каждый из которых несколько ограничен и не интуитивно понятен:

  1. Наследовать класс, а затем реализовать функциональность в методе экземпляра в производном классе.
  2. Реализовать функциональность в статический метод, добавленный к вспомогательному классу.
  3. Использовать агрегацию вместо наследования.
Текущие решения C #

Первый вариант в принципе проще, но К сожалению, он ограничен тем фактом, что многие классы ограничивают наследование определенных членов или полностью запрещают его. Сюда входит запечатанный класс и различные примитивные типы данных в C #, такие как int, float и string. Второй вариант, с другой стороны, не разделяет этих ограничений, но он может быть менее интуитивным, поскольку требует ссылки на отдельный класс вместо прямого использования методов рассматриваемого класса.

В качестве примера рассмотрим необходимость расширения класса строки с помощью нового обратного метода, возвращаемое значение которого представляет собой строку с символами в обратном порядке. Поскольку строковый класс является запечатанным типом, метод обычно добавляется в новый, как показано ниже:

string x = "некоторое строковое значение"; строка y = Utility.Reverse (x);

Однако в этом может стать все труднее ориентироваться по мере увеличения библиотеки служебных методов и классов, особенно для новичков. Местоположение также менее интуитивно понятно, потому что, в отличие от большинства строковых методов, оно будет не членом строкового класса, а вообще в совершенно другом классе. Поэтому лучшим синтаксисом будет следующий:

строка x = "некоторое строковое значение"; строка y = x.Reverse ();
Текущие решения VB.NET

В большинстве случаев решение VB.NET похоже на решение C # выше. Однако VB.NET имеет уникальное преимущество в том, что он позволяет передавать члены в расширение по ссылке (C # допускает только по значению). Учитывая следующее;

Dim x As String = "какое-то строковое значение" x.Reverse ()

Поскольку Visual Basic позволяет передавать исходный объект по ссылке, можно вносить изменения в исходный объект напрямую, без необходимости создавать другая переменная. Он также более интуитивен, поскольку работает согласованно с существующими методами классов.

Методы расширения

Однако новая языковая функция методов расширения в C # 3.0 делает возможным последний код. Для этого подхода требуются статический класс и статический метод, как показано ниже.

общедоступный статический класс Utility {общедоступная статическая строка Reverse (эта строка вводится) {char chars = input.ToCharArray (); Array.Reverse (символы); вернуть новую строку (символы); }}

В определении модификатор this перед первым аргументом указывает, что это метод расширения (в данном случае для типа «строка»). При вызове первый аргумент не «передается», потому что он уже известен как «вызывающий» объект (объект перед точкой).

Основное различие между вызовом методов расширения и вызовом статических вспомогательных методов состоит в том, что статические методы вызываются в префиксной нотации, тогда как методы расширения вызываются в инфиксной нотации. Последнее приводит к более удобочитаемому коду, когда результат одной операции используется для другой операции.

Со статическими методами
HelperClass.Operation2 (HelperClass.Operation1 (x, arg1), arg2)
С методами расширения
x.Operation1 (arg1).Operation2 (arg2)
Конфликты имен в методы расширения и методы экземпляра

В C # 3.0 для класса могут существовать как метод экземпляра, так и метод расширения с одинаковой сигнатурой. В таком сценарии метод экземпляра предпочтительнее метода расширения. Ни компилятор, ни IDE Microsoft Visual Studio не предупреждают о конфликте имен. Рассмотрим этот класс C #, где метод GetAlphabet ()вызывается для экземпляра этого класса:

class AlphabetMaker {public void GetAlphabet () {// Когда этот метод реализован, Console.WriteLine ( «abc»); // он затеняет реализацию} // в классе ExtensionMethods. } static class ExtensionMethods {public static void GetAlphabet (this AlphabetMaker am) {// Это будет вызываться только Console.WriteLine ("ABC"); // если нет экземпляра} // метод с такой же сигнатурой. }

Результат вызова GetAlphabet ()для экземпляра AlphabetMaker, если существует только метод расширения:

ABC

Результат, если оба существуют метод экземпляра и метод расширения:

abc
См. также
Ссылки
Внешние ссылки
Последняя правка сделана 2021-05-19 10:11:18
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте