Design Orientado a Objetos

23

Suponha que você tenha o seguinte:

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

Deerherda de Animale Grassherda de Food.

Por enquanto, tudo bem. Animalobjetos podem comer Foodobjetos.

Agora vamos misturar um pouco. Vamos adicionar um Lionque herda de Animal.

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

Agora temos um problema porque Lionpodemos comer os dois Deere Grass, mas Deernão Foodé Animal.

Sem o uso de herança múltipla e o design orientado a objetos, como você resolve esse problema?

FYI: Usei http://www.asciiflow.com para criar os diagramas ASCII.

Michael Irey
fonte
14
Modelar o mundo real geralmente é um problema, mais cedo ou mais tarde, porque sempre há algo estranho acontecendo (como peixe voador, peixe ou pássaro - mas um pinguim é um pássaro, não pode voar e come peixe). O que o @Ampt diz parece plausível, um Animal deve ter uma coleção de coisas que come.
Rob van der Veer
2
Eu acho que o Animal deveria herdar da Comida. Se algo tentar comer um leão, basta lançar uma InvalidOperationException.
RalphChapin
4
@RalphChapin: Todos os tipos de coisas comem leão (abutres, insetos, etc.). Eu acho que animal e comida são distinções artificiais que se quebram porque não são amplas o suficiente (todos os animais são comida de outros animais, eventualmente). Se você classificasse o "LivingThing", teria que lidar apenas com os casos extremos com plantas que comem coisas não-vivas (minerais, etc.), e não quebraria nada ter o LivingThing.Eat (LivingThing).
23613 Satanicpuppy
2
Compartilhar sua pesquisa ajuda a todos. Conte-nos o que você tentou e por que ele não atendeu às suas necessidades. Isso demonstra que você reservou um tempo para tentar ajudar a si mesmo, evita reiterar respostas óbvias e, acima de tudo, ajuda a obter uma resposta mais específica e relevante. Veja também How to Ask
gnat
9
Esta pergunta foi respondida pelo jogo Age of Empire III. ageofempires.wikia.com/wiki/List_of_Animals O Deer and Gazelle implementa IHuntable, Sheep e Cow são IHerdable(controláveis ​​por humanos), e o Lion implementa apenas o IAnimal, o que não implica em nenhuma dessas interfaces. O AOE3 suporta a consulta do conjunto de interfaces suportadas por um objeto específico (semelhante a instanceof) que permite que um programa consulte suas capacidades.
rwong

Respostas:

38

Relações IS A = Herança

Leão é um animal

TEM UM relacionamento = Composição

Carro tem uma roda

PODE FAZER relacionamentos = Interfaces

Eu posso comer

Robert Harvey
fonte
5
+1 Isso é tão simples e ainda tal sumário boa dos 3 tipos de relacionamento diferentes
dreza
4
Alternativa: ICanBeEatenouIEdible
Mike Weller
2
Relacionamentos com CAN HAZ = lolcats
Steven A. Lowe
1
Como isso responde à pergunta?
user253751
13

OO é apenas uma metáfora que se molda ao mundo real. Mas as metáforas só vão tão longe.

Normalmente, não há maneira correta de modelar algo no OO. Existe uma maneira correta de fazer isso para um problema específico em um domínio específico e você não deve esperar que funcione bem se alterar o problema, mesmo que os objetos do domínio sejam os mesmos.

Eu acho que este é um equívoco comum mais Comp. Eng. os alunos têm em seus primeiros anos. OO não é uma solução universal, apenas uma ferramenta decente para algum tipo de problema que pode modelar seu domínio razoavelmente bem.

Não respondi à pergunta, precisamente porque nos falta informações sobre o domínio. Mas com o exposto acima, você poderá criar algo que atenda às suas necessidades.

DPM
fonte
3
+1 OO é uma ferramenta, não uma religião.
Mouviciel 19/07/2013
Concordo, pode não haver solução perfeita se esse problema continuar a mudar e evoluir. No estado atual, esse problema não possui informações de domínio para encontrar uma solução?
Michael Irey
Você está pensando seriamente que um mundo real está sendo modelado no OP? Um modelo de relacionamento é representado através de um exemplo.
Basilevs 08/01
@Basilevs Esse é o tipo de implicação, na verdade, já que ele menciona como os animais se comportam na vida real. É preciso se preocupar com o motivo pelo qual esse comportamento deve ser contabilizado no programa, IMO. Dito isto, teria sido legal da minha parte sugerir algum design possível.
DPM
10

