2017-01-05 2 views
1

Предположим, у меня есть абстрактная птица класса, и одна из ее функций - fly (int height).Liskov принцип подстановки дизайна адаптации

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

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

Добавление утки как подтипа птицы нарушает правило замены Лискова, потому что при вызове duck.fly мы либо бросали исключение, ничего не делали и не нарушали бы принцип правильности.

Как бы вы начали вводить эти изменения, имея в виду принципы дизайна SOLID?

+1

Не совсем нарушение ЛСД. Дегенерированная реализация является лишь признаком нарушения ЛСД. Вы должны рассмотреть контракт своего метода fly(). На что повлияет клиент, когда муха() утки ничего не делает? – Kata

+0

Этот вопрос похож на знаменитый треугольник и квадратную проблему. Я думаю, что утка не должна быть подтипом птицы. – shreesha

+0

Не является нарушением LSP, если вы не делаете исключение в 'duck.fly()' или вы нарушаете некоторые постусловия этого метода или инварианты класса 'Bird' (которые нелегко угадать в таком надуманном примере) – guillaume31

ответ

1

Я вижу 3 варианта для вас:

  1. Используйте Bird как абстрактный базовый класс для общей функциональности и извлечь из него FlyingBird и AquaticBird.

  2. Использование композиции объекта и рисунка посетителя, как описано Зораном Хорватом: https://vimeo.com/195774910 (что стоит посмотреть в любом случае). Хотя это кажется излишним в данном случае.

  3. Используйте это решение, как вы описали его.

В конце, это все о балансе. Если вы ожидаете много разных птиц с разными способностями, чтобы присоединиться, то вы должны серьезно рассмотреть вариант 2, в противном случае в зависимости от того, как использовать классы позже выбрать между 1 и 3.

Update как на Gilads комментарии о Вариант 2 (Object Состав & посетителей Pattern)

Главное здесь в том, что вы можете иметь один класс Bird и приложить способностей к нему, такие как Flying и Swimming и, возможно, позже вы решите, что у вас есть другие способности или классификация какого-либо вида. Вы можете сделать это либо путем создания различных интерфейсов (IFlyable, ISwimmable) и что-то вроде этого:

public class SomeFlyingBird : BirdBase, IFlyable 
{ 
.... 
} 

public class Duck : BirdBase, ISwimmable 
{ 
.... 
} 

И затем использовать шаблон посетителя посетить конкретные действия.

Или, если вы ищете что-то более надежное и элегантное вы можете создать различные классы в способность и прикрепить их к классу птиц:

public class Bird/Animal 
{ 
    string name; 
    List<Ability> abilities = new List<Ability>(); 

    public Bird (string name) 
    { 
     this.name = name; 
    } 

    public void Add (Ability ability) 
    { 
      this.abilities.Add(ability); 
    } 
} 

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

public static Bird CreateDuck() 
{ 
    var duck = new Bird("Donald"); 
    duck.Add(new SwimAbility()); 
    return duck; 
} 

Способность и плавать может выглядеть следующим образом:

public abstract class Ability 
{ 
    public virtual void Accept(AbilityVisitor visitor) => 
     visitor.Visit(this); 
} 

public class SwimAbility : Ability 
{ 
    public override void Accept(AbilityVisitor visitor) 
    { 
     base.Accept(visitor); 
     visitor.Visit(this); 
    } 
} 

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

Я настоятельно советую вам смотреть видео с Зораном Хорвата и скачать и играть с кодом, который он обеспечивает: http://www.postsharp.net/blog/post/webinar-recording-object-composition

+0

Я не могу заставить видео работать, вариант 2 звучит интересно, можете немного рассказать об этом? –

+0

Я исследовал посетителя, но я не вижу, как он может решить проблему под рукой любым элегантным способом. –

+0

Это можно сделать таким образом, но я бы согласился с вашими комментариями, что это лишний случай. Другим моментом является то, что он добавляет возможности во время выполнения, что затрудняет обеспечение того, чтобы все утки были сконструированы таким образом, чтобы дать им возможность плавать, и может сделать тестирование сложнее (просто потому, что вы не забыли добавить эту способность в свои единичный тестовый код не означает, что у него есть настоящий код). – ROX

2

вопрос идентифицировать из-за следующие несовместимые особенности модели.

  1. Все птицы могут летать
  2. Утки птица
  3. Уток не могут летать

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

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

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

Независимо от того, разрешаете ли вы совершать полеты для уток, все в порядке, зависит от того, что ваш кодовый код ожидает от летать вообще, - в действительности вы исправляете несогласованность, изменяя значение мухи в пункте 1 на «Все птицы могут быть заданы летать (хотя некоторые не будут) ». В этом случае fly действительно становится flyIfyouCan, который может быть признаком несовершенного дизайна, но может быть прагматичным решением, особенно при адаптации существующего дизайна.

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

В общем, я думаю, что вы на самом деле описываете изменение точки 1, поскольку «все птицы могут летать», чтобы «все птицы могли двигаться», а затем не-утки реализуют движение, поскольку муха и утки реализуют движение как плавание (есть ли у них также метод мух или нет). Это, вероятно, связано с изменением некоторых существующих звонков, чтобы летать на вызовы для перемещения.

+1

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

+0

Это означает, что мы соглашаемся с тем исключением. Ничего не стоит делать не всегда плохо - в этом случае было бы странно, если бы муха ничего не делала (я ожидаю состояние сообщения, связанное с изменением местоположения птиц), но в некоторых реальных случаях это может быть ОК, если ничего не делать совместим с ожидаемыми условиями сообщения. – ROX

+0

В общем, я постараюсь, чтобы модель была такой же, как и в случае с реальным миром. Это делает его более надежным в будущем в отношении изменений требований (конечно, иногда мы получаем запросы на изменение, которые не реалистичны, но мы не можем легко предсказать, что это будет) – ROX

0

Так что ваша проблема здесь

(1) Утка птица.

(2) Но он не может летать.

Так реальная проблема,

  • Вы реализовали свой класс Bird предполагая "Все птицы могут летать".
  • Но это действительно не так. Таким образом, вы либо не думали о угловых случаях (например, Duck) раньше, либо не намеревались иметь такие классы, как Duck.

Решение:

Так простейшим и мудрейшим решение будет принято здесь использовать общее имя для имени метода, так что обе стороны могут прийти к согласию. Итак, здесь вы можете переименовать свой метод Bird.fly() в Bird.move().

Таким образом, как Duck.move(), так и Crow.move() имеют смысл, но они делают по-своему.

Все другие доступные решения могут убить Полиморфизм (Использование двух разных базовых классов). Это означает, что вы не сможете называть обе утки и ворон для перемещения (плавать и летать соответственно) одним методом. (например, Bird.move() do).

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

+0

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

+0

@GiladBaruchian Я согласен с вашей точкой в ​​предположении. На самом деле я просто указал, что такое реальный случай, а не на то, чтобы сказать, что этого следовало избегать. Но я не думаю, что у вас есть правильное намерение по поводу решения. Это лучшее динамическое решение. Быть родовым - это не пункт, чтобы отвергнуть. И вы спрашиваете о шаблоне. Дело в том, что вы должны строго избегать перетаскивания шаблонов в ваш код, если только это не отражает вашу проблему. Если вы на самом деле не закончите с созданием анти-шаблона и вреда себе в течение длительного времени. –

Смежные вопросы