Zero objetos de comportamento no OOP - meu dilema de design

94

A idéia básica por trás da OOP é que dados e comportamento (com base nesses dados) são inseparáveis ​​e são acoplados à idéia de um objeto de uma classe. O objeto possui dados e métodos que funcionam com esse (e outros dados). Obviamente, pelos princípios da OOP, objetos que são apenas dados (como estruturas C) são considerados um antipadrão.

Por enquanto, tudo bem.

O problema é que notei que meu código parece estar indo cada vez mais na direção desse antipadrão ultimamente. Parece-me que quanto mais eu tento obter informações ocultas entre classes e designs fracamente acoplados, mais minhas classes passam a ser uma mistura de dados puros sem classes de comportamento e todo comportamento sem classes de dados.

Geralmente, eu desenho classes de uma maneira que minimize a conscientização da existência de outras classes e minimize o conhecimento das interfaces de outras classes. Eu imponho isso especialmente de cima para baixo, as classes de nível inferior não conhecem as classes de nível superior. Por exemplo:

Suponha que você tenha uma API geral de jogo de cartas. Você tem uma aula Card. Agora essa Cardclasse precisa determinar a visibilidade para os jogadores.

Uma maneira é ter boolean isVisible(Player p)na Cardaula.

Outra é ter boolean isVisible(Card c)na Playeraula.

Não gosto da primeira abordagem em particular, pois concede conhecimento sobre Playerclasse de nível superior a uma Cardclasse de nível inferior .

Em vez disso, optei pela terceira opção, onde temos uma Viewportclasse que, dada ae Playeruma lista de cartões, determina quais cartões são visíveis.

No entanto, essa abordagem rouba as classes Carde as Playerclasses de uma possível função membro. Depois de fazer isso para outras coisas que a visibilidade de cartões, você é deixado com Carde Playerclasses que contêm dados puramente como toda a funcionalidade é implementada em outras classes, que são principalmente as classes sem dados, apenas métodos, como o Viewportacima.

Isso é claramente contra a idéia principal do POO.

Qual é o caminho correto? Como devo executar a tarefa de minimizar as interdependências de classe e minimizar o conhecimento e o acoplamento assumidos, mas sem acabar com um design estranho, onde todas as classes de baixo nível contêm apenas dados e as classes de alto nível contêm todos os métodos? Alguém tem alguma terceira solução ou perspectiva sobre o design de classe que evite todo o problema?

PS Aqui está outro exemplo:

Suponha que você tenha uma classe DocumentIdimutável, que tenha apenas um único BigDecimal idmembro e um getter para esse membro. Agora você precisa ter um método em algum lugar, que DocumentIdretorne Documentesse ID a partir de um banco de dados.

Você:

  • Adicione Document getDocument(SqlSession)método à DocumentIdclasse, introduzindo repentinamente conhecimento sobre sua persistência ( "we're using a database and this query is used to retrieve document by id"), a API usada para acessar o DB e similares. Além disso, essa classe agora requer o arquivo JAR de persistência apenas para compilar.
  • Adicione outra classe com o método Document getDocument(DocumentId id), deixando a DocumentIdclasse como morta, sem comportamento, como uma estrutura.
RokL
fonte
21
Algumas de suas instalações aqui estão completamente erradas, o que tornará muito difícil responder à pergunta subjacente. Mantenha suas perguntas o mais concisas e sem opinião possível e você obterá melhores respostas.
pdr
31
"Isso é claramente contra a idéia principal de OOP" - não, não é, mas é uma falácia comum.
Doc Brown
5
Eu acho que o problema está no fato de que existiram escolas diferentes para "Orientação a Objetos" no passado - da maneira que era originalmente usada por pessoas como Alan Kay (consulte geekswithblogs.net/theArchitectsNapkin/archive/2013/09/08/ … ), E o caminho é ensinado no contexto de OOA / OOD por essas pessoas do Rational ( en.wikipedia.org/wiki/Object-oriented_analysis_and_design ).
Doc Brown
21
Essa é uma pergunta muito boa e bem postada - ao contrário de alguns dos outros comentários, eu diria. Ele mostra claramente como são ingênuos ou incompletos os conselhos sobre como estruturar o programa - e quão difícil é fazê-lo, e quão inacessível é um design adequado em muitas situações, não importa o quanto se esforce para fazê-lo corretamente. E embora uma resposta óbvia para a pergunta específica seja multi-métodos, o problema subjacente do design persiste.
Thiago Silva
5
Quem disse que classes sem comportamento são um anti-padrão?
James Anderson

