Шаблоны интерфейса расширений

Новые расширения в .Net 3.5 позволяют отделять функциональность от интерфейсов.

Например, в .Net 2.0

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }

    List<IChild> GetChildren()
}

Может (в 3.5) стать:

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }
}

public static class HaveChildrenExtension {
    public static List<IChild> GetChildren( this IHaveChildren ) {
        //logic to get children by parent type and id
        //shared for all classes implementing IHaveChildren 
    }
}

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

Единственный недостаток заключается в том, что реализация абстрактных основ может быть виртуальной, но можно ли обойти это (будет ли метод экземпляра скрывать метод расширения с тем же именем? что это за путаница в коде?)

Есть ли другие причины не использовать этот шаблон регулярно?


Пояснение:

Да, я вижу тенденцию использования методов расширения, чтобы везде их использовать. Я был бы особенно осторожен с любыми. Типы чистых значений без большого количества экспертных проверок (я думаю, что единственное, что у нас есть в строке, это .SplitToDictionary () - аналогично .Split () , но с ключом - также ограничитель значения)

Я думаю, что там есть целая дискуссия о лучших практиках; -)

(Кстати: DannySmurf, ваш премьер-министр звучит страшно.)

Я специально спрашиваю здесь об использовании методов расширения, где ранее мы Я имел методы интерфейса.


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

Это то, что MS сделала с IEnumerable и IQueryable для Linq?

24
задан Ijas Ameenudeen 20 January 2019 в 13:53
поделиться

10 ответов

Я думаю, что разумное использование дополнительных методов поместило интерфейсы на более сопоставимое положение с (абстрактными) базовыми классами.


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

Как конкретный пример, первая версия библиотеки могла бы определить интерфейс как так:

public interface INode {
  INode Root { get; }
  List<INode> GetChildren( );
}

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

public interface IChildNode : INode {
  INode Parent { get; }
}

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

public static class NodeExtensions {
  public INode GetParent( this INode node ) {
    // If the node implements the new interface, call it directly.
    var childNode = node as IChildNode;
    if( !object.ReferenceEquals( childNode, null ) )
      return childNode.Parent;

    // Otherwise, fall back on a default implementation.
    return FindParent( node, node.Root );
  }
}

Теперь все пользователи новой библиотеки могут рассматривать и и современные реализации прежней версии тождественно.


Перегрузки. Другая область, где дополнительные методы могут быть полезными, находится в обеспечении перегрузок для методов интерфейса. У Вас мог бы быть метод с несколькими параметрами для управления его действием, которого только первый или два важны в 90%-м случае. Так как C# не позволяет устанавливать значения по умолчанию для параметров, пользователи или должен назвать полностью параметризованный метод каждым разом, или каждая реализация должна реализовать тривиальные перегрузки для базового метода.

Вместо этого дополнительные методы могут использоваться для обеспечения тривиальных реализаций перегрузки:

public interface ILongMethod {
  public bool LongMethod( string s, double d, int i, object o, ... );
}

...
public static LongMethodExtensions {
  public bool LongMethod( this ILongMethod lm, string s, double d ) {
    lm.LongMethod( s, d, 0, null );
  }
  ...
}


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

<час>

Редактирование: А связанное сообщение Joe Duffy: Дополнительные методы как реализации метода интерфейса по умолчанию

9
ответ дан 28 November 2019 в 23:47
поделиться

Методы расширения должны использоваться именно так: расширения. Любой важный код, связанный с структурой / дизайном, или нетривиальная операция должны быть помещены в объект, который составлен / унаследован от класса или интерфейса.

Как только другой объект попытается использовать расширенный объект, он не увидит расширения и, возможно, ему придется переопределить / повторно сослаться на них снова.

Традиционная мудрость заключается в том, что методы расширения следует использовать только для:

  • служебных классов, как упомянул Вайбхав
  • , расширяющих закрытые сторонние API
11
ответ дан 28 November 2019 в 23:47
поделиться

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

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

Мои два бита.

6
ответ дан 28 November 2019 в 23:47
поделиться

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

Например:

namespace Model
{
    class Person
    {
        public string Title { get; set; }
        public string FirstName { get; set; }
        public string Surname { get; set; }
    }
}

namespace View
{
    static class PersonExtensions
    {
        public static string FullName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName + " " + p.Surname;
        }

