Projektowanie obiektowe

23

Załóżmy, że masz:

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

Deerdziedziczy Animali Grassdziedziczy po Food.

Jak na razie dobrze. Animalprzedmioty mogą jeść Foodprzedmioty.

Teraz pomieszajmy to trochę. Pozwala dodać, Lionktóry dziedziczy Animal.

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

Teraz mamy problem, ponieważ Lionmożemy jeść zarówno Deeri Grass, ale Deertak nie Foodjest Animal.

Bez korzystania z wielokrotnego dziedziczenia i projektowania obiektowego, jak rozwiązać ten problem?

FYI: Użyłem http://www.asciiflow.com, aby stworzyć diagramy ASCII.

Michael Irey
źródło
14
Modelowanie realnego świata jest zwykle prędzej czy później problemem, ponieważ zawsze dzieje się coś dziwnego (np. Latająca ryba, ryba lub ptak? Ale pingwin to ptak, nie potrafi latać i zjada ryby). To, co mówi @Ampt, brzmi realistycznie: Zwierzę powinno mieć zbiór rzeczy, które je.
Rob van der Veer
2
Myślę, że Zwierzęta powinny odziedziczyć po Żywności. Jeśli coś próbuje zjeść Lwa, po prostu rzuć InvalidOperationException.
RalphChapin
4
@RalphChapin: Wszystkie rzeczy jedzą lwa (sępy, robaki itp.). Myślę, że zwierzęta i jedzenie to sztuczne rozróżnienia, które się rozpadną, ponieważ nie są wystarczająco szerokie (ostatecznie wszystkie zwierzęta są żywnością innych zwierząt). Jeśli sklasyfikowałeś „LivingThing”, musiałbyś zajmować się tylko skrzynkami z roślinami, które jedzą rzeczy nieożywione (minerały itp.), I nic by nie zepsuło, mając LivingThing.Eat (LivingThing).
Satanicpuppy
2
Udostępnianie badań pomaga wszystkim. Powiedz nam, co próbowałeś i dlaczego nie spełnia twoich potrzeb. To pokazuje, że poświęciłeś trochę czasu, aby spróbować sobie pomóc, oszczędza nam to powtarzania oczywistych odpowiedzi, a przede wszystkim pomaga uzyskać bardziej konkretną i odpowiednią odpowiedź. Zobacz także How to Ask
gnat
9
Na to pytanie odpowiedziała gra Age of Empire III. ageofempires.wikia.com/wiki/List_of_Animals Implementacja Deer and Gazelle IHuntable, Sheep and Cow są IHerdable(kontrolowane przez człowieka), a Lion implementuje tylko IAnimal, co nie implikuje żadnego z tych interfejsów. AOE3 obsługuje sprawdzanie zestawu interfejsów obsługiwanych przez konkretny obiekt (podobny do instanceof), który pozwala programowi sprawdzać jego możliwości.
rwong

Odpowiedzi:

38

Relacje IS A = Dziedziczenie

Lew jest zwierzęciem

MA Powiązania = Skład

Samochód ma koło

MOŻNA zrobić relacje = interfejsy

Mogę jeść

Robert Harvey
źródło
5
+1 To takie proste, a jednocześnie tak dobre podsumowanie 3 różnych typów relacji
dreza
4
Alternatywnie: ICanBeEatenlubIEdible
Mike Weller
2
Relacje CAN HAZ = Lolcats
Steven A. Lowe
1
Jak to odpowiada na pytanie?
user253751
13

OO to tylko metafora wzorująca się na prawdziwym świecie. Ale metafory idą tylko tak daleko.

Zwykle nie ma właściwego sposobu na modelowanie czegoś w OO. Jest właściwy sposób, aby to zrobić dla konkretnego problemu w określonej domenie i nie powinieneś oczekiwać, że zadziała dobrze, jeśli zmienisz swój problem, nawet jeśli obiekty domeny są takie same.

Myślę, że jest to częste nieporozumienia większości Comp. Inż. studenci mają w pierwszych latach. OO nie jest uniwersalnym rozwiązaniem, tylko porządnym narzędziem do rozwiązywania problemów, które mogą odpowiednio modelować domenę.