Respostas:

42

O que você descreve é ​​conhecido como modelo de domínio anêmico . Como acontece com muitos princípios de design de POO (como Lei de Demeter etc.), não vale a pena reverter apenas para satisfazer uma regra.

Não há nada de errado em ter sacos de valores, desde que não atrapalhem a paisagem inteira e não confiem em outros objetos para fazer as tarefas domésticas que poderiam estar fazendo por si mesmos .

Certamente seria um cheiro de código se você tivesse uma classe separada apenas para modificar propriedades de Card- se for razoavelmente esperado que ela cuide delas por conta própria.

Mas é realmente um trabalho de Cardsaber para que Playeré visível?

E por que implementar Card.isVisibleTo(Player p), mas não Player.isVisibleTo(Card c)? Ou vice-versa?

Sim, você pode tentar criar algum tipo de regra para isso, como fez - como Playerter um nível mais alto do que um Card(?) -, mas não é tão fácil de adivinhar e vou ter que procurar em mais de um lugar para encontrar o método

Com o tempo, isso pode levar a um comprometimento podre do projeto de implementação tantoisVisibleTo na classe quanto na classe, o que acredito ser um não-não. Por quê então? Como eu já imagino o dia vergonhoso em que retornará um valor diferente do que penso - é subjetivo - isso deve ser tornado impossível pelo design . CardPlayerplayer1.isVisibleTo(card1)card1.isVisibleTo(player1).

Visibilidade mútua dos cartões e os jogadores devem ser melhor governada por algum tipo de um objeto de contexto - seja ele Viewport, Dealou Game.

Não é igual a ter funções globais. Afinal, pode haver muitos jogos simultâneos. Observe que o mesmo cartão pode ser usado simultaneamente em muitas mesas. Vamos criar muitas Cardinstâncias para cada ás de espada?

Eu ainda pode implementar isVisibleToem Card, mas passar um objeto de contexto para ele e fazer Carddelegado a consulta. Programa para fazer interface para evitar acoplamentos altos.

Quanto ao seu segundo exemplo - se o ID do documento consiste apenas em a BigDecimal, por que criar uma classe de wrapper para ele?

Eu diria que tudo que você precisa é de um DocumentRepository.getDocument(BigDecimal documentID);

A propósito, enquanto ausente do Java, há structs em C #.

Vejo

para referência. É uma linguagem altamente orientada a objetos, mas ninguém faz muita diferença.

