Объектно-ориентированный дизайн

23

Предположим, у вас есть следующее:

     +--------+     +------+
     | Animal |     | Food |
     +-+------+     +----+-+
       ^                 ^
       |                 |
       |                 |
  +------+              +-------+
  | Deer |              | Grass |
  +------+              +-------+

Deerнаследует от Animalи Grassнаследует от Food.

Все идет нормально. Animalобъекты могут есть Foodобъекты.

Теперь давайте немного перемешаем. Давайте добавим, Lionкоторый наследуется от Animal.

     +--------+     +------+
     | Animal |     | Food |
     +-+-----++     +----+-+
       ^     ^           ^
       |     |           |
       |     |           |
  +------+ +------+     +-------+
  | Deer | | Lion |     | Grass |
  +------+ +------+     +-------+

Теперь у нас есть проблема, потому что Lionможет есть и то, Deerи другое Grass, но Deerэто не Foodтак Animal.

Как решить эту проблему без использования множественного наследования и объектно-ориентированного проектирования?

К вашему сведению: я использовал http://www.asciiflow.com для создания диаграмм ASCII.

Майкл Ирей
источник
14
Моделирование реального мира обычно является проблемой рано или поздно, потому что всегда происходит что-то странное (например, летающая рыба, рыба или птица? Но пингвин - это птица, она не умеет летать и ест рыбу). То, что говорит @Ampt, звучит правдоподобно, Животное должно иметь коллекцию вещей, которые оно ест.
Роб ван дер Веер
2
Я думаю, что животное должно наследовать от еды. Если что-то пытается съесть Льва, просто сгенерируйте исключение InvalidOperationException.
Ральф Чапин
4
@RalphChapin: все виды едят льва (стервятники, жуки и т. Д.). Я думаю, что животные и еда - это искусственные различия, которые сломаются, потому что они недостаточно широки (в конце концов, все животные - это пища некоторых других животных). Если бы вы классифицировали «LivingThing», вам пришлось бы иметь дело только с крайними случаями с растениями, которые едят неживые существа (минералы и т. Д.), И ничто не сломало бы наличие LivingThing.Eat (LivingThing).
Satanicpuppy
2
Поделиться своими исследованиями помогает всем. Расскажите нам, что вы пробовали и почему это не соответствует вашим потребностям. Это свидетельствует о том, что вы потратили время, чтобы попытаться помочь себе, избавляет нас от повторения очевидных ответов и, прежде всего, помогает получить более конкретный и актуальный ответ. Также см. Как спросить
комнат
9
На этот вопрос ответила игра Age of Empire III. ageofempires.wikia.com/wiki/List_of_Animals Олени и Газели реализуют IHuntable, Овцы и Коровы IHerdable(управляются человеком), а Лион реализует только IAnimal, что не подразумевает какой-либо из этих интерфейсов. AOE3 поддерживает запрос набора интерфейсов, поддерживаемых конкретным объектом (аналогично instanceof), что позволяет программе запрашивать его возможности.
Rwong

Ответы:

38

ИС отношения = наследование

Лев это животное

ИМЕЕТ отношения = Состав

У машины есть колесо

МОЖЕТ СДЕЛАТЬ отношения = Интерфейсы

Я могу есть

Роберт Харви
источник
5
+1 Это так просто и, тем не менее, такое хорошее резюме трех разных типов отношений
dreza
4
Альтернатива: ICanBeEatenилиIEdible
Майк Веллер
2
Отношения CAN HAZ = lolcats
Стивен А. Лоу
1
Как это отвечает на вопрос?
user253751
13

ОО - это просто метафора, которая образует себя после реального мира. Но метафоры только заходят так далеко.

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

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

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

ДПМ
источник
3
+1 ОО это инструмент, а не религия.
Mouviciel
Я согласен, не может быть идеального решения, если эта проблема продолжает изменяться и развиваться. В текущем состоянии этой проблеме не хватает информации о домене, чтобы найти решение?
Майкл Ирей
Вы серьезно думаете, что в OP моделируется реальный мир? Модель отношений представлена ​​на примере.
Базилевс
@Basilevs Это своего рода подтекст, на самом деле, поскольку он упоминает, как животные ведут себя в реальной жизни. Нужно заботиться о том, почему нужно, чтобы такое поведение учитывалось в программе IMO. Тем не менее, было бы мило с моей стороны предложить некоторые возможные конструкции.
DPM
10

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

             +----------------+                   +--------------------+
             |    Animal      |                   |      Food          |
             |----------------|<--+Interfaces+--->|--------------------|
             |                |                   |                    |
             +----------------+                   +--------------------+
                +           +                       +                 +
                |           |    Abstract Classes   |                 |
                |           |        |          |   |                 |
                v           v        v          v   v                 v
   +-----------------+  +----------------+     +------------+      +------------+
   |   Herbivore     |  |  Carnivore     |     |   Plant    |      |   Meat     |
   |-----------------|  |----------------|     |------------|      |------------|
   |Eat(Plant p)     |  |Eat(Meat m)     |     |            |      |            |
   |                 |  |                |     |            |      |            |
   +-----------------+  +----------------+     +------------+      +------------+
            +                    +                    +                   +
            |                    |                    |                   |
            v                    v                    v                   v
   +-----------------+  +----------------+     +------------+      +------------+
   |  Deer           |  |   Lion         |     |  Grass     |      |  DeerMeat  |
   |-----------------|  |----------------|     |------------|      |------------|
   |DeerMeat Die()      |void Kill(Deer) |     |            |      |            |
   +-----------------+  +----------------+     +------------+      +------------+
                                 ^                    ^
                                 |                    |
                                 |                    |
                              Concrete Classes -------+

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

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