Você deseja dividir ainda mais os animais em suas subclasses (ou pelo menos na medida em que faz sentido para o que está fazendo). Como você trabalha com animais básicos e dois tipos de alimentos (plantas e carne), faz sentido usar carnívoros e herbívoros para definir melhor um animal e mantê-lo separado. Aqui está o que eu desenhei para você.

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

Como você pode ver, ambos expõem um método de comer, mas o que comem muda. Agora, o Leão pode matar um cervo, o cervo pode morrer e devolver o DeerMeat, e a pergunta original do OP sobre como permitir que um leão coma um cervo, mas a grama não é respondida sem a engenharia de todo um ecossistema.

Obviamente, isso se torna muito rápido porque um cervo também pode ser considerado um tipo de carne, mas, para simplificar, eu criaria um método chamado kill () sob o cervo, que retorna uma carne de cervo e o colocaria como um classe de concreto que estende a carne.

Ampt
fonte
O Deer então exporia uma interface IMeat?
Dan Pichelman 19/07/2013
A carne não é uma interface, é uma classe abstrata. Eu adicionei como eu implementaria isso para você. #
19/07/13
Eat(Plant p)e Eat(Meat m)ambos violam o LSP.
Tulains Córdova
Como assim @ user61852? Eu propositadamente não expus o Eat na interface do animal para que cada tipo de animal pudesse ter seu próprio método de comer.
Ampt
1
TCWL (Muito complexo, vazará). O problema é distribuído e emergente e sua solução é estática, centralizada e predefinida. TCWL.
Tulains Córdova
7

Meu design seria assim:

  1. Os alimentos são declarados como interfaces; existe uma interface IFood e duas interfaces derivadas: IMeat e IVegetable
  2. Animais implementam IMeat e Vegetais implementam IVegetable
  3. Os animais têm dois descendentes, carnívoros e hebreus
  4. Carnívoros têm o método Eat que recebe uma instância do IMeat
  5. Os herbívoros têm o método Eat que recebe uma instância de IVegetable
  6. Leão desce do Carnívoro
  7. Cervos descem do herbívoro
  8. Grama desce do vegetal

Como os animais implementam o IMeat e o Deer é um animal (Herbívoro), o Lion, que é um animal (Carnívoro) que pode comer o IMeat também pode comer o Deer.

O cervo é um herbívoro, por isso pode comer capim porque implementa IVegetable.

Carnívoros não podem comer IVegeable e Herbívoros não podem comer IMeat.

AlexSC
fonte
1
Estou vendo muitos tipos de enumeração aqui usando herança apenas para restringir quando os tipos que estão sendo herdados não implementam nada ... Sempre que você se encontra criando tipos que não implementam nenhuma funcionalidade, é uma oferta algo que está a um pé; você expandiu um modelo no sistema do tipo que produz nenhum valor para a usabilidade em código
Jimmy Hoffa
Lembre-se de que onívoros existem, como seres humanos, macacos e ursos.
Tulains Córdova
Então, como você acrescenta que ambos, leões e veados, são mamíferos? :-)
johannes
2
@ JimmyHoffa Essas são chamadas de "interfaces de marcador" e são um uso totalmente válido da interface. É necessário revisar o código para decidir se o uso é justificado, mas há muitos casos de uso (como este, em que um leão que tenta comer Grass lançaria uma exceção NoInterface). A interface do marcador (ou a falta de) serve para prever uma exceção que será lançada se um método for chamado com argumentos não suportados.
rwong
1
@rwong Eu entendo o conceito, nunca o ouvi formalizar antes; apenas minha experiência tem sido toda vez que uma base de código em que estou trabalhando as torna mais complexas e difíceis de manter. Talvez minha experiência tenha sido apenas onde as pessoas as usaram errado.
21713 Jimmy Hoffa
5

Os alimentos que um animal pode ingerir não formam uma hierarquia; nesse caso, a natureza falhou indesculpável em se adaptar à modelagem simples orientada a objetos (observe que, mesmo se o fizesse, o animal teria que herdar dos alimentos, pois é alimento).

O conhecimento de quais alimentos um animal pode ingerir não pode viver inteiramente com nenhuma das classes, portanto, simplesmente ter uma referência a algum membro da hierarquia alimentar não é suficiente para dizer o que você pode comer.