Konrad Morawski
fonte
1
Apenas uma observação sobre estruturas em C #: elas não são estruturas típicas, como você as conhece em C. Na verdade, elas também suportam OOP com herança, encapsulamento e polimorfismo. Além de algumas peculiaridades, a principal diferença é como o tempo de execução lida com instâncias quando elas são passadas para outros objetos: estruturas são tipos de valor e classes são tipos de referência!
Aschratt #
3
@ Aschratt: Estruturas não suportam herança. Estruturas podem implementar interfaces, mas estruturas que implementam interfaces se comportam de maneira diferente dos objetos de classe que fazem o mesmo. Embora seja possível fazer com que estruturas se comportem de maneira semelhante a objetos, o melhor caso de uso para estruturas em que alguém deseja algo que se comporta como uma estrutura C, e as coisas que está encapsulando são primitivas ou tipos de classe imutáveis.
supercat
1
+1 por que "não é igual a ter funções globais". Isso não foi muito abordado por outros. (Embora se você tiver vários baralhos, uma função global ainda retornará valores diferentes para instâncias distintas da mesma placa).
Alexis6
@supercat Isso é digno de uma pergunta separada ou de uma sessão de bate-papo, mas atualmente não estou interessado em :-( Você diz (em C #) "estruturas que implementam interfaces se comportam de maneira diferente dos objetos de classe que fazem o mesmo". Concordo que existem outras diferenças comportamentais a serem consideradas, mas, no código a seguir Interface iObj = (Interface)obj;, o comportamento de AFAIK iObjnão é afetado pelo structou classstatus de obj(exceto que será uma cópia em caixa dessa atribuição se for a struct).
Mark Hurd
150

A idéia básica por trás da OOP é que dados e comportamento (com base nesses dados) são inseparáveis ​​e são acoplados à idéia de um objeto de uma classe.

Você está cometendo o erro comum de assumir que as classes são um conceito fundamental no OOP. As aulas são apenas uma maneira particularmente popular de obter o encapsulamento. Mas podemos permitir que isso deslize.

Suponha que você tenha uma API geral de jogo de cartas. Você tem um cartão de classe. Agora, essa classe de cartas precisa determinar a visibilidade para os jogadores.

BONS CÉUS NO. Quando você está jogando Bridge, você pergunta aos sete de copas quando é hora de mudar a mão do manequim de um segredo conhecido apenas pelo manequim para ser conhecido por todos? Claro que não. Isso não é uma preocupação do cartão.

Uma maneira é ter isVisible booleano (Player p) na classe Card. Outra é ter isVisible booleano (cartão c) na classe Player.

Ambos são horríveis; não faça nenhum deles. Nem o jogador nem a carta são responsáveis ​​por implementar as regras do Bridge!

Em vez disso, optei pela terceira opção, onde temos uma classe Viewport que, dada a um jogador e uma lista de cards, determina quais cards são visíveis.

Eu nunca joguei cartas com uma "viewport" antes, então não tenho idéia do que essa classe deve encapsular. Eu ter jogado cartas com um par de baralhos de cartas, alguns jogadores, uma mesa e uma cópia de Hoyle. Qual dessas coisas a Viewport representa?

No entanto, essa abordagem rouba as classes Card e Player de uma possível função membro.

Boa!

Depois de fazer isso para outras coisas que não a visibilidade dos cartões, você fica com as classes Card e Player que contêm puramente dados, pois toda a funcionalidade é implementada em outras classes, que são principalmente classes sem dados, apenas métodos, como o Viewport acima. Isso é claramente contra a idéia principal do POO.

Não; a idéia básica do OOP é que os objetos encapsulem suas preocupações . No seu sistema, um cartão não está preocupado com muita coisa. Nem é um jogador. Isso ocorre porque você está modelando com precisão o mundo . No mundo real, as propriedades são cartas relevantes para um jogo e extremamente simples. Poderíamos substituir as figuras dos cartões pelos números de 1 a 52 sem alterar muito o jogo. Poderíamos substituir as quatro pessoas por manequins rotulados como Norte, Sul, Leste e Oeste sem mudar muito o jogo. Jogadores e cartas são as coisas mais simples do mundo dos jogos de cartas. As regras são o que é complicado, então a classe que representa as regras é onde a complicação deve estar.

Agora, se um de seus jogadores for uma IA, seu estado interno poderá ser extremamente complicado. Mas essa IA não determina se pode ver um cartão. As regras determinam isso .

Aqui está como eu projetaria seu sistema.

Primeiro, as cartas são surpreendentemente complicadas se houver jogos com mais de um baralho. Você deve considerar a pergunta: os jogadores podem distinguir entre duas cartas do mesmo valor? Se o jogador um toca um dos sete de copas, e então algumas coisas acontecem, e o jogador dois joga um dos sete de copas, o jogador três pode determinar que eram os mesmos sete copas? Considere isso com cuidado. Mas, além dessa preocupação, os cartões devem ser muito simples; são apenas dados.

Em seguida, qual é a natureza de um jogador? Um jogador consome uma sequência de ações visíveis e produz uma ação .

O objeto de regras é o que coordena tudo isso. As regras produzem uma sequência de ações visíveis e informam os jogadores:

  • Jogador um, os dez de copas foram entregues a você pelo jogador três.
  • Jogador dois, uma carta foi entregue ao jogador um pelo jogador três.

E então pede ao jogador uma ação.

  • Jogador um, o que você quer fazer?
  • O jogador um diz: triple thep.
  • Jogador um, que é uma ação ilegal, porque um fromp triplo produz uma jogada indefensável.
  • Jogador um, o que você quer fazer?
  • O jogador um diz: descarte a dama de espadas.
  • Jogador dois, jogador um descartou a dama de espadas.

E assim por diante.

Separe seus mecanismos de suas políticas . As políticas do jogo devem ser encapsuladas em um objeto de política , não nas cartas . Os cartões são apenas um mecanismo.

Eric Lippert
fonte
41
@gnat: Uma opinião contrastante de Alan Kay é: "Na verdade, eu inventei o termo" orientado a objetos ", e posso dizer que não tinha C ++ em mente." Existem linguagens OO sem classes; JavaScript vem à mente.
precisa
19
@gnat: Concordo que o JS como está hoje não é um ótimo exemplo de uma linguagem OOP, mas mostra que alguém poderia facilmente construir uma linguagem OO sem classes. Concordo que a unidade fundamental de OO-ness em Eiffel e C ++ é a classe; onde eu discordo é a noção de que as classes são sine qua non da OO. O sine qua non do OO são objetos que encapsulam o comportamento e se comunicam por meio de uma interface pública bem definida.
precisa
16
Concordo com @EricLippert, as classes não são fundamentais para o OO, nem a herança, seja o que for que o mainstream possa dizer. O encapsulamento de dados, comportamento e responsabilidade é, no entanto, realizado. Existem linguagens baseadas em protótipos além Javascript que são OO, mas sem classes. É particularmente um erro focar na herança sobre esses conceitos. Dito isto, as aulas são meios muito úteis de organizar o encapsulamento do comportamento. O fato de você poder tratar as classes como objetos (e em linguagens de protótipos, vice-versa) torna a linha embaçada.
Schwern 02/04/19
6
Considere da seguinte maneira: que comportamento um cartão real, no mundo real, exibe por si próprio? Eu acho que a resposta é "nenhuma". Outras coisas atuam no cartão. O cartão em si, no mundo real, é literalmente apenas informação (4 de clubes), sem nenhum comportamento intrínseco de qualquer tipo. Como essas informações (também conhecidas como "uma carta") são usadas depende de 100% de alguém / outra pessoa, também conhecidas como "regras" e "jogadores". As mesmas cartas podem ser usadas para uma variedade infinita (bem, talvez não exatamente) de jogos diferentes, por qualquer número de jogadores diferentes. Um cartão é apenas um cartão, e tudo o que tem são propriedades.
Craig
5
@ Montagist: Deixe-me esclarecer um pouco as coisas então. Considere C. Acho que você concorda que C não tem aulas. No entanto, você poderia dizer que estruturas são "classes", criar campos do tipo ponteiro de função, criar vtables, criar métodos chamados "construtores" que configuram as vtables para que algumas estruturas sejam herdadas umas das outras, e assim por diante. Você pode emular a herança baseada em classe em C. E você pode emular em JS. Mas fazer isso significa criar algo no topo da linguagem que ainda não está lá.
precisa
29

Você está certo de que o acoplamento de dados e comportamento é a ideia central do POO, mas há mais do que isso. Por exemplo, encapsulamento : OOP / programação modular nos permite separar uma interface pública dos detalhes da implementação. No POO, isso significa que os dados nunca devem estar acessíveis ao público e devem ser usados ​​apenas via acessadores. Por essa definição, um objeto sem nenhum método é realmente inútil.

Uma classe que não oferece métodos além dos acessadores é essencialmente uma estrutura complicada demais. Mas isso não é ruim, porque OOP oferece a flexibilidade de alterar detalhes internos, o que uma estrutura não. Por exemplo, em vez de armazenar um valor em um campo de membro, ele pode ser recalculado a cada vez. Ou um algoritmo de apoio é alterado e, com ele, o estado que deve ser mantido.

Embora a POO tenha algumas vantagens claras (especialmente a programação processual simples), é ingênuo buscar a OOP "pura". Alguns problemas não são bem mapeados para uma abordagem orientada a objetos e são resolvidos mais facilmente por outros paradigmas. Ao encontrar esse problema, não insista em uma abordagem inferior.

  • Considere calcular a sequência de Fibonacci de maneira orientada a objetos . Não consigo pensar em uma maneira sã de fazer isso; Uma programação estruturada simples oferece a melhor solução para esse problema.

  • Sua isVisiblerelação pertence a ambas as classes, ou a nenhuma, ou realmente: ao contexto . Registros comportamentais são típicos de uma abordagem de programação funcional ou procedural, que parece ser a mais adequada ao seu problema. Não há nada errado com

    static boolean isVisible(Card c, Player p);
    

    e não há nada errado em Cardnão ter métodos além ranke suitacessadores.

amon
fonte
11
@ Humad sim, esse é exatamente o meu ponto, e não há nada de errado nisso. Use o paradigma <del> idioma </del> correto para o trabalho em questão. (A propósito, a maioria das linguagens além do Smalltalk não é totalmente orientada a objetos. Por exemplo, Java, C # e C ++ oferecem suporte a programação imperativa, estruturada, procedural, modular, funcional e orientada a objetos. Todos esses paradigmas não OO estão disponíveis por uma razão : para que você possa usá-los)
amon
1
Existe uma maneira sensata de OO para fazer Fibonacci, chame o fibonaccimétodo em uma instância de integer. Gostaria de enfatizar seu argumento de que OO é sobre encapsulamento, mesmo em lugares aparentemente pequenos. Deixe o número inteiro descobrir como fazer o trabalho. Mais tarde, você pode melhorar a implementação, adicionar armazenamento em cache para melhorar o desempenho. Diferentemente das funções, os métodos seguem os dados para que todos os chamadores obtenham o benefício de uma implementação aprimorada. Talvez números inteiros de precisão arbitrários sejam adicionados posteriormente, eles podem ser tratados de forma transparente como números inteiros normais e podem ter seu próprio fibonaccimétodo aprimorado de desempenho .
Schwern
2
@Schwern, se alguma coisa Fibonaccié uma subclasse da classe abstrata Sequence, a sequência é utilizada por qualquer conjunto de números e é responsável por armazenar sementes, estado, cache e um iterador.
George Reith
2
Eu não esperava que o "puro OOP Fibonacci" fosse tão eficaz no corte de nerds . Pare qualquer discussão circular sobre isso nesses comentários, embora tenha um certo valor de entretenimento. Agora vamos fazer algo construtivo para variar!
amon
3
seria bobagem fazer de fibonacci um método de números inteiros, para que você possa dizer que é POO. É uma função e deve ser tratada como uma função.
precisa saber é o seguinte
19

A idéia básica por trás da OOP é que dados e comportamento (com base nesses dados) são inseparáveis ​​e são acoplados à idéia de um objeto de uma classe. O objeto possui dados e métodos que funcionam com esse (e outros dados). Obviamente, pelos princípios da OOP, objetos que são apenas dados (como estruturas C) são considerados um antipadrão. (...) Isso é claramente contrário à idéia principal de OOP.

Essa é uma pergunta difícil porque se baseia em algumas premissas defeituosas:

  1. A ideia de que OOP é a única maneira válida de escrever código.
  2. A ideia de que OOP é um conceito bem definido. Tornou-se uma palavra da moda que é difícil encontrar duas pessoas que possam concordar sobre o que é o POO.
  3. A idéia de que OOP é sobre empacotamento de dados e comportamento.
  4. A ideia de que tudo é / deve ser uma abstração.

Não vou falar muito sobre o item 1-3, porque cada um pode gerar sua própria resposta e isso convida a muitas discussões baseadas em opiniões. Mas acho que a idéia de "OOP é sobre o acoplamento de dados e comportamento" é especialmente preocupante. Não apenas leva ao número 4, mas também à idéia de que tudo deve ser um método.

Há uma diferença entre as operações que definem um tipo e as maneiras como você pode usá-lo. Ser capaz de recuperar o ielemento th é essencial para o conceito de uma matriz, mas a classificação é apenas uma das muitas coisas que posso escolher fazer com uma. A classificação não precisa ser um método, assim como "criar uma nova matriz contendo apenas os elementos pares".

OOP é sobre o uso de objetos. Objetos são apenas uma maneira de obter abstração . A abstração é um meio de evitar acoplamentos desnecessários no seu código, não um fim por si só. Se sua noção de cartão é definida apenas pelo valor de seu conjunto e classificação, é bom implementá-lo como uma tupla ou registro simples. Não há detalhes não essenciais nos quais qualquer outra parte do código possa formar uma dependência. Às vezes você simplesmente não tem nada a esconder.

Você não faria isVisibleum método desse Cardtipo porque ser visível provavelmente não é essencial para a sua noção de cartão (a menos que você tenha cartões muito especiais que possam se tornar translúcidos ou opacos ...). Deve ser um método do Playertipo? Bem, isso provavelmente também não é uma qualidade definidora dos jogadores. Deveria fazer parte de algum Viewporttipo? Mais uma vez, isso depende do que você define como uma viewport e se a noção de verificar a visibilidade dos cartões é essencial para definir uma viewport.

É muito possível, isVisibledeve ser apenas uma função gratuita.

Doval
fonte
1
+1 para o senso comum, em vez de zangão irracional.
rightfold
Pelas linhas que li, o ensaio que você vinculou se parece com uma leitura sólida que não tenho há um tempo.
Arthur Havlicek
@ArthurHavlicek É mais difícil de seguir se você não entender os idiomas usados ​​no exemplo de código, mas achei bastante esclarecedor.
Doval
9

Obviamente, pelos princípios da OOP, objetos que são apenas dados (como estruturas C) são considerados um antipadrão.

Não, eles não são. Os objetos Plain-Old-Data são um padrão perfeitamente válido e eu esperaria que eles em qualquer programa que lida com dados que precisam ser persistidos ou comunicados entre áreas distintas do seu programa.

Embora sua camada de dados possa gerar uma Playerclasse completa quando lê da Playerstabela, ela pode ser apenas uma biblioteca de dados geral que retorna um POD com os campos da tabela, que passa para outra área do programa que converte um jogador POD para sua Playerclasse de concreto .

O uso de objetos de dados, digitados ou não, pode não fazer sentido no seu programa, mas isso não os torna um antipadrão. Se eles fazem sentido, use-os e, se não, não.

DougM
fonte
5
Não discorde de nada do que você disse, mas isso não responde à pergunta. Dito isto, culpo a pergunta mais do que a resposta.
Pd2 /
2
Exatamente, cartões e documentos são apenas recipientes de informações, mesmo no mundo real, e qualquer "padrão" que não possa lidar com isso precisa ser ignorado.
Jeffo
1
Plain-Old-Data objects are a perfectly valid pattern Eu não disse que eles não estavam, estou dizendo que é errado quando eles preenchem toda a metade inferior do aplicativo.
RokL
8

Pessoalmente, acho que o Design Orientado a Domínio ajuda a esclarecer esse problema. A pergunta que faço é: como descrevo o jogo de cartas para seres humanos? Em outras palavras, o que estou modelando? Se a coisa que estou modelando inclui genuinamente a palavra "viewport" e um conceito que corresponde ao seu comportamento, eu criaria o objeto de viewport e o faria logicamente.

No entanto, se eu não tiver o conceito de viewport no meu jogo, é algo que acho que preciso, porque, caso contrário, o código "parecerá errado". Penso duas vezes em adicioná-lo ao meu modelo de domínio.

A palavra modelo significa que você está construindo uma representação de algo. Eu aviso contra colocar em uma classe que represente algo abstrato além do que você está representando.

Editarei para acrescentar que é possível que você precise do conceito de uma Viewport em outra parte do seu código, se precisar interagir com uma exibição. Mas, em termos de DDD, isso seria uma preocupação de infraestrutura e existiria fora do modelo de domínio.

RibaldEddie
fonte
A resposta de Lippert acima é um exemplo melhor desse conceito.
precisa saber é o seguinte
5

Normalmente não faço autopromoção, mas o fato é que escrevi muito sobre questões de design de OOP no meu blog . Para resumir várias páginas: você não deve iniciar o design com classes. A partir de interfaces ou APIs e código de forma a partir daí, há maiores chances de fornecer abstrações significativas, ajustar especificações e evitar inchar classes concretas com código não reutilizável.

Como isso se aplica a Card- Playerproblema: Criar uma ViewPortabstração faz sentido se você pensar Carde Playercomo sendo duas bibliotecas independentes (o que implicaria Playeràs vezes é usado sem Card). No entanto, eu estou inclinado a pensar que a Playerdetém Cardse deve fornecer um Collection<Card> getVisibleCards ()assessor para eles. Ambas as soluções ( ViewPorte as minhas) são melhores do que fornecer isVisiblecomo método Cardou Player, em termos de criação de relacionamentos de código compreensíveis.

Uma solução de fora da classe é muito, muito melhor para o DocumentId. Há pouca motivação para fazer (basicamente um número inteiro) depender de uma complexa biblioteca de banco de dados.

Arthur Havlicek
fonte
Eu gosto do seu blog.
RokL
3

Não sei se a pergunta em questão está sendo respondida no nível certo. Eu pedi aos sábios do fórum que pensassem ativamente no cerne da questão aqui.

U Mad está trazendo à tona uma situação em que ele acredita que a programação de acordo com seu entendimento de OOP geralmente resultaria em muitos nós de folha sendo titulares de dados, enquanto sua API de nível superior é a maior parte do comportamento.

Eu acho que o tópico foi um pouco tangencial para se isVisible seria definido no Card vs Player; foi um mero exemplo ilustrado, embora ingênuo.

Eu tinha pressionado os experientes aqui a olhar para o problema em questão. Eu acho que há uma boa pergunta que U Mad fez. Entendo que você empurraria as regras e a lógica em questão para um objeto próprio; mas pelo que entendi a pergunta é

  1. É bom ter construções simples de suporte de dados (classes / estruturas; não me importo com o que elas são modeladas quanto a esta pergunta) que realmente não oferecem muita funcionalidade?
  2. Se sim, qual é a melhor ou preferida maneira de modelá-los?
  3. Se não, como incorporamos esses contadores de dados em classes de API mais altas (incluindo comportamento)

Minha visão:

Acho que você está fazendo uma pergunta de granularidade difícil de acertar na programação orientada a objetos. Na minha pouca experiência, eu não incluiria uma entidade no meu modelo que não inclua nenhum comportamento por si só. Se fosse necessário, provavelmente usaria uma construção que é projetada para armazenar essa abstração, diferentemente de uma classe que tem a idéia de encapsular dados e comportamento.

Harsha
fonte
3
O problema é que a pergunta (e sua resposta) é sobre como fazer as coisas "em geral". O fato é que nunca fazemos as coisas "em geral". Sempre fazemos coisas específicas. É necessário examinar nossas coisas específicas e compará-las com nossos requisitos, a fim de determinar se nossas coisas específicas são as coisas corretas para a situação.
John Saunders
@JohnSaunders Eu percebo sua sabedoria aqui e concordo até certo ponto, mas também há uma mera abordagem conceitual necessária antes de abordar um problema. Afinal, a questão aqui não é tão aberta quanto parece ser. Eu acho que é uma pergunta válida de OOD que qualquer designer de OO enfrenta nos usos iniciais do OOP. Qual é a sua opinião? Se uma concreção ajudar, poderíamos discutir a construção de um exemplo de sua escolha.
Harsha #
Estou fora da escola há mais de 35 anos. No mundo real, encontro muito pouco valor em "abordagens conceituais". Acho que a experiência é uma professora melhor do que Meyers neste caso.
John Saunders
Eu realmente não entendo a classe para dados versus classe para distinção de comportamento. Se você abstrair seus objetos corretamente, não haverá distinção. Imagine uma função Pointwith getX(). Você pode imaginar que está recebendo um de seus atributos, mas também pode lê-lo do disco ou da Internet. Obter e configurar é um comportamento, e ter aulas que fazem exatamente isso é ótimo. Bancos de dados somente obter e definir fwiw dados
Arthur Havlicek
@ArthurHavlicek: Saber o que uma classe não fará é geralmente tão útil quanto saber o que ela fará. Ter algo especificado em seu contrato que ele se comportará como nada mais que um detentor de dados imutáveis ​​compartilháveis ​​ou como nada mais que um detentor de dados mutáveis ​​não compartilháveis, é útil.
supercat
2

Uma fonte comum de confusão no POO decorre do fato de que muitos objetos encapsulam dois aspectos do estado: as coisas que eles sabem e o que sabem sobre eles. As discussões sobre o estado dos objetos freqüentemente ignoram o último aspecto, já que em estruturas em que as referências a objetos são promíscuas, não há maneira geral de determinar o que as coisas podem saber sobre qualquer objeto cuja referência já tenha sido exposta ao mundo exterior.

Eu sugeriria que provavelmente seria útil ter um CardEntityobjeto que encapsulasse esses aspectos do cartão em componentes separados. Um componente se relacionaria às marcações na carta (por exemplo, "Diamond King" ou "Lava Blast; os jogadores têm chance de esquivar-se do AC-3 ou sofrer dano em 2D6"). Pode-se dizer respeito a um aspecto único do estado, como a posição (por exemplo, no convés, na mão de Joe ou na mesa em frente a Larry). Um terço pode estar relacionado a ele (talvez ninguém, talvez um jogador ou talvez muitos jogadores). Para garantir que tudo seja mantido sincronizado, os locais onde um cartão pode estar não seriam encapsulados como campos simples, mas como CardSpaceobjetos; para mover um cartão para um espaço, daria-lhe uma referência aoCardSpaceobjeto; ele se retiraria do espaço antigo e se colocaria no novo espaço).

Encapsular explicitamente "quem sabe sobre X" separadamente de "o que X sabe" deve ajudar a evitar muita confusão. Às vezes, é necessário cuidado para evitar vazamentos de memória, especialmente com muitas associações (por exemplo, se novos cartões surgirem e cartões antigos desaparecerem, é preciso garantir que os cartões que devem ser abandonados não sejam permanentemente presos a objetos de vida longa. ), mas se a existência de referências a um objeto formar uma parte relevante de seu estado, é inteiramente apropriado que o próprio objeto encapsule explicitamente essas informações (mesmo que delegue para alguma outra classe o trabalho de realmente gerenciá-las).

supercat
fonte
0

No entanto, essa abordagem rouba as classes Card e Player de uma possível função membro.

E como isso é ruim / desaconselhado?

Para usar uma analogia semelhante ao exemplo de suas placas, considere a Car, a Drivere você precisa determinar se a Driverunidade pode Car.

OK, então você decidiu que não quer que você Carsaiba se a Driverchave do carro está correta ou não e, por algum motivo desconhecido, você também decidiu que não quer que você Driversaiba sobre a Carclasse (você não sabia exatamente o que fazer) isso também na sua pergunta original). Portanto, você tem uma classe intermediária, algo ao longo das linhas de uma Utilsclasse, que contém o método com regras de negócios para retornar um booleanvalor para a pergunta acima.

Eu acho que isso está bem. A classe intermediária pode precisar apenas verificar as chaves do carro agora, mas pode ser refatorada para considerar se o motorista possui uma carteira de motorista válida, sob a influência de álcool ou, em um futuro distópico, verifique a biometria do DNA. Por encapsulamento, realmente não há grandes problemas com essas três classes coexistindo juntas.

hjk
fonte