У меня есть код, где хорошая модель наследования пошла под откос, и я пытаюсь понять, почему и как это исправить. По сути, представьте, что у вас есть иерархия Zoo с:
class Animal
class Parrot : Animal
class Elephant : Animal
class Cow : Animal
и т.п.
У вас есть методы eat (), run () и т. Д., И все хорошо. Затем однажды кто-то приходит и говорит - наш класс CageBuilder работает отлично и использует animal.weight () и animal.height (), за исключением нового африканского бизона, который слишком силен и может разрушить стену, поэтому я добавлю еще одно свойство класса Animal - isAfricanBizon (), которое используется при выборе материала и переопределяет его только для класса AfricanBizon. Следующий человек приходит и делает что-то подобное, и в следующий раз вы знаете, что у вас есть все эти свойства, специфичные для некоторого подмножества иерархии в базовом классе.
Какой хороший способ улучшить / реорганизовать такой код? Одна альтернатива здесь будет состоять в том, чтобы просто использовать dynamic_casts для проверки типов, но это загромождает вызывающих и добавляет кучу if-then-else повсеместно. Здесь вы можете иметь более специфичные интерфейсы, но если все, что у вас есть, это ссылка на базовый класс, которая тоже мало поможет Любые другие предложения? Примеры?
Благодарность!
источник
Ответы:
Кажется, что проблема заключается в том, что вместо того, чтобы реализовывать RequConcreteWall (), они реализовали вызов флага IsAfricanBison (), а затем переместили логику того, должна ли стена меняться за пределами области действия класса. Ваши занятия должны раскрывать поведение и требования, а не личность; Ваши потребители этих классов должны работать исходя из того, что им говорят, а не исходя из того, что они есть.
источник
isAfricanBizon () не является универсальным. Предположим, вы расширяете свою животноводческую ферму гиппопотамом, который также слишком силен, но возвращает true из isAfricanBizon (), чтобы получить надлежащий эффект, было бы просто глупо.
вы всегда хотите добавить методы интерфейса, которые отвечают на конкретный вопрос, в этом случае это будет что-то вроде силы ()
источник
strength
Метод может быть запрошенmaterial.canHold(animal)
, позволяя чистый способ поддержки различных видов материала , чемConcreteWall
.Я думаю, что ваша проблема заключается в следующем: у вас есть различные клиенты библиотеки, которые заинтересованы только в подмножестве иерархии, но передают указатель / ссылку на базовый класс. Это на самом деле проблема, которую dynamic_cast <> должен решить.
Клиенты должны минимизировать использование dynamic_cast <>; они должны использовать его, чтобы определить, требует ли объект особой обработки, и, если это так, делают все операции с опущенной ссылкой.
Если у вас есть наборы функциональных возможностей типа «встраивание», которые применяются к нескольким отдельным иерархиям, вы можете использовать шаблон интерфейса, который используют Java и C #; иметь виртуальный базовый класс, который является чисто виртуальным классом, и использовать dynamic_cast <>, чтобы определить, предоставляет ли экземпляр для него реализацию.
источник
Одна вещь , которую вы можете сделать , это заменить явной проверки типа , как
isAfricanBison()
с проверкой свойств вы на самом деле интересует, то естьisTooStrong()
.источник
Животные не должны заботиться о бетонных стенах. Может быть, вы можете выразить это с помощью простых значений.
Я подозреваю, что это не жизнеспособно, хотя. Это проблема с игрушечными примерами.
Я бы никогда не хотел видеть requireConcreteWalls () или строки и строки динамического приведения указателя во всяком случае.
Обычно это дешевое решение. Это легко поддерживать и осмыслять. И действительно, проблема гласит, что в любом случае это связано с типом животных.
Это не мешает вам использовать общий код, просто немного загрязняет Animal.
Но то, как строится клетка, может быть политикой какой-то другой системы, и, возможно, у вас есть более одного типа строителя клетки на животное. Есть много странных и запутанных комбинаций, которые вы можете придумать.
Я использовал компонентно-ориентированный дизайн для благих целей, главная проблема в том, что это может быть хлопотно, когда право собственности Animal разделено. Как избежать попадания деструкторов, являющихся болевыми точками.
Двойная отправка - это еще один вариант, хотя я всегда старался прыгнуть в него.
Помимо этого трудно угадать проблему.
источник
Ну, конечно, все животные обладают свойством
attemptEscape()
. В то время как одни метод можетfalse
дать результат во всех сценариях, в то время как другие могут иметь шанс, основанный на эвристике их других внутренних характеристик, таких какsize
иweight
. Тогда, безусловно, в какой-то моментattemptEscape()
становится тривиальным, поскольку он наверняка вернетсяtrue
.Боюсь, я не совсем понимаю ваш вопрос, хотя ... у всех животных есть связанные действия и характеристики. Специфичные для животного должны быть введены там, где оно подходит. Попытка напрямую связать бизона с попугаями не является хорошей настройкой присущи и не должна быть проблемой при правильном дизайне.
источник
Другой вариант - использовать фабрику, которая создает клетки, подходящие для каждого животного. Я думаю, что это может быть лучше, если условия очень разные для каждого из них. Но если это всего лишь одно условие, вышеупомянутый
RequiresConcreteWall()
метод сделает это.источник
как насчет RecomCageType () в отличие от requireConcreteWall ()
источник
Почему бы не сделать что-то подобное
class Animals { /***/ } class HeavyAnimals{} : Animals //The basic class for animals like the African Bison
С классом HeavyAnimals вы можете создать класс африканских бизонов, расширив класс HeavyAnimals.
Итак, теперь вы родительский класс (Животные), который можно использовать для создания другого базового класса, например, класса HeavyAnimal, с которым можно создавать класс африканских бизонов и других тяжелых животных. Так что с африканским бизоном у вас теперь есть доступ к методам и свойству класса Animal (это база для всех животных) и доступ к классу HeavyAnimals (это база для Heavy Animals)
источник