David West, em seu livro Object Thinking (capítulo 10, seção 1, subseção 2), propôs que, em um ambiente ideal de OO, todos os objetos deveriam ser capazes de se apresentar mediante solicitação; seja para humanos (como GUI), componentes não nativos (como JSON e / ou XML) ou qualquer outra parte interessada:
O pensamento sobre objetos diz que uma visão (às vezes chamada de interface) - gráfica ou não - é um meio para um objeto se comunicar com outro objeto e nada mais. A necessidade de uma visualização surge quando um objeto precisa se apresentar de forma "não nativa" para outro objeto (geralmente um ser humano) ou aplicativo (por exemplo, uma visualização XML para objetos de dados compartilhados entre plataformas).
A descoberta da necessidade e dos parâmetros que devem ser satisfeitos por uma exibição é manifestada nos cenários em que o objeto participa. Sempre que um objeto é solicitado a se exibir, ele deve usar uma exibição - uma representação - apropriada para o remetente dessa mensagem de exibição. Se, por exemplo, um objeto está tentando instanciar-se (obter um valor para si mesmo), ele deve apresentar uma visão de si mesmo como uma solicitação implícita a um ser humano (ou outro objeto de prestação de serviço) por um valor. Se estivermos criando uma GUI que servirá como intermediário entre um objeto de software e um objeto humano, usaremos glifos para fins de exibição e widgets para fins de interação.
Mas quais glifos e widgets precisam ser incluídos na GUI? Somente aqueles necessários para concluir o cenário ou os cenários 4 de interesse imediato à medida que o aplicativo é executado. Essa perspectiva é contra-intuitiva para a maioria dos desenvolvedores, pois sugere que uma GUI seja definida a partir do aplicativo.
Como exemplo, considere uma cervejaria. De um lado estão cubas cheias de cerveja. Na complexa linha de produção que consiste em lavadoras de garrafas, estações de envase, tampadoras e montadoras de embalagens. Acima de tudo, há uma estação de controle que monitora a cervejaria e notifica os gerentes humanos sobre status e problemas. É provável que os desenvolvedores tradicionais iniciem sua análise e design de "um sistema de gerenciamento de cervejaria" do ponto de vista do painel de controle. Isso é análogo ao design a partir da interface no.
O pensamento de objetos sugere, em vez disso, que você considere qual objeto é o principal cliente da cervejaria e de todas as suas inúmeras máquinas. Em nome de quem existe o complexo labirinto de equipamentos? A resposta correta dos negócios é, obviamente, "O cliente". Mas uma resposta mais reflexiva do pensamento sobre objetos é: "A cerveja". Todos os cenários são escritos da perspectiva da cerveja, tentando entrar em uma garrafa, com uma tampa, colocada em um pacote e residente em um caminhão. O painel de controle é um observador passivo 5 do estado da cervejaria. Se a cerveja encontrar um problema em algum momento, é responsabilidade da cerveja solicitar intervenção dos operadores humanos enviando uma mensagem ao painel de controle (ou painéis de controle específicos da máquina) solicitando um serviço de intervenção.
Essa perspectiva simplificará o design da GUI e, mais importante, eliminará o host de objetos de gerente e controlador que parecem surgir inevitavelmente ao projetar a partir da perspectiva do painel de controle (GUI).
Vindo de um iniciante no mundo OO: será esse realmente o caso?
Ter objetos que sabem se representar certamente poderia reduzir o número de objetos de controlador / gerente que West disse repetidamente em seu livro que um Object Thinker supostamente deveria tentar evitar a todo custo. Mas o cumprimento dessa "regra" não quebrará o SRP ?
Além disso (se for o caso), dada uma implementação típica em, digamos, um aplicativo Android: Como alguém pode atingir esse tipo de objetivo? Todo objeto que criamos deve saber se apresentar como um View
?
fonte
Respostas:
Acho que essa é uma das coisas mais difíceis de entender sobre o design de OO e, honestamente, acho que muitos autores estão errados sobre isso e / ou não o explicam muito bem. Muitas pessoas entendem isso errado e nunca descobrem realmente. Vamos dar um exemplo que não é baseado em GUI, mas que fica na mesma armadilha.
Em Java, todo objeto tem um método igual. Você tem tipos de coleções como set e map que dependem desse método para determinar quando os objetos devem ser adicionados à coleção ou quando são duplicados. Isso parece ser bom para muitas pessoas. O problema é que você acaba com um objeto (a coleção) cujo comportamento não é determinado por ele, mas pelos objetos que ele contém. É um pouco como ter os passageiros no ônibus direto para onde deveriam ir. E se eles discordarem? Este não é um problema teórico, é um problema realmente complicado, onde você basicamente precisa quebrar a herança para evitar erros no seu programa. Tome uma forma e uma forma colorida. Um quadrado 2x2 é igual a um quadrado azul 2x2? Shape diz 'yes' e ColoredShape diz 'no'. Quem' está certo? A resposta depende do que você deseja que aconteça na sua coleção. Pode não depender nem do que você está tentando fazer.
Você verá isso surgir como um problema repetidamente. O engraçado é que existe uma solução e fica ao lado do Comparable. Os objetos que implementam Comparable têm esse mesmo dilema, mas agora eles precisam não apenas determinar se são iguais, mas também se são maiores que outro objeto. É realmente intratável fora de um escopo muito restrito de uso. Então, temos outra coisa chamada Comparador. Seu trabalho é olhar para dois objetos e dizer à coleção qual é o maior. Todos os problemas que você está tentando fazer isso no objeto comparável desaparecem.
Não conheço este livro e não conheço o autor, mas o exemplo da cerveja não parece útil. Como a cerveja saberia se deveria estar em uma garrafa ou em um barril e por que tomaria essa decisão? Seu trabalho é provar bem e fornecer álcool à corrente sanguínea dos usuários. Realmente achamos que as cervejarias funcionam assim? "Ok cerveja, você deve estar em uma garrafa ou em um barril e se for uma garrafa, deve ser uma garrafa de 25 onças ou uma garrafa de 12 onças?" Qual é a cerveja neste caso (sem trocadilhos) afinal? É uma gota de cerveja? Talvez isso esteja fora de contexto, mas acho que isso está errado ou, pelo menos, não está adicionando nenhuma iluminação a esse conceito.
Dito tudo isso, há uma abordagem para a criação de interfaces que usei que pode simplificar as coisas e torná-las mais OO. Essencialmente, você cria uma interface que define as ações abstratas que você pode executar para exibir o objeto. Você pode ter uma interface chamada
Display
métodos comosetTitle
ousetDescription
se estiver usando o padrão de nomenclatura Java padrão. Então seu objeto teria um métododisplay(Display display)
(porque três vezes é o charme!) Nessa abordagem, o objeto não precisa entender qual é a interface, pode ser texto, binário, svg, bitmap, o que for e a interface não precisa saber sobre o objeto . Dessa maneira, um objeto pode "se exibir" sem precisar saber como o monitor funciona. Essa abordagem pode reduzir bastante o número de classes de wrapper necessárias, mas pode ser complicada se você tiver requisitos de exibição complexos que variam de acordo com o objeto. Você pode misturá-lo com abordagens padrão do tipo MVC com bom efeito.fonte
Display
trabalho funcionaria em sua implementação concreta (como deveriamJFrame
, por exemplo, por exemplo) ) Obrigado.O princípio da responsabilidade única não significa que uma classe faça apenas uma coisa. Isso significa que uma classe tem um único motivo para mudar.
Você provavelmente está pensando em um método , que realmente faz apenas uma coisa.
Levada à sua conclusão lógica, sua versão do SRP significaria que você nunca seria capaz de registrar nada, porque o registro é uma responsabilidade separada.
É melhor pensar em uma classe como um assunto único e bem definido . Você pode ter vários métodos que apóiam esse assunto, e todos podem estar fazendo coisas diferentes.
O método mais fundamental "exibir-me" é
ToString
, que é sempre um método no objeto.Tudo isso dito, quando criamos uma interface do usuário, geralmente incentivamos a Separação de Preocupações , fornecendo objetos cujo único objetivo é exibir dados de outros objetos (por exemplo, Visualizações).
Talvez um exemplo esteja em ordem. Considere um site PHP, usando um View. Uma visualização simples pode ser assim:
No PHP, o View sempre contém uma
output()
função. Para obter a representação visual de uma visualização, basta ligaroutput()
e você obterá uma string adequada para exibição em qualquer navegador moderno.Se você perceber, a tela faz referência a um objeto chamado
model
. Este é o objeto que contém os dados reais. A vista e o modelo estão em objetos separados; é isso que estabelece a separação de preocupações.A separação de preocupações é importante para sites, porque é o que permite que um designer trabalhe em um design de página da Web, além do programador.
Leitura Adicional
O Padrão MVC e PHP
fonte
Receipt
objeto em um software típico de PDV, você ainda ligariaReceipt.draw(Canvas)
e nãoReceiptView.draw(Receipt)
.A julgar pelas citações coladas, você está interpretando mal o texto.
O autor não está dizendo que todo objeto deve ser capaz de se apresentar em uma interface do usuário. Isso seria impossível, pois um objeto não pode saber em qual interface do usuário ele será mostrado (em um aplicativo WinForms, um aplicativo Linux usando XServer, como uma string JSON, como XML, como uma imagem PNG etc.).
O argumento é que você deve escrever visões especializadas cuja única responsabilidade é exibir uma determinada entidade. Você pode, por exemplo, escrever uma exibição que renderize um objeto como HTML (como é feito pelas exibições nos aplicativos MVC). Você pode criar um serializador JSON que pode transformar um objeto em uma sequência JSON. Você pode criar um objeto que transforme outros tipos de objetos em relatórios PDF. Tudo depende.
A questão é separar a entidade comercial da representação visual (ou serializada). São coisas diferentes e mudam por diferentes razões.
fonte