Ampt
источник
Олень тогда выставит интерфейс IMeat?
Дэн Пичельман,
Мясо - это не интерфейс, а абстрактный класс. Я добавил, как я бы это реализовал для вас
Ampt
Eat(Plant p)и Eat(Meat m)оба нарушают ЛСП.
Тулаинс Кордова
Как же так @ user61852? Я намеренно не выставлял Eat в интерфейсе животных, чтобы у каждого типа животных был свой метод питания.
Ampt
1
TCWL (слишком сложный, будет течь). Проблема распределена и возникает, и ваше решение является статичным, централизованным и предопределенным. TCWL.
Тулаинс Кордова
7

Мой дизайн будет выглядеть так:

  1. Продукты объявлены как интерфейсы; есть интерфейс IFood и два производных интерфейса от него: IMeat и IVegetable
  2. Животные реализуют IMeat, а Овощи - IVegetable
  3. Животные имеют двух потомков, плотоядных и лесных животных
  4. У плотоядных есть метод Eat, который получает экземпляр IMeat
  5. У травоядных есть метод Eat, который получает экземпляр IVegetable
  6. Лев спускается с хищника
  7. Олень спускается с травоядного
  8. Трава спускается с Овощей

Поскольку животные реализуют IMeat, а Олень - это (Травоядное) животное, Лев, который является (Плотоядным) животным, которое может есть IMeat, может также есть и оленей.

Олень - это травоядное животное, поэтому он может есть траву, потому что в нем реализован овощ.

Плотоядные не могут есть IVegeable, а травоядные не могут есть IMeat.

AlexSC
источник
1
Я вижу здесь много типов перечисления, использующих наследование, просто для ограничения, когда наследуемые типы ничего не реализуют ... Всякий раз, когда вы обнаруживаете, что создаете типы, которые вообще не реализуют какую-либо функциональность, это что-то пустое; Вы расширили модель в системе типов, которая не дает никакого значения юзабилити в коде
Джимми Хоффа
Помните, чем существуют всеядные существа, такие как люди, обезьяны и медведи.
Тулаинс Кордова
Итак, как вы добавите, что оба, львы и олени, являются млекопитающими? :-)
Йоханнес
2
@JimmyHoffa Они называются «маркерными интерфейсами» и являются полностью допустимым использованием интерфейса. Это должно быть проверено кодом, чтобы решить, является ли использование оправданным, но есть много вариантов использования (таких как этот, где Лев, пытающийся съесть Траву, вызвал бы исключение NoInterface). Интерфейс маркера (или его отсутствие) служит для предсказания исключения, которое будет выдано, если метод вызывается с неподдерживаемыми аргументами.
Rwong
1
@ rwong Я понимаю концепцию, никогда не слышал ее формализованной раньше; просто мой опыт был каждый раз, когда кодовая база, в которой я работал, делает их более сложными и сложными в обслуживании. Возможно, мой опыт, однако, просто был там, где люди использовали их неправильно.
Джимми Хоффа
5

Какая пища может есть животное, на самом деле не образует иерархию, в этом случае природа непростительно не соответствовала простому объектно-ориентированному моделированию (заметьте, что даже если бы это было так, животное должно было бы наследовать от пищи, поскольку это пища).

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

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

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

PSR
источник
Как говорят о регулярном выражении «У меня была проблема, поэтому я использовал регулярное выражение, теперь у меня есть две проблемы», MI примерно такой же, как «У меня была проблема, поэтому я использовал MI, теперь у меня есть 99 проблем». Следуя той тщеславности, на которую ты тут сунулся, хотя из еды, зная, что ее можно есть, это на самом деле упрощает модель на тонну. Инверсия зависимостей FTW.
Джимми Хоффа
1

Я подойду к проблеме с другой стороны: ООП - это поведение. В вашем случае, Grassесть ли какое-то поведение, чтобы быть ребенком Food? Так что в вашем случае не будет Grassкласса, или, по крайней мере, он не будет наследоваться от Food. Кроме того, если вам необходимо установить, кто может есть что во время компиляции, сомнительно, что вам нужна Animalабстракция. Кроме того, нередко можно увидеть плотоядных животных, поедающих траву , хотя и не для пропитания.

Так что я бы разработал это как (не собираясь беспокоиться об искусстве ASCI):

IEdibleсо свойством Type, которое представляет собой перечисление мяса, растений, туш и т. д. (это не будет часто меняться и не будет иметь какого-либо особого поведения, поэтому нет необходимости моделировать его как класс hiearchy).