Nie odpowiedziałem na pytanie, właśnie dlatego, że brakuje nam informacji o domenie. Mając powyższe na uwadze, możesz być w stanie zaprojektować coś, co odpowiada Twoim potrzebom.

DPM
źródło
3
+1 OO to narzędzie, a nie religia.
mouviciel
Zgadzam się, że może nie być idealnym rozwiązaniem, jeśli problem będzie się ciągle zmieniać i ewoluować. Czy w obecnym stanie tego problemu brakuje informacji o domenie, aby znaleźć rozwiązanie?
Michael Irey,
Czy naprawdę myślisz, że prawdziwy świat jest modelowany w OP? Model relacji jest reprezentowany przez przykład.
Basilevs,
@Basilevs To właściwie implikacja, ponieważ wspomina, jak zwierzęta zachowują się w prawdziwym życiu. Należy zastanowić się, dlaczego takie zachowanie należy uwzględnić w programie, IMO. To powiedziawszy, miło byłoby z mojej strony zaproponować jakiś możliwy projekt.
DPM
10

Chcesz dalej dzielić zwierzęta na ich podklasy (lub przynajmniej o ile ma to sens w tym, co robisz). Biorąc pod uwagę, że pracujesz z czymś, co wygląda jak podstawowe zwierzęta i dwa rodzaje pożywienia (rośliny i mięso), sensowne jest użycie zwierząt mięsożernych i roślinożernych do dalszego zdefiniowania zwierzęcia i utrzymania go w oddzielności. Oto, co dla ciebie przygotowałem.

             +----------------+                   +--------------------+
             |    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 -------+

Jak widać, oboje ujawniają metodę jedzenia, ale to, co jedzą, zmienia się. Lew może teraz zabijać jelenia, jeleń może umrzeć i zwrócić DeerMeat, a pierwotne pytanie PO, jak pozwolić lwie jeść jelenia, ale bez trawy jest odpowiedzią bez opracowania całego ekosystemu.

Oczywiście staje się to interesujące bardzo szybko, ponieważ Jeleń można również uznać za rodzaj mięsa, ale dla uproszczenia stworzyłbym metodę zwaną kill () pod jeleniem, która zwraca mięso jelenia, i umieściłem to jako klasa betonu rozciągającego mięso.

Ampt
źródło
Czy Deer wystawiłby interfejs IMeat?
Dan Pichelman
Mięso nie jest interfejsem, to klasa abstrakcyjna. Dodałem, jak bym to dla ciebie
wdrożył
Eat(Plant p)i Eat(Meat m)oba naruszają LSP.
Tulains Córdova
Jak to @ user61852? Celowo nie ujawniłem opcji Jedz w interfejsie zwierząt, aby każdy rodzaj zwierzęcia mógł mieć własną metodę jedzenia.
Ampt
1
TCWL (zbyt skomplikowane, wycieknie). Problem jest rozpowszechniony i pojawia się, a twoje rozwiązanie jest statyczne, scentralizowane i wstępnie zdefiniowane. TCWL.
Tulains Córdova
7

Mój projekt wyglądałby tak:

  1. Żywność deklaruje się jako interfejsy; istnieje interfejs IFood i dwa pochodne interfejsy: IMeat i IVegetable
  2. Zwierzęta wdrażają IMeat, a warzywa wdrażają IVegetable
  3. Zwierzęta mają dwoje potomków, mięsożerców i zwierząt rasy heivivores
  4. Zwierzęta mięsożerne mają metodę Eat, która otrzymuje instancję IMeat
  5. Zwierzęta roślinożerne mają metodę Eat, która otrzymuje instancję IVegetable
  6. Lew schodzi z Carnivore
  7. Jeleń pochodzi od Herbivore
  8. Trawa pochodzi od warzyw

Ponieważ Zwierzęta wdrażają IMeat, a Jeleń jest Zwierzęciem (Roślinożernym), Lewem, który jest Zwierzęciem (Carnivore), który może jeść IMeat może również jeść Jelenia.

Jeleń jest roślinożercą, więc może jeść trawę, ponieważ wprowadza IVegetable.

Zwierzęta mięsożerne nie mogą jeść IVegeable, a zwierzęta roślinożerne nie mogą jeść IMeat.

