Общеизвестным недостатком традиционных иерархий классов является то, что они плохи, когда дело доходит до моделирования реального мира. В качестве примера пытаемся представить виды животных с помощью классов. При этом есть несколько проблем, но я никогда не видел решения, когда подкласс «теряет» поведение или свойство, которое было определено в суперклассе, например, когда пингвин не может летать (там Возможно, это лучшие примеры, но это первое, что приходит мне в голову).
С одной стороны, вы не хотите определять для каждого свойства и поведения какой-либо флаг, который указывает, присутствует ли он вообще, и проверять его каждый раз перед доступом к этому поведению или свойству. Вы просто хотите сказать, что птицы могут просто и ясно летать в классе птиц. Но тогда было бы хорошо, если бы потом можно было определить «исключения», не прибегая к каким-либо ужасным взломам. Это часто происходит, когда система некоторое время была продуктивной. Вы неожиданно находите «исключение», которое вообще не вписывается в оригинальный дизайн, и вы не хотите изменять большую часть своего кода, чтобы приспособить его.
Итак, существуют ли какие-то языковые или конструктивные шаблоны, которые могут чисто решить эту проблему, не требуя серьезных изменений в «суперклассе» и во всем коде, который его использует? Даже если решение обрабатывает только конкретный случай, несколько решений могут вместе сформировать полную стратегию.
Подумав больше, я понимаю, что забыл о принципе замены Лискова. Вот почему вы не можете сделать это. Предполагая, что вы определяете «черты / интерфейсы» для всех основных «групп объектов», вы можете свободно реализовывать черты в разных ветвях иерархии, например, черта «Летающий» может быть реализована птицами и некоторыми особыми видами белок и рыб.
Таким образом, мой вопрос может быть таким: «Как я могу не реализовать черту?» Если ваш суперкласс является Java Serializable, вы должны быть им тоже, даже если у вас нет возможности сериализовать ваше состояние, например, если вы содержали «Socket».
Один из способов сделать это состоит в том, чтобы всегда определять все ваши черты в парах с самого начала: Flying и NotFlying (что вызовет UnsupportedOperationException, если не будет проверено против). Not-trait не будет определять какой-либо новый интерфейс, и его можно будет просто проверить. Походит на "дешевое" решение, особенно если используется с самого начала.
" it would be nice if one could define "exceptions" afterward, without having to use some horrible hacks everywhere"
считаете ли вы фабричный метод управления поведением хакерским?
NotSupportedException
из Penguin.fly()
.
class Penguin < Bird; undef fly; end;
. Если вы должны это другой вопрос.
function save_yourself_from_crashing_airplane(Bird b) { f.fly() }
станет намного сложнее. (как сказал Питер Тёрёк, это нарушает LSP)