Eu tenho um aplicativo da web. Não acredito que a tecnologia seja importante. A estrutura é um aplicativo de camada N, mostrado na imagem à esquerda. Existem 3 camadas.
UI (padrão MVC), Business Logic Layer (BLL) e Data Access Layer (DAL)
O problema que tenho é que meu BLL é enorme, pois possui a lógica e os caminhos através da chamada de eventos do aplicativo.
Um fluxo típico através do aplicativo pode ser:
Evento disparado na interface do usuário, vá para um método na BLL, execute a lógica (possivelmente em várias partes da BLL), eventualmente para o DAL, retorne à BLL (onde provavelmente há mais lógica) e retorne algum valor à interface do usuário.
O BLL neste exemplo está muito ocupado e estou pensando em como dividir isso. Eu também tenho a lógica e os objetos combinados dos quais não gosto.
A versão à direita é o meu esforço.
A lógica ainda é como o aplicativo flui entre a interface do usuário e o DAL, mas provavelmente não há propriedades ... Somente métodos (a maioria das classes nessa camada pode ser estática, pois não armazena nenhum estado). A camada Poco é onde existem classes que possuem propriedades (como uma classe Person, onde haveria nome, idade, altura etc.). Isso não teria nada a ver com o fluxo do aplicativo, eles apenas armazenam o estado.
O fluxo pode ser:
Mesmo acionado a partir da interface do usuário e passa alguns dados para o controlador de camada de interface do usuário (MVC). Isso traduz os dados brutos e os converte no modelo poco. O modelo poco é então passado para a camada lógica (que era o BLL) e, eventualmente, para a camada de consulta de comando, potencialmente manipulada no caminho. A camada de consulta de comando converte o POCO em um objeto de banco de dados (que é quase a mesma coisa, mas uma foi projetada para persistência e a outra para o front end). O item é armazenado e um objeto de banco de dados é retornado à camada de Consulta de Comando. Em seguida, é convertido em um POCO, onde retorna à camada Lógica, potencialmente processado posteriormente e, finalmente, volta à interface do usuário.
A lógica e as interfaces compartilhadas é onde podemos ter dados persistentes, como MaxNumberOf_X e TotalAllowed_X e todas as interfaces.
A lógica / interfaces compartilhadas e o DAL são a "base" da arquitetura. Eles não sabem nada sobre o mundo exterior.
Tudo sabe sobre o poco além da lógica / interfaces compartilhadas e do DAL.
O fluxo ainda é muito semelhante ao primeiro exemplo, mas tornou cada camada mais responsável por uma coisa (seja estado, fluxo ou qualquer outra coisa) ... mas estou rompendo a OOP com essa abordagem?
Um exemplo para demonstrar a lógica e o Poco pode ser:
public class LogicClass
{
private ICommandQueryObject cmdQuery;
public PocoA Method1(PocoB pocoB)
{
return cmdQuery.Save(pocoB);
}
/*This has no state objects, only ways to communicate with other
layers such as the cmdQuery. Everything else is just function
calls to allow flow via the program */
public PocoA Method2(PocoB pocoB)
{
pocoB.UpdateState("world");
return Method1(pocoB);
}
}
public struct PocoX
{
public string DataA {get;set;}
public int DataB {get;set;}
public int DataC {get;set;}
/*This simply returns something that is part of this class.
Everything is self-contained to this class. It doesn't call
trying to directly communicate with databases etc*/
public int GetValue()
{
return DataB * DataC;
}
/*This simply sets something that is part of this class.
Everything is self-contained to this class.
It doesn't call trying to directly communicate with databases etc*/
public void UpdateState(string input)
{
DataA += input;
}
}
fonte
Respostas:
Sim, é muito provável que você esteja quebrando os principais conceitos de OOP. No entanto, não se sinta mal, as pessoas fazem isso o tempo todo, isso não significa que sua arquitetura esteja "errada". Eu diria que é provavelmente menos sustentável do que um design OO adequado, mas isso é bastante subjetivo e, de qualquer maneira, não é sua pergunta. ( Aqui está um artigo meu criticando a arquitetura de várias camadas em geral).
Raciocínio : O conceito mais básico de POO é que dados e lógica formam uma única unidade (um objeto). Embora seja uma declaração muito simplista e mecânica, mesmo assim, ela não é realmente seguida no seu design (se eu entendi corretamente). Você está claramente separando a maioria dos dados da maior parte da lógica. Ter métodos sem estado (como estáticos), por exemplo, é chamado de "procedimentos" e geralmente é antitético ao POO.
É claro que sempre há exceções, mas esse design viola essas coisas como regra.
Mais uma vez, gostaria de enfatizar "viola OOP"! = "Errado", portanto isso não é necessariamente um julgamento de valor. Tudo depende das restrições de sua arquitetura, casos de uso de manutenção, requisitos, etc.
fonte
Um dos princípios fundamentais da programação funcional são as funções puras.
Um dos princípios fundamentais da Programação Orientada a Objetos é reunir funções com os dados em que atuam.
Esses dois princípios básicos desaparecem quando seu aplicativo precisa se comunicar com o mundo exterior. Na verdade, você só pode ser fiel a esses ideais em um espaço especialmente preparado em seu sistema. Nem todas as linhas do seu código devem atender a esses ideais. Mas se nenhuma linha do seu código atender a esses ideais, não será possível afirmar que você está usando OOP ou FP.
Portanto, não há problema em ter apenas "objetos" de dados que você percorre, porque precisa cruzar um limite que você simplesmente não pode refatorar para mover o código interessado. Apenas saiba que não é OOP. Isso é realidade. OOP é quando, uma vez dentro desse limite, você reúne toda a lógica que atua nesses dados em um único local.
Não que você tenha que fazer isso também. OOP não é tudo para todas as pessoas. É o que é. Apenas não afirme que algo segue o OOP quando não acontece ou você vai confundir as pessoas que tentam manter seu código.
Seu POCO parece ter uma lógica de negócios muito bem, então eu não me preocuparia muito em ser anêmico. O que me preocupa é que todos parecem muito mutáveis. Lembre-se de que getters e setters não fornecem encapsulamento real. Se o seu POCO está indo para esse limite, tudo bem. Apenas entenda que isso não está oferecendo todos os benefícios de um objeto OOP encapsulado real. Alguns chamam isso de objeto de transferência de dados ou DTO.
Um truque que usei com sucesso é criar objetos OOP que comem DTOs. Eu uso o DTO como um objeto de parâmetro . Meu construtor lê o estado a partir dele (lido como cópia defensiva ) e o joga de lado. Agora eu tenho uma versão totalmente encapsulada e imutável do DTO. Todos os métodos relacionados a esses dados podem ser movidos para cá, desde que estejam deste lado desse limite.
Eu não forneço getters ou setters. Eu sigo dizer, não pergunte . Você chama meus métodos e eles vão fazer o que precisa ser feito. Eles provavelmente nem lhe dizem o que fizeram. Eles apenas fazem isso.
Agora, eventualmente, algo, em algum lugar, vai esbarrar em outro limite e tudo isso se desfaz novamente. Isso é bom. Gire outro DTO e jogue-o por cima do muro.
Essa é a essência do objetivo da arquitetura de portas e adaptadores. Eu tenho lido sobre isso de uma perspectiva funcional . Talvez também lhe interesse.
fonte
Se eu leio sua explicação corretamente, seus objetos ficam mais ou menos assim: (complicado sem contexto)
Na medida em que suas classes Poco contêm apenas dados e suas classes Logic contêm os métodos que atuam nesses dados; sim, você violou os princípios do "Classic OOP"
Novamente, é difícil dizer a partir de sua descrição generalizada, mas eu arriscaria que o que você escreveu pudesse ser classificado como Modelo de Domínio Anêmico.
Eu não acho que essa seja uma abordagem particularmente ruim, nem, se você considerar o seu Poco como uma estrutura, ele necessariamente quebrará a OOP no sentido mais específico. Agora seus Objetos são agora as LogicClasses. De fato, se você tornar seus Pocos imutáveis, o design poderá ser considerado bastante funcional.
No entanto, quando você faz referência à lógica compartilhada, aos Pocos quase iguais, e à estática, começo a me preocupar com os detalhes do seu design.
fonte
Um problema em potencial que eu vi no seu design (e é muito comum) - alguns dos piores códigos "OO" que eu já encontrei foram causados por uma arquitetura que separava os objetos "Data" dos objetos "Code". Isso é coisa de nível de pesadelo! O problema é que, em qualquer parte do código comercial, quando você deseja acessar seus objetos de dados, você Tende a codificá-lo imediatamente (não é necessário), você pode criar uma classe de utilitário ou outra função para lidar com isso, mas é isso que Eu já vi acontecer repetidamente ao longo do tempo).
O código de acesso / atualização geralmente não é coletado, então você acaba com funcionalidades duplicadas em todos os lugares.
Por outro lado, esses objetos de dados são úteis, por exemplo, como persistência do banco de dados. Eu tentei três soluções:
Copiar valores para dentro e para fora para objetos "reais" e jogar fora seu objeto de dados é entediante (mas pode ser uma solução válida se você quiser seguir esse caminho).
Adicionar métodos de organização de dados aos objetos de dados pode funcionar, mas pode resultar em um grande objeto de dados confuso que está fazendo mais de uma coisa. Também pode dificultar o encapsulamento, já que muitos mecanismos de persistência querem acessadores públicos ... Eu não amei quando o fiz, mas é uma solução válida
A solução que melhor funcionou para mim é o conceito de uma classe "Wrapper" que encapsula a classe "Data" e contém toda a funcionalidade de manipulação de dados - então eu não exponho a classe de dados de maneira alguma (nem mesmo setters e getters a menos que sejam absolutamente necessários). Isso remove a tentação de manipular o objeto diretamente e obriga a adicionar funcionalidades compartilhadas ao wrapper.
A outra vantagem é que você pode garantir que sua classe de dados esteja sempre em um estado válido. Aqui está um exemplo rápido de psuedocode:
Observe que você não tem a verificação de idade espalhada por todo o código em áreas diferentes e também não se sente tentada a usá-la porque não consegue nem descobrir qual é o aniversário (a menos que precise de algo mais, em Nesse caso, você pode adicioná-lo).
Eu tendem a não apenas estender o objeto de dados porque você perde esse encapsulamento e a garantia de segurança - nesse ponto, você também pode adicionar os métodos à classe de dados.
Dessa forma, sua lógica de negócios não possui um monte de junk / iteradores de acesso a dados espalhados por ela, torna-se muito mais legível e menos redundante. Também recomendo adquirir o hábito de agrupar sempre as coleções pelo mesmo motivo - mantendo as construções de loop / pesquisa fora da lógica de negócios e garantindo que elas estejam sempre em bom estado.
fonte
Nunca mude seu código porque você pensa ou alguém lhe diz que não é isso ou aquilo. Altere seu código se houver problemas e você descobriu uma maneira de evitar esses problemas sem criar outros.
Portanto, além de você não gostar das coisas, você quer investir muito tempo para fazer uma mudança. Anote os problemas que você tem agora. Anote como seu novo design resolveria os problemas. Descubra o valor da melhoria e o custo de fazer suas alterações. Então - e isso é mais importante - verifique se você tem tempo para concluir essas alterações, ou você terminará metade nesse estado, metade nesse estado, e essa é a pior situação possível. (Certa vez, trabalhei em um projeto com 13 tipos diferentes de cadeias de caracteres e três esforços identificáveis e semi-arsados para padronizar um tipo)
fonte
A categoria "OOP" é muito maior e mais abstrata do que o que você está descrevendo. Não se importa com tudo isso. Preocupa-se com responsabilidade clara, coesão, acoplamento. Portanto, no nível que você está perguntando, não faz muito sentido perguntar sobre a "prática OOPS".
Dito isto, para o seu exemplo:
Parece-me que há um mal-entendido sobre o que significa MVC. Você está chamando sua interface do usuário de "MVC", separadamente da lógica de negócios e do controle de "back-end". Mas para mim, o MVC inclui todo o aplicativo da web:
Existem algumas suposições básicas extremamente importantes aqui:
Importante: a interface do usuário faz parte do MVC. Não o contrário (como no seu diagrama). Se você aceitar isso, os modelos gordos são realmente muito bons - desde que eles realmente não contenham coisas que não deveriam.
Observe que "modelos de gordura" significa que toda a lógica de negócios está na categoria Modelo (pacote, módulo, seja qual for o nome no idioma de sua escolha). Obviamente, as classes individuais devem ser estruturadas em OOP de acordo com as diretrizes de codificação fornecidas (isto é, algumas linhas de código máximas por classe ou método, etc.).
Observe também que como a camada de dados é implementada tem consequências muito importantes; especialmente se a camada do modelo é capaz de funcionar sem uma camada de dados (por exemplo, para testes de unidade ou para DBs baratos na memória no laptop do desenvolvedor, em vez de DBs caros da Oracle ou o que você tiver). Mas esse é realmente um detalhe de implementação no nível da arquitetura que estamos analisando agora. Obviamente, aqui você ainda deseja fazer uma separação, ou seja, eu não gostaria de ver código que tenha lógica de domínio pura entrelaçada diretamente com o acesso a dados, acoplando-o intensamente. Um tópico para outra pergunta.
Voltando à sua pergunta: Parece-me que há uma grande sobreposição entre sua nova arquitetura e o esquema MVC que descrevi, para que você não esteja completamente errado, mas parece que está reinventando algumas coisas, ou utilizá-lo porque o seu ambiente / bibliotecas de programação atual sugere isso. Difícil de dizer para mim. Portanto, não posso lhe dar uma resposta exata sobre se o que você pretende é particularmente bom ou ruim. Você pode descobrir verificando se cada "coisa" tem exatamente uma classe responsável por isso; se tudo é altamente coeso e com baixo acoplamento. Isso fornece uma boa indicação e, na minha opinião, é suficiente para um bom design de OOP (ou uma boa referência do mesmo, se você desejar).
fonte