AlexSC
źródło
1
Widzę tutaj wiele rodzajów wyliczeń, które używają dziedziczenia, aby ograniczyć, gdy dziedziczone typy nie implementują niczego ... Za każdym razem, gdy tworzysz typy, które w ogóle nie implementują żadnej funkcji, jest to gratka, że ​​coś jest stopą; rozwinąłeś model w systemie tekstowym, który nie daje żadnej wartości użyteczności w kodzie
Jimmy Hoffa
Pamiętajcie, że istnieją wszystkożerne istoty ludzkie, małpy i niedźwiedzie.
Tulains Córdova
Jak zatem dodać, że zarówno lwy, jak i jelenie są ssakami? :-)
johannes
2
@JimmyHoffa Są to tak zwane „interfejsy znaczników” i są całkowicie poprawnym użyciem interfejsu. Należy zweryfikować kod, aby zdecydować, czy użycie jest uzasadnione, ale istnieje wiele przypadków użycia (takich jak ten, w którym Lew próbujący zjeść trawę zgłosiłby wyjątek NoInterface). Interfejs znaczników (lub jego brak) służy do przepowiedzenia wyjątku, który zostanie zgłoszony, jeśli metoda zostanie wywołana z nieobsługiwanymi argumentami.
rwong
1
@rwong Rozumiem tę koncepcję, nigdy wcześniej nie słyszałem o jej sformalizowaniu; tylko moje doświadczenie zawsze było podstawą kodu, w którym pracuję, dzięki czemu wszystko staje się bardziej złożone i trudniejsze w utrzymaniu. Być może moje doświadczenie dotyczyło jednak sytuacji, w których ludzie źle je wykorzystali.
Jimmy Hoffa,
5

To, co zwierzę może zjeść, w rzeczywistości nie tworzy hierarchii, w tym przypadku natura niewybaczalnie nie dostosowała się do prostego modelowania obiektowego (zauważ, że nawet gdyby tak było, zwierzę musiałoby odziedziczyć po jedzeniu, ponieważ jest pokarmem).

Wiedza o tym, jakie pokarmy może jeść zwierzę, nie może żyć całkowicie z żadną z tych klas, więc samo odniesienie do jakiegoś członka hierarchii żywieniowej nie wystarczy, aby powiedzieć ci, co możesz jeść.

To relacja wiele do wielu. Oznacza to, że za każdym razem, gdy dodajesz zwierzę, musisz dowiedzieć się, co ono może zjeść, i za każdym razem, gdy dodajesz jedzenie, musisz dowiedzieć się, co może je zjeść. To, czy będzie można wykorzystać dalszą strukturę, zależy od tego, jakie zwierzęta i pokarm modelujesz.

Wielokrotne dziedziczenie też tak naprawdę nie rozwiązuje zbyt dobrze. Potrzebujesz jakiejś kolekcji rzeczy, które zwierzę może jeść, lub zwierząt, które mogą jeść.

psr
źródło
Jak mówią o wyrażeniu regularnym „Miałem problem, więc użyłem wyrażenia regularnego, teraz mam dwa problemy”, MI odpowiada: „Miałem problem, więc użyłem MI, teraz mam 99 problemów” Gdybym był tobą, ja ” Jeśli podążasz za próżnością, którą tu nabijasz, choć wiesz o tym, co można zjeść, to w rzeczywistości upraszcza model o tonę. Odwrócenie zależności FTW.
Jimmy Hoffa
1

Podejdę do problemu z innej strony: OOP dotyczy zachowania. Czy w twoim przypadku zachowuje Grasssię jakieś dziecko Food? Więc w twoim przypadku nie będzie Grassklasy, a przynajmniej nie zostanie ona odziedziczona Food. Ponadto, jeśli musisz wymusić, kto może jeść, co w czasie kompilacji, wątpliwe jest, czy potrzebujesz Animalabstrakcji. Nie jest też rzadkością, że mięsożercy jedzą trawę , choć nie pożywienia.

Zaprojektowałbym to jako (nie zawracając sobie głowy sztuką ASCI):

IEdiblez właściwością Type, która jest wyliczeniem mięsa, roślin, tuszy itp. (nie zmienia się to często i nie ma żadnego konkretnego zachowania, dlatego nie ma potrzeby modelowania tego jako hierarchii klas).

Animalmetodami CanEat(IEdible food)i Eat(IEdible food), które są logiczne. Następnie określone zwierzęta mogą sprawdzać, ilekroć mogą w danym przypadku jeść określone pokarmy, a następnie je jeść, aby uzyskać pożywienie / zrobić coś innego. Ponadto modelowałbym klasy Carnivore, Herbivore, Omnivore jako wzór strategii , niż jako część hierarchii zwierząt.

Euforyk
źródło
1

TL; DR: Projekt lub model z kontekstem.

Myślę, że twoje pytanie jest trudne, ponieważ brakuje kontekstu rzeczywistego problemu, który próbujesz rozwiązać. Masz pewne modele i pewne relacje, ale brakuje ci ram, w których musi działać. Bez kontekstu modelowanie i metafory nie działają dobrze, pozostawiając drzwi otwarte na wiele interpretacji.

Myślę, że bardziej produktywne jest skupienie się na sposobie wykorzystania danych. Gdy masz już wzorzec wykorzystania danych, łatwiej jest pracować wstecz do tego, jakie powinny być modele i relacje.

Na przykład bardziej szczegółowe wymagania będą wymagać różnych relacji między obiektami:

  • wspierać Animals eating nieprzestrzegania FoododczuwalnaGastroliths
  • wsparcie Chocolatejak Poisondla Dogs, ale nie dlaHumans

Jeśli zaczniemy od ćwiczenia modelowania prostej przedstawionej relacji, interfejs żywnościowy może być najlepszy; a jeśli jest to suma, w jaki sposób relacje w systemie, to twoja kara. Jednak tylko kilka dodatkowych wymagań lub relacji może znacznie wpłynąć na modele i relacje, które działały w prostszym przypadku.

dietbuddha
źródło
Zgadzam się, ale to tylko mały przykład i nie próbowaliśmy modelować świata. Na przykład możesz mieć rekina, który zjada opony i tablice rejestracyjne. Możesz po prostu stworzyć rodzicielską klasę abstrakcyjną za pomocą metody, która zjada dowolny obiekt, a Food może rozszerzyć tę klasę abstact.
hagensoft
@hagensoft: Zgoda. Czasami daję się ponieść emocjom, ponieważ ciągle widzę programistów modelujących w oparciu o metaforę, z której natychmiast skorzystali, zamiast patrzeć na to, jak dane muszą być konsumowane i wykorzystywane. Pobierają się z projektem OO opartym na wstępnym pomyśle, a następnie próbują zmusić problem do dopasowania swojego rozwiązania zamiast dopasowywania rozwiązania do problemu.
dietbuddha
1

Podejście ECS polegające na nadpisywaniu składu:

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.

Pseudo kod:

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)

Natureto systempętla przechodząca przez te jednostki, szukająca składników, które mają dzięki uogólnionej funkcji zapytania. Naturespowoduje, że istoty z głodem mięsa zaatakują inne istoty, które mają mięso jako żywność za pomocą swojej broni, chyba że mają do tego bytu powinowactwo. Jeśli atak się powiedzie, istota żywi się swoją ofiarą, po czym ofiara zamieni się w zwłoki pozbawione mięsa. Naturespowoduje, że istoty z głodem roślin będą się odżywiać istotami, które mają rośliny jako żywność, pod warunkiem, że istnieją.

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))
    }
}

Być może chcemy rozszerzyć Grasssię na potrzebę światła słonecznego i wody i chcemy wprowadzić światło słoneczne i wodę do naszego świata. Nie Grassmogą jednak szukać ich bezpośrednio, ponieważ tak nie jest mobility. Animalsmogą również potrzebować wody, ale mogą ją aktywnie szukać, ponieważ mają mobility. Rozszerzanie i zmienianie tego modelu jest dość łatwe bez kaskadowego niszczenia całego projektu, ponieważ dodajemy nowe komponenty i rozszerzamy zachowanie naszych systemów (lub liczby systemów).


źródło
0

Bez korzystania z wielokrotnego dziedziczenia i projektowania obiektowego, jak rozwiązać ten problem?

Jak większość rzeczy, to zależy .

