C # имеет абстрактные классы и интерфейсы, должны ли у него также быть "mixins"? - PullRequest
31 голосов
/ 22 февраля 2010

Время от времени я сталкиваюсь с случаем, когда я хочу, чтобы коллекция классов имела одинаковую логику. Например, может быть, я хочу, чтобы и Bird, и Airplane имели возможность Fly(). Если вы думаете о «шаблоне стратегии», я бы согласился, но даже со стратегией иногда невозможно избежать дублирования кода.

Например, допустим, применяется следующее (и это очень похоже на реальную ситуацию, с которой я недавно столкнулся):

  1. И Bird, и Airplane должны содержать экземпляр объекта, который реализует IFlyBehavior.
  2. И Bird, и Airplane должны запросить экземпляр IFlyBehavior для Fly() при вызове OnReadyToFly().
  3. И Bird, и Airplane должны запросить экземпляр IFlyBehavior для Land() при вызове OnReadyToLand().
  4. OnReadyToFly() и OnReadyToLand() являются частными.
  5. Bird наследует Animal и Airplane наследует PeopleMover.

Теперь, допустим, мы позже добавим Moth, HotAirBalloon и 16 других объектов, и предположим, что все они следуют одному и тому же шаблону.

Теперь нам понадобится 20 копий следующего кода:

private IFlyBehavior _flyBehavior;

private void OnReadyToFly()
{
    _flyBehavior.Fly();
}

private void OnReadyToLand()
{
    _flyBehavior.Land();
}

Две вещи, которые мне не нравятся в этом:

  1. Это не очень СУХОЙ (одни и те же девять строк кода повторяются снова и снова). Если мы обнаружили ошибку или добавили BankRight() к IFlyBehavior, нам нужно будет распространить изменения на все 20 классов.

  2. Нет никакого способа добиться того, чтобы все 20 классов последовательно реализовывали эту повторяющуюся внутреннюю логику. Мы не можем использовать интерфейс, потому что интерфейсы разрешают только публичные члены. Мы не можем использовать абстрактный базовый класс, потому что объекты уже наследуют базовые классы, а C # не допускает множественного наследования (и даже если классы еще не наследуют классы, мы можем позже захотеть добавить новое поведение, которое реализует, скажем, ICrashable, поэтому абстрактный базовый класс не всегда будет жизнеспособным решением).

Что если ...?

Что, если в C # появилась новая конструкция, скажем, pattern или template, или [впишите вашу идею здесь], которая работала бы как интерфейс, но позволяла вам добавлять модификаторы частного или защищенного доступа к членам? Вам все еще нужно будет предоставить реализацию для каждого класса, но если ваш класс реализует шаблон PFlyable, у вас будет хотя бы способ обеспечить, чтобы у каждого класса был необходимый шаблонный код для вызова Fly() и Land(). А в современной IDE, такой как Visual Studio, вы сможете автоматически генерировать код с помощью команды «Реализация шаблона».

Лично я думаю, что было бы более целесообразно просто расширить значение интерфейса, чтобы охватить любой контракт, будь то внутренний (частный / защищенный) или внешний (публичный), но я предложил сначала добавить целую новую конструкцию, потому что люди быть очень непреклонным в отношении значения слова «интерфейс», и я не хотел, чтобы семантика стала центром ответов людей.

Вопросы:

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

Обновление

Из комментария AakashM я считаю, что уже есть название для функции, которую я запрашиваю: a Mixin . Итак, я думаю, мой вопрос может быть сокращен до: «Должен ли C # разрешать Mixins?»

Ответы [ 7 ]

3 голосов
/ 22 февраля 2010

Проблему, которую вы описываете, можно решить с помощью шаблона Visitor (все можно решить с помощью шаблона Visitor, так что будьте осторожны!)

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

Подводя итог:

  1. Новые функции не нужно добавлять ко всем различным типам
  2. Звонок посетителю можно перенести в корень каждой иерархии классов

Для справки см. Шаблон посетителя

2 голосов
/ 22 февраля 2010

Не можем мы использовать методы расширения для этого

    public static void OnReadyToFly(this IFlyBehavior flyBehavior)
    {
          _flyBehavior.Fly()
    }

Это имитирует функциональность, которую вы хотели (или Mixins)

2 голосов
/ 22 февраля 2010

Visual Studio уже предлагает это в «бедной форме» с фрагментами кода. Кроме того, с помощью инструментов рефакторинга, таких как ReSharper (и, возможно, даже встроенной поддержки рефакторинга в Visual Studio), вы получаете долгий путь в обеспечении согласованности.

[РЕДАКТИРОВАТЬ: я не думал о методах расширения, этот подход еще больше продвигает вас (вам нужно только сохранить _flyBehaviour в качестве закрытой переменной). Это делает остальную часть моего ответа, вероятно, устаревшей ...]

Тем не менее, просто ради обсуждения: как это можно улучшить? Вот мое предложение.

Можно представить что-то вроде следующего, которое будет поддерживаться будущей версией компилятора C #:

// keyword 'pattern' marks the code as eligible for inclusion in other classes
pattern WithFlyBehaviour
{
   private IFlyBehavior_flyBehavior;

   private void OnReadyToFly()
   {
      _flyBehavior.Fly();
   }

   [patternmethod]
   private void OnReadyToLand()
   {
      _flyBehavior.Land();
   }     
}

Что бы вы могли использовать тогда что-то вроде:

 // probably the attribute syntax can not be reused here, but you get the point
 [UsePattern(FlyBehaviour)] 
 class FlyingAnimal
 {
   public void SetReadyToFly(bool ready)
   {
      _readyToFly = ready;
      if (ready) OnReadyToFly(); // OnReadyToFly() callable, although not explicitly present in FlyingAnimal
   }

 }

Будет ли это улучшением? Наверное. Это действительно того стоит? Может быть ... * * 1013

1 голос
/ 22 февраля 2010

Вы только что описали аспектно-ориентированное программирование .

Одной из популярных реализаций AOP для C #, по-видимому, является PostSharp (хотя основной сайт у меня не работает / не работает, эта является прямой страницей "О программе").


Чтобы прокомментировать комментарий: я не уверен, поддерживает ли PostSharp его, но я думаю, что вы говорите о этой части AOP:

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

0 голосов
/ 10 июля 2010

Черты Scala были разработаны для решения такого сценария. Есть также некоторые исследования, чтобы включить черты в C # .

ОБНОВЛЕНИЕ: Я создал собственный эксперимент, чтобы иметь ролей в C # . Посмотри.

0 голосов
/ 22 февраля 2010

Можете ли вы получить такое поведение, используя новый ExpandoObject в .NET 4.0?

0 голосов
/ 22 февраля 2010

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

  1. Пусть объекты Bird и Plane реализуют свойство для IFlyBehavior объекта для интерфейса IFlyer

    public interface IFlyer 
    {  
        public IFlyBehavior FlyBehavior 
    }
    
    public Bird : IFlyer
    {
        public IFlyBehaviour FlyBehavior {get;set;}
    }
    
    public Airplane : IFlyer
    {
        public IFlyBehaviour FlyBehavior {get;set;}
    }
    
  2. Создать класс расширения для IFlyer

    public IFlyerExtensions
    {
    
        public void OnReadyToFly(this IFlyer flyer) 
        {
            flyer.FlyBehavior.Fly(); 
        }
    
        public void OnReadyToLand(this IFlyer flyer) 
        {
            flyer.FlyBehavior.Land(); 
        }
    }
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...