Animalс методами CanEat(IEdible food)и Eat(IEdible food), которые логичны. Затем определенные животные могут проверять всякий раз, когда они могут съесть данную пищу в определенных обстоятельствах, а затем съесть эту еду, чтобы получить средства к существованию / сделать что-то еще. Кроме того, я бы смоделировал классы «Плотоядное животное» , «Травоядное животное», «Всеядное животное» как образец стратегии , а не как часть иерархии животных.

Euphoric
источник
1

TL; DR: дизайн или модель с контекстом.

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

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

Например, более подробные требования потребуют различных объектных отношений:

  • поддержка Animals eatнон- FoodлайкGastroliths
  • поддержка Chocolateкак Poisonдля Dogs, но не дляHumans

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

dietbuddha
источник
Я согласен, но это всего лишь маленький пример, и мы не пытались моделировать мир. Например, у вас может быть Акула, которая ест Шины и Номерные знаки. Вы можете просто создать родительский абстрактный класс с помощью метода, который питается любым видом объекта, и Food может расширить этот абсолютный класс.
hagensoft
@hagensoft: Согласен. Я иногда увлекаюсь, потому что постоянно вижу, как разработчики моделируют на основе метафоры, которой они сразу пользуются, а не смотрят на то, как данные должны потребляться и использоваться. Они вступают в брак с ОО-проектом, основанным на первоначальной идее, а затем пытаются заставить проблему соответствовать своему решению, а не подстраивают решение под проблему.
dietbuddha
1

Подход ECS к составному наследованию:

An entity is a collection of components.
Systems process entities through their components.

Lion has claws and fangs as weapons.
Lion has meat as food.
Lion has a hunger for meat.
Lion has an affinity towards other lions.

Deer has antlers and hooves as weapons.
Deer has meat as food.
Deer has a hunger for plants.

Grass has plant as food.

псевдокод:

lion = new Entity("Lion")
lion.put(new Claws)
lion.put(new Fangs)
lion.put(new Meat)
lion.put(new MeatHunger)
lion.put(new Affinity("Lion"))

deer = new Entity("Deer")
deer.put(new Antlers)
deer.put(new Hooves)
deer.put(new PlantHunger)

grass = new Entity("Grass")
grass.put(new Plant)

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

Nature({lion, deer, grass})

Nature(entities)
{
    for each entity in entities:
    {
       if entity.has("MeatHunger"):
           attack_meat(entity, entities.with("Meat", exclude = entity))
       if entity.has("PlantHunger"):
           eat_plants(entity, entites.with("Plant", exclude = entity))
    }
}

Возможно, мы хотим расширить, Grassчтобы иметь потребность в солнечном свете и воде, и мы хотим ввести солнечный свет и воду в наш мир. И Grassвсе же не может искать их напрямую, так как не имеет mobility. Animalsможет также нуждаться в воде, но может активно искать это, так как у них есть mobility. Довольно легко продолжать расширять и изменять эту модель без каскадных поломок всего проекта, поскольку мы просто добавляем новые компоненты и расширяем поведение наших систем (или количества систем).


источник
0

Как решить эту проблему без использования множественного наследования и объектно-ориентированного проектирования?

Как и большинство вещей, это зависит .

Это зависит от того, что вы видите «этой проблемой».

  • Это общая проблема реализации , например, как «обойти» отсутствие множественного наследования в выбранной вами платформе?
  • Это проблема дизайна именно для этого конкретного случая , например, как смоделировать тот факт, что животные также являются пищей?
  • Это философской проблемой с моделью предметной области , например, являются ли «еда» и «животное» действительными, необходимыми и достаточными классификациями для предполагаемого практического применения?

Если вы спрашиваете об общей проблеме реализации, ответ будет зависеть от возможностей вашей среды. Интерфейсы IFood и IAnimal могут работать с подклассом EdibleAnimal, реализующим оба интерфейса. Если ваша среда не поддерживает интерфейсы, просто заставьте Animal наследовать от Food.

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

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

Стивен А. Лоу
источник
0

Наследование должно использоваться для чего-то, что всегда является чем-то другим, и не может измениться. Трава не всегда еда. Например, я не ем траву.

Трава играет роль корма для определенных животных.

Нил Макгиган
источник
Это просто абстракция. Если это требование, вы можете создать больше подразделений, которые расширяют абстрактный класс Plant и заставляют людей есть абстрактный класс, такой как «HumanEatablePlants», который бы группировал растения, которые люди едят, в конкретные классы.
hagensoft
0

Вы только что столкнулись с основным ограничением ОО.

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

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

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

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

Олень может быть домашним животным, домашним скотом, животным в зоопарке или охраняемым видом.

Лев также может быть животным в зоопарке или охраняемым видом.

Жизнь не проста.

Джеймс Андерсон
источник
0

Как решить эту проблему без использования множественного наследования и объектно-ориентированного проектирования?

Какая проблема? Что делает эта система?Пока вы не ответите, я понятия не имею, какие классы могут потребоваться. Вы пытаетесь смоделировать экологию с хищниками, травоядными и растениями, проецируя популяции видов в будущее? Вы пытаетесь заставить компьютер играть 20 вопросов?

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

Кевин Клайн
источник