        public static string FormalName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName[0] + ". " + p.Surname;
        }
    }
}

Таким образом, методы расширения могут использоваться аналогично шаблонам данных XAML. Вы не можете получить доступ к закрытым / защищенным членам класса, но это позволяет поддерживать абстракцию данных без чрезмерного дублирования кода во всем приложении.

3
ответ дан 28 November 2019 в 23:47
поделиться

Нет ничего неправильно с расширением интерфейсов, на самом деле именно так LINQ работает для добавления дополнительных методов к классам набора.

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

2
ответ дан 28 November 2019 в 23:47
поделиться

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

Что касается вопроса: остерегайтесь ограничений при выполнении этого - например, в показанном коде, используя метод расширения для реализации GetChildren, эффективно «запечатывает» эту реализацию и не позволяет ни одному IHaveChildren предоставлять свое собственное, если необходимо. Если все в порядке, то я не возражаю против такого метода расширения. Он не установлен в камне и обычно может быть легко подвергнут рефакторингу, когда потребуется больше гибкости.

Для большей гибкости использование шаблона стратегии может быть предпочтительным. Что-то вроде:

public interface IHaveChildren 
{
    string ParentType { get; }
    int ParentId { get; }
}

public interface IChildIterator
{
    IEnumerable<IChild> GetChildren();
}

public void DefaultChildIterator : IChildIterator
{
    private readonly IHaveChildren _parent;

    public DefaultChildIterator(IHaveChildren parent)
    {
        _parent = parent; 
    }

    public IEnumerable<IChild> GetChildren() 
    { 
        // default child iterator impl
    }
}

public class Node : IHaveChildren, IChildIterator
{ 
    // *snip*

    public IEnumerable<IChild> GetChildren()
    {
        return new DefaultChildIterator(this).GetChildren();
    }
}
2
ответ дан 28 November 2019 в 23:47
поделиться

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

Вот почему вы всегда объявляете интерфейс как тип вместо фактического класса.

IInterface variable = new ImplementingClass();

Верно?

Если вам действительно нужен контракт с некоторыми дополнительными функциями, абстрактные классы - ваши друзья.

1
ответ дан 28 November 2019 в 23:47
поделиться

Rob Connery (Дозвуковая и Витрина MVC) реализовал подобный IRepository шаблон в своем приложении Витрины. Это - не совсем шаблон выше, но это действительно совместно использует некоторые общие черты.

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

Не традиционный подход, но очень прохладный и определенно случай DRY.

1
ответ дан 28 November 2019 в 23:47
поделиться

Одна проблема, которую я вижу, состоит в том, что в большой компании этот шаблон может сделать код трудным (если не невозможным) для любого пользователя, чтобы понять и использовать. Если несколько разработчиков постоянно добавляют свои собственные методы к существующим классам, отдельно от этих классов (и, дай Бог всем нам, даже к классам BCL), я мог бы видеть, как кодовая база выходит из-под контроля довольно быстро.

Даже на моей собственной работе я мог видеть, как это происходит, с желанием моего руководителя добавить каждый бит кода, над которым мы работаем, либо в пользовательский интерфейс, либо на уровень доступа к данным, я мог видеть, что он настаивает на 20 или 30 методах. добавляемый в System.String, который только косвенно связан с обработкой строк.

0
ответ дан 28 November 2019 в 23:47
поделиться

Мне нужно было решить нечто подобное: Я хотел передать List функции расширения, где IIDable - это интерфейс с длинной функцией getId (). Я попытался использовать GetIds (этот List bla), но компилятор не позволил мне это сделать. Вместо этого я использовал шаблоны, а затем ввел внутри функции тип интерфейса. Мне нужна эта функция для некоторых классов, сгенерированных linq to sql.

Надеюсь, это поможет :)

    public static List<long> GetIds<T>(this List<T> original){
        List<long> ret = new List<long>();
        if (original == null)
            return ret;

        try
        {
            foreach (T t in original)
            {
                IIDable idable = (IIDable)t;
                ret.Add(idable.getId());
            }
            return ret;
        }
        catch (Exception)
        {
            throw new Exception("Class calling this extension must implement IIDable interface");
        }
0
ответ дан 28 November 2019 в 23:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: