Design orientato agli oggetti


23

Supponiamo di avere quanto segue:

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

Deereredita da Animaled Grasseredita da Food.

Fin qui tutto bene. Animalgli oggetti possono mangiare Foodoggetti.

Ora lasciamo un po 'di confusione. Consente di aggiungere un Lionche eredita da Animal.

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

Ora abbiamo un problema perché Lionpossiamo mangiare entrambi Deere Grass, ma Deernon Foodlo è Animal.

Senza utilizzare l'ereditarietà multipla e la progettazione orientata agli oggetti, come si risolve questo problema?

Cordiali saluti: Ho usato http://www.asciiflow.com per creare i diagrammi ASCII.


14
Modellare il mondo reale di solito è un problema prima o poi, perché c'è sempre qualcosa di strano (come un pesce volante, un pesce o un uccello? Ma un pinguino è un uccello, non può volare e mangia pesce). Quello che @Ampt dice sembra plausibile, un animale dovrebbe avere una raccolta di cose che mangia.
Rob van der Veer,

2
Penso che gli animali dovrebbero ereditare dal cibo. Se qualcosa cerca di mangiare un leone, basta lanciare un InvalidOperationException.
RalphChapin,

4
@RalphChapin: tutti i tipi di cose mangiano leone (avvoltoi, insetti, ecc.). Penso che gli animali e il cibo siano distinzioni artificiali che si rompono perché non sono abbastanza larghe (tutti gli animali sono cibo per altri animali, alla fine). Se ti classificassi su "LivingThing", dovrai solo occuparti dei casi limite con piante che mangiano cose non viventi (minerali, ecc.) E non si spezzerebbe nulla per avere LivingThing.Eat (LivingThing).
Satanicpuppy,

2
Condividere la tua ricerca aiuta tutti. Raccontaci cosa hai provato e perché non ha soddisfatto le tue esigenze. Ciò dimostra che hai impiegato del tempo per cercare di aiutarti, ci salva dal ribadire risposte ovvie e soprattutto ti aiuta a ottenere una risposta più specifica e pertinente. Vedi anche Come chiedere
moscerino

9
A questa domanda ha risposto il gioco Age of Empire III. ageofempires.wikia.com/wiki/List_of_Animals I cervi e la gazzella implementano IHuntable, Sheep and Cow sono IHerdable(controllabili dall'uomo) e Lion implementa solo IAnimal, che non implica nessuna di quelle interfacce. AOE3 supporta l'interrogazione dell'insieme di interfacce supportate da un oggetto particolare (simile a instanceof) che consente a un programma di interrogare le sue capacità.
rwong,

Risposte:


38

Relazioni IS A = Ereditarietà

Il leone è un animale

Ha relazioni A = Composizione

L'auto ha una ruota

PU DO FARE relazioni = Interfacce

Posso mangiare


5
+1 Questo è così semplice e tuttavia un buon riassunto dei 3 diversi tipi di relazione
dreza,

4
Alternativa: ICanBeEatenoppureIEdible
Mike Weller,

2
Relazioni CAN HAZ = lolcats
Steven A. Lowe

1
Come risponde alla domanda?
user253751

13

OO è solo una metafora che si modella sul mondo reale. Ma le metafore vanno solo così lontano.

Normalmente non esiste un modo giusto per modellare qualcosa in OO. C'è un modo giusto per farlo per un problema particolare in un determinato dominio e non dovresti aspettarti che funzioni bene se cambi il problema, anche se gli oggetti del dominio sono gli stessi.

Penso che questo sia un malinteso comune più Comp. Ing. gli studenti hanno nei loro primi anni. OO non è una soluzione universale, solo uno strumento decente per alcuni tipi di problemi che possono modellare il tuo dominio ragionevolmente bene.

Non ho risposto alla domanda, proprio perché ci mancano le informazioni sul dominio. Ma tenendo presente quanto sopra potresti essere in grado di progettare qualcosa che si adatta alle tue esigenze.


3
+1 OO è uno strumento, non una religione.
mouviciel,

Sono d'accordo, potrebbe non essere la soluzione perfetta se questo problema continua a cambiare ed evolversi. Allo stato attuale, questo problema non ha informazioni sul dominio per trovare una soluzione?
Michael Irey,

Pensi seriamente che un mondo reale sia modellato in OP? Un modello di relazione è rappresentato tramite un esempio.
Basilevs,

@Basilevs Questo è il tipo di implicazione, in realtà, dal momento che menziona come gli animali si comportano nella vita reale. Bisogna preoccuparsi del perché si debba tenere conto di quel comportamento nel programma, IMO. Detto questo, sarebbe stato carino da parte mia suggerire qualche possibile progetto.
DPM

10

Vuoi scomporre ulteriormente gli animali nelle loro sottoclassi (o almeno per quanto ha senso per quello che stai facendo). Dato che stai lavorando con quelli che sembrano animali di base e due tipi di cibo (piante e carne), ha senso usare carnivori ed erbivori per definire ulteriormente un animale e tenerli separati. Ecco cosa ho elaborato per te.

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

Come puoi vedere, entrambi espongono un metodo alimentare, ma ciò che mangiano cambia. Il leone può ora uccidere un cervo, il cervo può morire e restituire DeerMeat, e la domanda originale di OP su come consentire a un leone di mangiare un cervo ma non si risponde all'erba senza progettare un intero ecosistema.

Naturalmente, questo diventa interessante molto rapidamente perché un cervo potrebbe essere considerato anche un tipo di carne, ma per mantenere le cose semplici, creerei un metodo chiamato kill () sotto i cervi, che restituisce una carne di cervo, e lo metto come classe concreta che estende la carne.


Deer avrebbe quindi esposto un'interfaccia IMeat?
Dan Pichelman,

La carne non è un'interfaccia, è una classe astratta. Ho aggiunto come lo implementerei per te
Ampt

Eat(Plant p)ed Eat(Meat m)entrambi violano LSP.
Tulains Córdova,

In che modo @ user61852? Di proposito non ho esposto Eat in the animal interface in modo che ogni tipo di animale potesse avere il suo metodo di mangiare.
Appunto

1
TCWL (Troppo complesso, perderà). Il problema è distribuito ed emergente e la soluzione è statica, centralizzata e predefinita. TCWL.
Tulains Córdova,

7

Il mio design sarebbe così:

  1. Gli alimenti sono dichiarati come interfacce; c'è un'interfaccia IFood e due interfacce derivate da essa: IMeat e IVegetable
  2. Gli animali implementano IMeat e gli ortaggi implementano IVegetable
  3. Gli animali hanno due discendenti, carnivori ed ebivori
  4. I carnivori hanno il metodo Eat che riceve un'istanza di IMeat
  5. Gli erbivori hanno il metodo Eat che riceve un'istanza di IVegetable
  6. Il leone discende dal carnivoro
  7. Il cervo discende da Erbivoro
  8. L'erba scende dalla verdura

Poiché gli animali implementano IMeat and Deer è un (Herbivore) Animal, Lion, che è un (Carnivore) Animal che può mangiare IMeat può anche mangiare Deer.

Il cervo è un erbivoro, quindi può mangiare erba perché implementa IVegetable.

I carnivori non possono mangiare IVegeable e gli erbivori non possono mangiare IMeat.


1
Sto vedendo molti tipi di enumerazione qui che usano l'ereditarietà solo per vincolare quando i tipi ereditati non implementano nulla ... Ogni volta che ti ritrovi a creare tipi che non implementano alcuna funzionalità, è un dono che qualcosa è un piede; hai ampliato un modello nel sistema di tipi che non fornisce alcun valore all'usabilità nel codice
Jimmy Hoffa,

Ricorda che esistono onnivori, come umani, scimmie e orsi.
Tulains Córdova,

Quindi, come si aggiunge che sia i leoni che i cervi sono mammiferi? :-)
johannes,

2
@JimmyHoffa Quelli sono chiamati "interfacce marker" e sono un uso totalmente valido dell'interfaccia. Deve essere sottoposto a revisione del codice per decidere se l'uso è giustificato, ma ci sono molti casi d'uso (come questo, in cui un Lion che prova a mangiare erba lancerebbe un'eccezione NoInterface). L'interfaccia marker (o la mancanza di) serve a prevedere un'eccezione che verrà generata se viene chiamato un metodo con argomenti non supportati.
rwong

1
@rwong Capisco il concetto, non l'ho mai sentito formalizzato prima; solo la mia esperienza è stata ogni volta che una base di codice in cui ho lavorato li rende più complessi e difficili da mantenere. Forse la mia esperienza è stata solo quella in cui le persone le hanno usate male.
Jimmy Hoffa,

5

Quali alimenti un animale può effettivamente non formare una gerarchia, in questo caso la natura non è riuscita a conformarsi ingiustificatamente alla semplice modellizzazione orientata agli oggetti (si noti che anche se lo facesse, l'animale dovrebbe ereditare dal cibo, dal momento che è cibo).

La conoscenza di quali alimenti un animale può mangiare non può vivere interamente con nessuna delle classi, quindi semplicemente avere un riferimento ad un membro della gerarchia alimentare non può essere sufficiente per dirti quali cose puoi mangiare.

È una relazione da molte a molte. Ciò significa che ogni volta che aggiungi un animale, devi capire cosa può mangiare e ogni volta che aggiungi un cibo, devi capire cosa può mangiarlo. La presenza di ulteriori strutture da sfruttare dipende dagli animali e dagli alimenti che stai modellando.

L'ereditarietà multipla non risolve davvero molto bene neanche questo. Hai bisogno di una sorta di raccolta di cose che un animale può mangiare o di animali che possono mangiare un cibo.


Come si dice su regex "Ho avuto un problema, quindi ho usato regex, ora ho due problemi", l'MI è sulla falsariga di "Ho avuto un problema, quindi ho usato l'MI, ora ho 99 problemi" Se fossi in te io ' d seguire quel vano che stavi frugando qui anche se del cibo sapendo cosa può mangiarlo, questo in realtà semplifica un sacco il modello. FTW di inversione di dipendenza.
Jimmy Hoffa,

1

Affronterò il problema da un'altra parte: OOP riguarda il comportamento. Nel tuo caso, Grassha qualche comportamento di cui essere figlio Food? Quindi, nel tuo caso, non ci sarà Grassclasse, o almeno, non sarà ereditato Food. Inoltre, se devi imporre a chi può mangiare cosa in fase di compilazione, è discutibile se hai bisogno di Animalastrazione. Inoltre, non è raro vedere i carnivori che mangiano erba , anche se non per il sostentamento.

Quindi progetterei questo come (non mi preoccuperò con l'arte ASCI):

IEdiblecon proprietà Type, che è enum di carne, piante, carcasse, ecc. (questo non cambia spesso e non ha alcun comportamento specifico, quindi non è necessario modellarlo come classe hiearchy).

Animalcon metodi CanEat(IEdible food)e Eat(IEdible food), che sono logici. Quindi, specifici animali possono controllare ogni volta che possono mangiare cibo dato in determinate circostanze e quindi mangiare quel cibo per ottenere sostentamento / fare qualcos'altro. Inoltre, modellerei le classi Carnivore, Herbivore, Omnivore come modello di strategia , piuttosto che come parte della gerarchia animale.


1

TL; DR: progettazione o modello con un contesto.

Penso che la tua domanda sia difficile perché manca il contesto del problema reale che stai cercando di risolvere. Hai alcuni modelli e alcune relazioni, ma manca il quadro in cui deve funzionare. Senza contesto, la modellazione e le metafore non funzionano bene lasciando la porta aperta a più interpretazioni.

Penso che sia più produttivo concentrarsi su come verranno consumati i dati. Una volta che hai il modello di utilizzo dei dati, è più facile tornare indietro a ciò che dovrebbero essere i modelli e le relazioni.

Ad esempio requisiti più dettagliati richiederanno relazioni oggetto diverse:

  • sostenere Animals eatil non- FoodlikeGastroliths
  • sostenere Chocolatecome Poisonper Dogs, ma non perHumans

Se iniziamo con l'esercizio di come modellare la semplice relazione presentata, l'interfaccia alimentare potrebbe essere la migliore; e se questa è la somma totale delle relazioni nel sistema, allora va bene. Tuttavia, solo alcuni requisiti o relazioni aggiuntivi possono influenzare notevolmente i modelli e le relazioni che hanno funzionato nel caso più semplice.


Sono d'accordo, ma è solo un piccolo esempio e non stavo cercando di modellare il mondo. Ad esempio puoi avere uno squalo che mangia pneumatici e targhe. Puoi semplicemente creare una classe astratta genitore con un metodo che mangia qualsiasi tipo di oggetto e Food potrebbe estendere questa classe abstact.
Hensensoft

@hagensoft: concordato. A volte vengo portato via perché vedo costantemente gli sviluppatori modellare sulla base di una metafora su cui hanno immediatamente catturato, piuttosto che guardare come i dati devono essere consumati e utilizzati. Si sposano con un progetto OO basato su un'idea iniziale e quindi provano a forzare il problema ad adattarsi alla loro soluzione invece di adattare la loro soluzione al problema.
dietbuddha,

1

Approccio basato sulla composizione per ereditarietà:

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.

pseudocodice:

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è un systemciclo che scorre attraverso queste entità, cercando quali componenti hanno attraverso una funzione di query generalizzata. Naturefarà sì che entità affamate di carne attaccino altre entità che hanno carne come cibo usando le loro armi, a meno che non abbiano un'affinità con quell'entità. Se l'attacco ha successo, l'entità si nutrirà della sua vittima, a quel punto la vittima si trasformerà in un cadavere privo di carne. Naturefarà sì che le entità affamate di piante si nutrano di entità che hanno piante come cibo, a condizione che esistano.

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

Forse vogliamo estenderci Grassper avere bisogno della luce solare e dell'acqua e vogliamo introdurre la luce solare e l'acqua nel nostro mondo. Tuttavia, Grassnon è possibile cercarli direttamente, poiché non è cosìmobility . Animalspuò anche aver bisogno di acqua, ma può cercarla attivamente poiché hanno mobility. È abbastanza facile continuare a estendere e modificare questo modello senza interrompere a cascata l'intero progetto, poiché aggiungiamo solo nuovi componenti ed estendiamo il comportamento dei nostri sistemi (o il numero di sistemi).


0

Senza utilizzare l'ereditarietà multipla e la progettazione orientata agli oggetti, come si risolve questo problema?

Come la maggior parte delle cose, dipende .

Dipende da cosa che vedi "questo problema".

  • E 'un problema di implementazione generale , ad esempio come "aggirare" l'assenza di ereditarietà multipla nella piattaforma prescelta?
  • È un problema di progettazione solo per questo caso specifico , ad esempio come modellare il fatto che anche gli animali sono cibo?
  • E 'un problema filosofico con il modello di dominio , ad esempio "classificazioni alimentari" e "animali" valide, necessarie e sufficienti per l'applicazione pratica prevista?

Se stai chiedendo del problema generale di implementazione, la risposta dipenderà dalle capacità del tuo ambiente. Le interfacce IFood e IAnimal potrebbero funzionare, con una sottoclasse EdibleAnimal che implementa entrambe le interfacce. Se il tuo ambiente non supporta le interfacce, crea l'eredità degli animali dagli alimenti.

Se stai chiedendo di questo specifico problema di progettazione, fai semplicemente ereditare gli animali dal cibo. È la cosa più semplice che potrebbe funzionare.

Se stai chiedendo di questi concetti di design, la risposta dipende fortemente da cosa intendi fare con il modello. Se è per un videogioco cane-mangia-cane o anche un'applicazione per tenere traccia dei programmi di alimentazione in uno zoo, potrebbe essere sufficiente per funzionare. Se è per un modello concettuale per i modelli comportamentali degli animali, è probabilmente un po 'superficiale.


0

L'ereditarietà dovrebbe essere usata per qualcosa che è sempre qualcos'altro e che non può cambiare. L'erba non è sempre cibo. Ad esempio, non mangio erba.

L'erba svolge il ruolo di alimento per alcuni animali.


È solo un'astrazione. Se questo è un requisito, allora potresti creare più divisioni che estendano la classe astratta di Plant e inducano gli umani a mangiare una classe astratta come "HumanEatablePlants" che raggrupperebbe le piante che gli umani mangiano in classi concrete.
Hagensoft,

0

Hai appena incontrato il limite di base di OO.

OO funziona bene con strutture gerarchiche. Ma una volta che ti allontani da rigide gerarchie, l'astrazione non funziona così bene.

Conosco tutto sulle composizioni di metamorfosi, ecc. Che vengono utilizzate per aggirare queste limitazioni ma sono goffe e, soprattutto, portano a codici oscuri e difficili da seguire.

Le basi dati relazionali sono state inventate principalmente per allontanarsi dai limiti delle rigide strutture gerarchiche.

Per fare un esempio, l'erba potrebbe anche essere un materiale da costruzione, una materia prima per la carta, un materiale per l'abbigliamento, un'erbaccia o un raccolto.

Un cervo potrebbe essere un animale domestico, un bestiame, un animale da zoo o una specie protetta.

Un leone potrebbe anche essere un animale da zoo o una specie protetta.

La vita non è semplice


0

Senza utilizzare l'ereditarietà multipla e la progettazione orientata agli oggetti, come si risolve questo problema?

Che problema? Cosa fa questo sistema? Fino a quando non risponderai, non ho idea di quali classi possano essere richieste. Stai cercando di modellare un'ecologia, con carnivori, erbivori e piante, proiettando popolazioni di specie nel futuro? Stai cercando di far giocare 20 domande al computer?

Iniziare la progettazione è una perdita di tempo prima che siano definiti tutti i casi d'uso. L'ho visto portato a livelli ridicoli quando una squadra di circa dieci ha iniziato a produrre un modello OO di una compagnia aerea usando il software attraverso le immagini. Hanno lavorato per due anni a modellare senza alcun problema aziendale in mente. Alla fine il cliente si stancò di aspettare e chiese al team di risolvere un problema reale. Tutta quella modellistica era completamente inutile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.