Evitando objetos de domínio inchados

12

Estamos tentando mover os dados da camada de serviço inchada para a camada de domínio usando uma abordagem DDD. Atualmente, temos muita lógica de negócios em nossos serviços, que está espalhada por todo o lugar e não se beneficia da herança.

Temos uma classe de domínio central, que é o foco da maior parte do nosso trabalho - um comércio. O objeto Trade saberá como precificar a si próprio, estimar riscos, validar a si próprio etc. Podemos então substituir condicionais por polimorfismo. Por exemplo: o SimpleTrade terá um preço próprio, mas o ComplexTrade terá um preço diferente.

No entanto, estamos preocupados que isso possa inchar as classes Trade. Realmente deve ser responsável por seu próprio processamento, mas o tamanho da classe aumentará exponencialmente à medida que mais recursos forem adicionados.

Portanto, temos opções:

  1. Coloque a lógica de processamento na classe Trade. A lógica de processamento agora é polimórfica com base no tipo de negociação, mas a classe Trade agora tem várias responsabilidades (preço, risco, etc.) e é grande
  2. Coloque a lógica de processamento em outra classe, como TradePricingService. Não é mais polimórfico com a árvore de herança Trade, mas as classes são menores e mais fáceis de testar.

Qual seria a abordagem sugerida?

djcredo
fonte
Não tem problema - fico feliz em aceitar a migração!
1
Cuidado com o contrário: martinfowler.com/bliki/AnemicDomainModel.html
TrueWill
1
"o tamanho da classe aumentará exponencialmente à medida que mais recursos forem adicionados" - qualquer programador deve saber melhor do que usar a palavra "exponencialmente" dessa maneira.
precisa
@Piskvor que é simplesmente estúpido
Arnis Lapsa
@Arnis L .: Obrigado pelo seu comentário. Observe que isso foi, citação, "migrado do stackoverflow.com em 22 de novembro às 22:19", junto com meu comentário. Agora removi meu comentário de que "isso seria melhor para programadores.SE"; agora, você tem algo a acrescentar ou essa foi a única ideia que você queria expressar?
Piskvor deixou o prédio

Respostas:

8

Se você for direcionado ao domínio, considere tratar sua classe Trade como uma raiz agregada e divida suas responsabilidades em outras classes.

Você não deseja terminar com uma subclasse Trade para cada combinação de preço e risco; portanto, um Trade pode conter objetos Price e Risk (composição). Os objetos Preço e Risco fazem os cálculos reais, mas não são visíveis para nenhuma classe, exceto Comércio. Você reduz o tamanho do comércio, sem expor suas novas classes ao mundo exterior.

Tente usar a composição para evitar grandes árvores de herança. Muita herança pode levar a situações em que você tenta calçar um comportamento que realmente não se encaixa no modelo. É melhor colocar essas responsabilidades em uma nova classe.

Terry Wilcox
fonte
Concordo. Pensar nos objetos em termos de comportamentos que formam a solução (Preço, Avaliação de Risco) em vez de tentar modelar o problema evita essas classes monolíticas.
Garrett Salão
Concordo também. Composição, muitas classes menores com específica, responsabilidades individuais, o uso limitado de métodos privados, lotes de interfaces de felizes, etc.
Ian
4

Sua pergunta definitivamente me faz pensar no Padrão de Estratégia . Em seguida, você pode trocar várias estratégias de negociação / preço, semelhantes ao que você está chamando de TradePricingService.

Definitivamente, acho que o conselho que você obterá aqui é usar composição em vez de herança.

Scott Whitlock
fonte
2

Uma solução possível que usei em um caso semelhante é o padrão de design do adaptador (a página referenciada contém muitos códigos de amostra). Possivelmente em combinação com o padrão de design da delegação para facilitar o acesso aos principais métodos.

Basicamente, você divide a funcionalidade do Negociador em várias áreas disjuntas - por exemplo, manipulação de preços, riscos, validação, todas podem ser áreas diferentes. Para cada área, você pode implementar uma hierarquia de classes separada que lida com essa funcionalidade exata nas diferentes variantes necessárias - uma interface comum para cada área. A classe principal do Trader é então reduzida aos dados e referências mais básicos a vários objetos manipuladores, que podem ser construídos quando necessário. Gostar

interface IPriceCalculator {
  double getPrice(ITrader t);
}
interface ITrader {
  IPriceCalculator getPriceCalculator();
}
class Tracer implements ITrader {
  private IPriceCalculator myPriceCalculator = null;
  IPriceCalculator getPriceCalculator() {
    if (myPriceCalculator == null)
      myPriceCalculator = PriceCalculatorFactory.get(this);
    return myPriceCalculator;
  }
}

Uma grande vantagem dessa abordagem é que as combinações possíveis, por exemplo, de preços e ricks, são completamente separadas e, portanto, podem ser combinadas conforme necessário. Isso é bastante difícil com a herança de thread único da maioria das linguagens de programação. A decisão de qual combinação usar pode até ser calculada muito tarde :-)

Normalmente, tento manter as classes do adaptador - por exemplo, as subclasses IPriceCalculatoracima - sem estado. Ou seja, essas classes não devem conter dados locais, se possível, para reduzir o número de instâncias que precisam ser criadas. Por isso, geralmente forneço o principal objeto adaptado como argumento em todos os métodos - como getPrice(ITrader)acima.

Tonny Madsen
fonte
2

não posso dizer muito sobre seu domínio, mas

Temos uma classe de domínio central, que é o foco da maior parte do nosso trabalho - um comércio.

... é um cheiro para mim. Eu provavelmente tentaria esboçar as diferentes responsabilidades da classe e, eventualmente, decompô-la em diferentes agregados. Os agregados seriam então projetados em torno de papéis e / ou pontos de vista das partes interessadas / especialistas do domínio envolvidos. Se Preço e Risco estiverem envolvidos no mesmo comportamento / caso de uso, provavelmente pertencerão ao mesmo agregado. Mas se forem dissociados, talvez pertençam a agregados separados.

Talvez a RiskEvaluation possa ser uma entidade separada em seu domínio, eventualmente com um ciclo de vida específico (não posso dizer realmente, estou apenas especulando ... você conhece seu domínio, não sei), mas a chave é criar conceitos implícitos explícito e para evitar o acoplamento que não é impulsionado pelo comportamento, mas apenas pelo acoplamento de dados herdado.

Em geral, eu pensava no comportamento esperado e nos diferentes ciclos de vida dos componentes envolvidos. Apenas adicionar comportamento sobre os dados agrupados cria objetos inchados. Mas os dados foram agrupados de acordo com um projeto orientado a dados existente, portanto, não há necessidade de manter isso.

ZioBrando
fonte