É um relacionamento de muitos para muitos. Isso significa que toda vez que você adiciona um animal, você precisa descobrir o que ele pode comer, e toda vez que você adiciona um alimento, você precisa descobrir o que pode comê-lo. Se existe mais estrutura para explorar, depende de quais animais e alimentos você estiver modelando.

A herança múltipla também não resolve muito bem isso. Você precisa de algum tipo de coleção de coisas que um animal possa comer ou de animais que possam comer.

psr
fonte
Como eles dizem sobre regex "Eu tive um problema, então usei regex, agora tenho dois problemas", o MI está na mesma linha de "Eu tive um problema, então usei o MI, agora tenho 99 problemas" Se eu fosse você, eu Se seguirmos em vão que você estava cutucando aqui, apesar da comida saber o que pode comê-la, isso na verdade simplifica uma tonelada do modelo. Inversão de dependência FTW.
21713 Jimmy Hoffa
1

Vou abordar o problema de outro lado: OOP é sobre comportamento. No seu caso, Grasstem algum comportamento para ser filho Food? Portanto, no seu caso, não haverá Grassclasse ou, pelo menos, não será herdado Food. Além disso, se você precisar aplicar quem pode comer o que em tempo de compilação, é questionável se você precisa de Animalabstração. Além disso, não é raro ver carnívoros comendo grama , embora não para sustento.

Então, eu projetaria isso como (não vou me incomodar com a arte ASCI):

IEdiblecom propriedade Type, que é enum de carne, planta, carcaça, etc. (isso não muda frequentemente e não possui nenhum comportamento específico, portanto, não há necessidade de modelá-lo como classe hiearchy).

Animalcom métodos CanEat(IEdible food)e Eat(IEdible food), que são lógicos. Então, animais específicos podem verificar sempre que podem comer alimentos em determinadas circunstâncias e depois comer esses alimentos para obter sustento / fazer outra coisa. Além disso, eu modelaria as classes Carnívora, Herbívora e Onívora como padrão de estratégia do que como parte da hierarquia animal.

Eufórico
fonte
1

TL; DR: design ou modelo com um contexto.

Penso que a sua pergunta é difícil porque falta contexto do problema real que você está tentando resolver. Você tem alguns modelos e alguns relacionamentos, mas não possui a estrutura na qual ele precisa trabalhar. Sem contexto, modelagem e metáforas não funcionam bem, deixando a porta aberta para múltiplas interpretações.

Eu acho mais produtivo focar em como os dados serão consumidos. Depois de ter o padrão de uso de dados, é mais fácil trabalhar de volta para o que devem ser os modelos e os relacionamentos.

Por exemplo, requisitos mais detalhados exigirão diferentes relacionamentos com objetos:

  • apoiando Animals eatnão- FoodlikeGastroliths
  • apoiar Chocolatecomo Poisonpara Dogs, mas não paraHumans

Se começarmos o exercício de como modelar o relacionamento simples apresentado, a Interface Alimentar pode ser a melhor; e se essa é a soma total de como os relacionamentos no sistema estão bem. No entanto, apenas alguns requisitos ou relacionamentos adicionais podem afetar amplamente os modelos e relacionamentos que funcionaram no caso mais simples.

dietbuddha
fonte
Concordo, mas é apenas um pequeno exemplo e não estamos tentando modelar o mundo. Por exemplo, você pode ter um tubarão que come pneus e matrículas. Você pode simplesmente criar uma classe abstrata pai com um método que consome qualquer tipo de objeto e Food poderá estender essa classe abstact.
21713 hagensoft
@hagensoft: Concordo. Às vezes eu me empolgo porque vejo constantemente os desenvolvedores modelando com base em uma metáfora que eles usaram imediatamente, em vez de observar como os dados precisam ser consumidos e usados. Eles se casam com um projeto de OO com base em uma idéia inicial e, em seguida, tentam forçar o problema a se adequar à sua solução, em vez de fazê-la encaixar no problema.
dietbuddha
1

Abordagem de composição por herança do 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.

Pseudo-código:

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é um systemloop que percorre essas entidades, procurando quais componentes eles possuem por meio de uma função de consulta generalizada. Naturefará com que entidades com fome de carne atacem outras entidades que tenham carne como alimento usando suas armas, a menos que tenham afinidade com essa entidade. Se o ataque for bem-sucedido, a entidade se alimentará da vítima; nesse momento, a vítima se tornará um cadáver privado de carne. Naturefará com que entidades com fome de plantas se alimentem de entidades que têm plantas como alimento, desde que existam.

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