To zależy od tego , jak widzisz „ten problem”.

  • Czy to ogólny problem z implementacją , np. Jak „obejść” brak wielokrotnego dziedziczenia na wybranej platformie?
  • Czy jest to problem projektowy tylko w tym konkretnym przypadku , np. Jak modelować fakt, że zwierzęta są również pokarmem?
  • Czy jest to filozoficzny problem związany z modelem domenowym , np. Czy „żywność” i „zwierzę” są ważne, konieczne i wystarczające do przewidzianego praktycznego zastosowania?

Jeśli pytasz o ogólny problem z implementacją, odpowiedź będzie zależeć od możliwości twojego środowiska. Interfejsy IFood i IAnimal mogą działać, z podklasą EdibleAnimal implementującą oba interfejsy. Jeśli twoje środowisko nie obsługuje interfejsów, po prostu spraw, aby Animal dziedziczyło z żywności.

Jeśli pytasz o ten konkretny problem projektowy, po prostu spraw, że Animal odziedziczy po żywności. To najprostsza rzecz, która może działać.

Jeśli pytasz o te koncepcje projektowe, odpowiedź silnie zależy od tego, co zamierzasz zrobić z modelem. Jeśli jest to gra wideo z psem-jedzącym-psem, a nawet aplikacja do śledzenia harmonogramów karmienia w zoo, może wystarczyć. Jeśli chodzi o koncepcyjny model wzorców zachowań zwierząt, to prawdopodobnie jest trochę płytki.

Steven A. Lowe
źródło
0

Dziedziczenia należy używać do czegoś, co zawsze jest czymś innym i nie może się zmienić. Trawa nie zawsze jest pożywieniem. Na przykład nie jem trawy.

Trawa odgrywa rolę pokarmu dla niektórych zwierząt.

Neil McGuigan
źródło
To tylko abstrakcja. Jeśli jest to wymóg, możesz stworzyć więcej podziałów, które rozszerzają klasę abstrakcyjną Roślin i zmuszają ludzi do jedzenia klasy abstrakcyjnej, takiej jak „HumanEatablePlants”, która grupowałaby rośliny, które ludzie jedzą, w konkretne klasy.
hagensoft
0

Właśnie natrafiłeś na podstawowe ograniczenie OO.

OO działa dobrze z hierarchicznymi strukturami. Ale kiedy odejdziesz od ścisłych hierarchii, abstrakcja nie działa tak dobrze.

Wiem wszystko o kompozycjach metamorfozy itp., Które są używane do ominięcia tych ograniczeń, ale są niezdarne i, co ważniejsze, prowadzą do niejasnego i trudnego do naśladowania kodu.

Relacyjne bazy danych zostały wymyślone przede wszystkim w celu uniknięcia ograniczeń ścisłych struktur hierarchicznych.

Na przykład trawa może być również materiałem budowlanym, surowcem do papieru, materiałem na odzież, chwastem lub rośliną uprawną.

Jeleń może być zwierzęciem, żywym inwentarzem, zwierzęciem z zoo lub gatunkiem chronionym.

Lew może być także zwierzęciem z zoo lub gatunkiem chronionym.

Życie nie jest proste.

James Anderson
źródło
0

Bez korzystania z wielokrotnego dziedziczenia i projektowania obiektowego, jak rozwiązać ten problem?

Jaki problem? Co robi ten system? Dopóki nie odpowiesz na to pytanie, nie mam pojęcia, jakie klasy mogą być wymagane. Czy próbujesz modelować ekologię za pomocą mięsożerców, roślinożerców i roślin, projektując populacje gatunków w przyszłość? Czy chcesz, aby komputer grał 20 pytań?

Rozpoczynanie projektowania jest stratą czasu, zanim zdefiniowane zostaną przypadki użycia. Widziałem to doprowadzone do absurdalnej skrajności, gdy około dziesięcioosobowy zespół zaczął produkować model OO linii lotniczej za pomocą oprogramowania poprzez zdjęcia. Pracowali przez dwa lata modelując, nie biorąc pod uwagę faktycznych problemów biznesowych. Wreszcie klient zmęczył się czekaniem i poprosił zespół o rozwiązanie rzeczywistego problemu. Całe to modelowanie było całkowicie bezużyteczne.

Kevin Cline
źródło