Наследование пошло не так


12

У меня есть код, где хорошая модель наследования пошла под откос, и я пытаюсь понять, почему и как это исправить. По сути, представьте, что у вас есть иерархия 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 повсеместно. Здесь вы можете иметь более специфичные интерфейсы, но если все, что у вас есть, это ссылка на базовый класс, которая тоже мало поможет Любые другие предложения? Примеры?

Благодарность!


@James: тогда вам придется писать парсеры вручную. : S
Matteo Italia

5
Ясно, что это случай смешного требования клиента. В Африке нет бизонов. Вы не можете проектировать объектные модели, которые не имеют никакого отношения к реальности. Если эта реальность не создана руками, полными долларов. Что решает проблему.
Ганс Пассант

1
Съешь всех зубров? [Я опубликовал это ранее, но по какой-то причине оно было удалено, предположительно бездыханным ослом.]
Джеймс МакНеллис

Нужен ли CageBuilder свой собственный класс? Что делать, если есть метод MakeCage по умолчанию, который может быть переопределен каждым отдельным классом.
Работа

1
Вы упоминаете беспорядок if-then-else как недостаток для вызывающих, но как только вызывающие абоненты начинают использовать isAfricanBizon (), они автоматически загромождают код if-then-else. Так что это либо беспорядок «если-то-еще» с помощью isAfricanBizon (), либо беспорядок «если-то-еще» с динамическим приведением типов.
davidk01

Ответы:


13

Кажется, что проблема заключается в том, что вместо того, чтобы реализовывать RequConcreteWall (), они реализовали вызов флага IsAfricanBison (), а затем переместили логику того, должна ли стена меняться за пределами области действия класса. Ваши занятия должны раскрывать поведение и требования, а не личность; Ваши потребители этих классов должны работать исходя из того, что им говорят, а не исходя из того, что они есть.


1
-1: говорит только то, что не надо делать. ОП уже знает, что это была плохая идея, поэтому вопрос.
Стивен Эверс

12

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

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


+1: Все остальные, кажется, нарушают концептуальную модель класса (которая просто инкапсулирует свойства различных видов животных), чтобы приспособить этот конкретный вариант использования. strengthМетод может быть запрошен material.canHold(animal), позволяя чистый способ поддержки различных видов материала , чем ConcreteWall.
Эйдан Калли

Мне нравится подход со свойством силы () лучше, чем другие считают, что требуется RequConcreteWall (), потому что он более гибкий для включения будущих требований. Для начала сделайте так, чтобы класс CageBuilder определил, какие материалы достаточно прочные, и тогда вы сможете легко расширить класс новыми материалами.
Джокинг

3

Я думаю, что ваша проблема заключается в следующем: у вас есть различные клиенты библиотеки, которые заинтересованы только в подмножестве иерархии, но передают указатель / ссылку на базовый класс. Это на самом деле проблема, которую dynamic_cast <> должен решить.

Клиенты должны минимизировать использование dynamic_cast <>; они должны использовать его, чтобы определить, требует ли объект особой обработки, и, если это так, делают все операции с опущенной ссылкой.

Если у вас есть наборы функциональных возможностей типа «встраивание», которые применяются к нескольким отдельным иерархиям, вы можете использовать шаблон интерфейса, который используют Java и C #; иметь виртуальный базовый класс, который является чисто виртуальным классом, и использовать dynamic_cast <>, чтобы определить, предоставляет ли экземпляр для него реализацию.


1

Одна вещь , которую вы можете сделать , это заменить явной проверки типа , как isAfricanBison()с проверкой свойств вы на самом деле интересует, то есть isTooStrong().


1
isTooStrong () для чего? Вы добавляете код, специфичный для клетки, в класс животных.
Стивен Эверс

1

Животные не должны заботиться о бетонных стенах. Может быть, вы можете выразить это с помощью простых значений.

class Animal {
public:
  virtual ~Animal() {}
  virtual size_t height() const = 0;
  virtual size_t weight() const = 0;
  virtual bool isStrong() const = 0;
};

Cage *CreateCageFromSQL(Animal &a);
Cage *CreateCageFromOrangePeelsAndSticks(Animal &a);

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

Я бы никогда не хотел видеть requireConcreteWalls () или строки и строки динамического приведения указателя во всяком случае.

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

class Animal {
public:
  virtual ~Animal() {}
  virtual CageBuilder *getCageBuilder() = 0;
};

Это не мешает вам использовать общий код, просто немного загрязняет Animal.

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

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

Двойная отправка - это еще один вариант, хотя я всегда старался прыгнуть в него.

Помимо этого трудно угадать проблему.


0

Ну, конечно, все животные обладают свойством attemptEscape(). В то время как одни метод может falseдать результат во всех сценариях, в то время как другие могут иметь шанс, основанный на эвристике их других внутренних характеристик, таких как sizeи weight. Тогда, безусловно, в какой-то момент attemptEscape()становится тривиальным, поскольку он наверняка вернется true.

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


-1

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



-2

Почему бы не сделать что-то подобное

class Animals { /***/ } class HeavyAnimals{} : Animals //The basic class for animals like the African Bison

С классом HeavyAnimals вы можете создать класс африканских бизонов, расширив класс HeavyAnimals.

Итак, теперь вы родительский класс (Животные), который можно использовать для создания другого базового класса, например, класса HeavyAnimal, с которым можно создавать класс африканских бизонов и других тяжелых животных. Так что с африканским бизоном у вас теперь есть доступ к методам и свойству класса Animal (это база для всех животных) и доступ к классу HeavyAnimals (это база для Heavy Animals)


2
Это может работать как дополнение или черта, но, конечно, не как подкласс. Это просто просьба о множественном наследовании в следующий раз, когда понадобится другое свойство.
Ордус
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.