Talvez desejemos estender Grassa necessidade de luz solar e água e queremos introduzir luz solar e água em nosso mundo. No entanto, Grassnão pode procurá-las diretamente, pois não tem mobility. Animalstambém pode precisar de água, mas pode procurá-la ativamente, pois precisa mobility. É muito fácil continuar estendendo e alterando esse modelo sem quebras em cascata de todo o design, pois apenas adicionamos novos componentes e estendemos o comportamento de nossos sistemas (ou o número de sistemas).


fonte
0

Sem o uso de herança múltipla e o design orientado a objetos, como você resolve esse problema?

Como a maioria das coisas, depende .

Depende do que você vê 'esse problema'.

  • É um problema geral de implementação , por exemplo, como 'contornar' a ausência de herança múltipla na plataforma escolhida?
  • É um problema de design apenas para este caso específico , por exemplo, como modelar o fato de que os animais também são alimentos?
  • É um problema filosófico com o modelo de domínio , por exemplo, 'comida' e 'animal' são classificações válidas, necessárias e suficientes para a aplicação prática prevista?

Se você estiver perguntando sobre o problema geral de implementação, a resposta dependerá dos recursos do seu ambiente. As interfaces IFood e IAnimal podem funcionar, com uma subclasse EdibleAnimal implementando as duas interfaces. Se o seu ambiente não suportar interfaces, basta tornar o Animal herdado do Food.

Se você está perguntando sobre esse problema específico de design, basta fazer o Animal herdar do Food. É a coisa mais simples que poderia funcionar.

Se você está perguntando sobre esses conceitos de design, a resposta depende fortemente do que você pretende fazer com o modelo. Se for para um videogame cachorro-comer-cachorro ou mesmo um aplicativo para rastrear horários de alimentação em um zoológico, pode ser o suficiente para funcionar. Se é para um modelo conceitual de padrões comportamentais de animais, provavelmente é um pouco superficial.

Steven A. Lowe
fonte
0

A herança deve ser usada para algo que sempre é outra coisa e que não pode mudar. Grama nem sempre é comida. Por exemplo, eu não como grama.

A grama desempenha o papel de alimento para certos animais.

Neil McGuigan
fonte
É apenas uma abstração. Se isso for um requisito, você poderá criar mais divisões que estendem a classe abstrata Plant e fazem os humanos comerem uma classe abstrata como 'HumanEatablePlants', que agruparia as plantas que os humanos comem em classes concretas.
21713 hagensoft
0

Você acabou de encontrar a limitação básica do OO.

OO funciona bem com estruturas hierárquicas. Mas uma vez que você se afasta de hierarquias estritas, a abstração não funciona tão bem.

Eu sei tudo sobre composições de metamorfose, etc., que são usadas para contornar essas limitações, mas são desajeitadas e, mais importante, levam a códigos obscuros e difíceis de seguir.

Os bancos de dados relacionais foram inventados principalmente para se livrar das limitações de estruturas hierárquicas estritas.

Para dar o seu exemplo, a grama também pode ser um material de construção, uma matéria-prima para papel, um material de vestuário, uma erva daninha ou uma colheita.

Um cervo pode ser um animal de estimação, gado, um animal de zoológico ou uma espécie protegida.

Um leão também pode ser um animal de zoológico ou uma espécie protegida.

A vida não é simples.

James Anderson
fonte
0

Sem o uso de herança múltipla e o design orientado a objetos, como você resolve esse problema?

Que problema? O que esse sistema faz? Até você responder, não tenho idéia de quais classes podem ser necessárias. Você está tentando modelar uma ecologia, com carnívoros, herbívoros e plantas, projetando populações de espécies no futuro? Você está tentando fazer o computador executar 20 perguntas?

É uma perda de tempo começar o design antes que quaisquer casos de uso sejam definidos. Vi isso levar a extremos ridículos quando uma equipe de cerca de dez começou a produzir um modelo OO de uma companhia aérea usando software através de fotos. Eles trabalharam por dois anos modelando sem nenhum problema comercial real em mente. Finalmente, o cliente se cansou de esperar e pediu à equipe que resolvesse um problema real. Toda essa modelagem foi completamente inútil.

Kevin Cline
fonte