пустой интерфейс (абстрактный класс) для логической группировки объектов, чтобы избежать нарушений LSP - PullRequest
1 голос
/ 17 августа 2011

В реальном мире квадрат - это прямоугольник, но для программы это не так ( Принцип LSP ). Считается ли анти-паттерном / плохим программированием создание пустого интерфейса для логической группировки объектов? Я думаю, что с пустым интерфейсом было бы невозможно нарушить LSP на уровне непосредственно под ним. Придуманный пример ниже.

Abstract class Canine{ }  

Dog extends Canine {…} 

Wolf extends Canine {…}

Ответы [ 3 ]

6 голосов
/ 17 августа 2011

Зависит от того, для чего это.

Пустой интерфейс не предъявляет синтаксических требований, так как нет методов для реализации.

Но интерфейс может быть задокументирован для наложения семантических требований, особенно если Canine сам наследует от Canoidea или Carnivora или чего-либо еще. Это делает пустой интерфейс потенциально полезным, поскольку он обеспечивает собственные гарантии. У всех Carnivora есть уши [*], но Canoidea характеризуются частично некоторым свойством ушей, которое не так просто выразить, как добавление метода, поэтому реализация интерфейса становится обещанием: «В дополнение к тому, что я Carnivora, мой уши так себя ведут ".

Это означает, что больше нельзя нарушать LSP. Вы можете случайно написать подкласс с переопределенной функцией, которая возвращает 12, что разрешено для Carnivora, но семантика Canine говорит, что он не вернет больше 10.

Интерфейсы, которые добавляют только семантику, не обязательно хороши для работы, поскольку есть определенные ошибки, которые легко сделать. Примером является разница между InputIterator и ForwardIterator в C ++ (хотя, конечно, это концепции шаблонов, а не унаследованные интерфейсы). Если вы пометите свой класс неправильным, компилятор никогда не заметит. Но если вы пометите свой класс RandomAccessIterator, когда на самом деле это всего лишь ForwardIterator, то компилятор будет замечать, когда кто-то пытается использовать operator+, поскольку operator+ является частью RandomAccessIterator но не ForwardIterator.

Пустой интерфейс без семантики бессмысленен и, следовательно, вероятно, бесполезен. Единственное использование, о котором я могу подумать, - это своего рода вариант - какой-то хитрый код может проверить, является ли объект экземпляром «Dog» или «Wolf» (или что-то еще), и соответственно делать разные вещи. Это, вероятно, не очень хорошее применение, так как, во-первых, это не очень хороший код. Вы могли бы также использовать конечный суперкласс, такой как Object, если он доступен, поскольку в любом случае код должен справляться с типами, которые он не распознает и не может обработать. «Что на земле Фокс, я не могу использовать Фокса здесь» не лучше и не хуже, чем «Что на Земле это Вектор, я не могу использовать Вектор здесь», так что если вы понимаете, что это собаки и волки Нет никакого реального преимущества в принятии Canine по сравнению с принятием Object. Они оба варианта, которые позволяют Собакам, Волкам, плюс что-нибудь еще, что кто-то выбирает, чтобы вставить в иерархию.

[*] Да, даже тюлени. У них просто нет ушных раковин.

2 голосов
/ 17 августа 2011

Нет, это не сломает LSP, поскольку с базой ничего нельзя сделать, и поэтому ни один из классов не может нарушить LSP.

Иногда я использую пустые интерфейсы для группирования классов. Я делаю это, чтобы показать какое-то намерение. Например, я получил пустой интерфейс с именем IMessage, который используется, чтобы показать, что цель класса - обрабатываться только как сообщение в моей структуре pub / sub. (И только классы, которые реализуют интерфейс, могут транспортироваться в нем).

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

0 голосов
/ 17 августа 2011

В вашем сценарии, как это помогает нам.У нас есть абстрактный класс Rectangle?Что мешает нам утверждать, что Квадрат расширяет Прямоугольник?

...