Princípio de Hollywood (IoC)

Neste artigo, tentarei falar sobre o princípio de design chamado Inversion of Control / IoC, também chamado de princípio de Hollywood. Vou mostrar como isso se relaciona com o princípio de substituir Barbara Liskovo (LSP) , além de contribuir para a guerra santa privada versus protegida.



Como prefácio, quero dizer algumas palavras sobre mim. Sou engenheiro de software em treinamento, trabalho na indústria de TI há mais de 10 anos e, recentemente, gosto de escrever artigos profissionais temáticos. Alguns deles foram bem sucedidos. Anteriormente, publiquei em outro recurso, infelizmente, inacessível na Rússia (saudações a Roskomnadzor). Se alguém quiser conhecê-los, você sabe o que fazer.

Todos os exemplos de código, como de costume, são apresentados no artigo com pseudo-código estilizado como "odiado php".

Tarefa inicial


Para torná-lo mais rápido e compreensível, seguimos imediatamente para o exemplo. Do departamento de vendas, eu queria ver as métricas: quanto ganhamos mensalmente, diariamente, a cada hora.

Resolvemos esse problema com a ajuda de três equipes que são executadas periodicamente em um cronograma:

  • MonthlyReportCommand
  • DailyReportCommand
  • HourlyRerortCommand

Vamos precisar das interfaces:

interface ReportCommandInterface { public function createReport(): Money; } interface MoneyRepositoryInterface { /** @return Money[] */ public function getMoney(Period $period): array; } interface MetricRepositoryInterface { public function saveMoneyMetric(Period $period, Money $amount, string $metricType); } 

Escrevemos equipes de relatórios (o último é omitido, como um exercício prático para quem deseja entender e praticar bem, escreva você mesmo):

 class MonthlyReportCommand implements ReportCommandInterface { //lets assume constructor is already here public function createReport(): Money { $period = new Period(new DateTime('first day of previous month'), new DateTime('last day of previous month')); $moneyRecords = $this->moneyRepository->getMoney($period); $amount = $this->calculateTotals($moneyRecords); $this->metricRepository->saveMoneyMetric($period, $amount, 'monthly income'); } /** @param Money[] $moneyRecords */ private function calculateTotals(array $moneyRecords): Money { //here is calculating sum of money records } } class DailyReportCommand implements ReportCommandInterface { //lets assume constructor is already here public function createReport(): Money { $period = new Period(new DateTime('yesterday'), new DateTime('today')); $moneyRecords = $this->moneyRepository->getMoney($period); $amount = $this->calculateTotals($moneyRecords); $this->metricRepository->saveMoneyMetric($period, $amount, 'daily income'); } /** @param Money[] $moneyRecords */ private function calculateTotals(array $moneyRecords): Money { //here calculates sum of money records } } class HourlyReportCommand ... { //the same as previous two but hourly } 

E vemos que o código do método calculTotals () será exatamente o mesmo em todos os casos. A primeira coisa que vem à mente é colocar código duplicado em uma classe abstrata comum. Assim:



 abstract class AbstractReportCommand { protected function calculateTotals(array $moneyRecords): Money { //here calculates sum of money records } } class MonthlyReportCommand extends AbstractReportCommand implements ReportCommandInterface { public function createReport(): Money { //realization is here, calls calculateTotals($moneyRecords) } } class DailyReportCommand extends AbstractReportCommand implements ReportCommandInterface { //the same as previous two but daily } class HourlyReportCommand ... { //the same as previous two but hourly } 

O método calculTotals () faz parte dos mecanismos internos de nossa classe. Nós o fechamos prudentemente, porque não deve ser chamado por clientes externos - não estamos projetando para isso. Declaramos esse método protegido, porque Planejamos chamá-lo de herdeiro - esse é o nosso objetivo. Obviamente, essa classe abstrata é muito semelhante a uma biblioteca - apenas fornece alguns métodos (para especialistas em php: isto é, funciona como Trait).

O segredo das classes abstratas


É hora de dar uma pausa no exemplo e relembrar o objetivo das classes abstratas:

Uma classe abstrata encapsula mecanismos gerais, permitindo ao mesmo tempo que herdeiros implementem seu próprio comportamento particular.

Abstração (lat. Abstractio - distração) é uma distração de detalhes e generalização. No momento, a classe AbstractReportCommand generaliza apenas a contagem de dinheiro para todos os relatórios. Mas podemos tornar nossa abstração mais eficiente usando o princípio de Hollywood, que soa assim:

"Não ligue para nós, nós ligaremos para você"

Para ver como isso funciona, vamos colocar no AbstractReportCommand um mecanismo geral de geração de relatórios:



 abstract class AbstractReportCommand implements ReportCommandInterface { /** @var MoneyRepositoryInterface */ private $moneyRepository; /** @var MetricRepositoryInterface */ private $metricRepository; //lets assume constructor is already here public function createReport(): Money { $period = $this->getPeriod(); $metricType = $this->getMetricType(); $moneyRecords = $this->moneyRepository->getMoney($period); $amount = $this->calculateTotals($moneyRecords); $this->metricRepository->saveMoneyMetric($period, $amount, $metricType); } abstract protected function getPeriod(): Period; abstract protected function getMetricType(): string; private function calculateTotals(array $moneyRecords): Money { //here calculates sum of money records } } class MonthlyReportCommand extends AbstractReportCommand { protected function getPeriod(): Period { return new Period(new DateTime('first day of previous month'), new DateTime('last day of previous month')); } protected function getMetricType(): string { return 'monthly income'; } } class DailyReportCommand extends AbstractReportCommand { protected function getPeriod(): Period { return new Period(new DateTime('yesterday'), new DateTime('today')); } protected function getMetricType(): string { return 'daily income'; } } class HourlyReportCommand ... { //the same as previous two but hourly } 

O que nós fizemos? Nenhum dos descendentes de uma classe abstrata se aplica a mecanismos comuns (não nos chame). Em vez disso, a abstração fornece a seus herdeiros um esquema de funcionamento geral e exige que eles implementem características comportamentais específicas, usando apenas os resultados (nós o desafiaremos).

Mas e o prometido IoC, LSP, privado versus protegido?


Então, o que Inversão de controle tem a ver com isso? De onde vem esse nome? Muito simples: primeiro, definimos a sequência de chamadas diretamente nas implementações finais, controlando o que será feito e quando. E depois, transferimos essa lógica para uma abstração geral. Agora, a abstração controla o que e quando será chamado, e as implementações simplesmente obedecem a isso. Ou seja, invertemos o controle.

Para corrigir esse comportamento e evitar problemas com o princípio de substituição de Barbara Liskov (LSP) , você pode fechar o método createReport () incluindo final na declaração do método. Afinal, todo mundo sabe que o LSP está diretamente relacionado à herança.

 abstract class AbstractReportCommand implements ReportCommandInterface { final public function createReport(): Money { //bla-bla realization } ... } 

Todos os descendentes da classe AbstractReportCommand ficam rigidamente subordinados a uma única lógica que não pode ser redefinida. Disciplina de ferro, ordem, futuro brilhante.

Pela mesma razão, a vantagem do privado sobre o protegido fica aparente. Tudo relacionado a mecanismos gerais de funcionamento deve ser conectado em uma classe abstrata e não acessível à redefinição - privada. Tudo o que precisa ser redefinido / implementado em casos especiais é protegido por resumo. Todos os métodos são projetados para fins específicos. E se você não sabe que tipo de escopo definir para um método, significa que não sabe por que o está criando. Vale a pena revisar esse design.

Conclusões


A construção de classes abstratas é sempre preferível com o uso de Inversão de controle, pois permite que você use a idéia de abstração ao máximo. Mas o uso de classes abstratas como bibliotecas em alguns casos também pode ser justificado.

Se você olhar de maneira mais ampla, nosso confronto em cidade pequena entre o princípio de Hollywood e a classe abstrata da biblioteca se transforma em uma disputa: uma estrutura (IoC adulta) versus uma biblioteca. Não faz sentido provar qual deles é melhor - cada um é criado para um propósito específico. A única coisa importante é a criação consciente de tais estruturas.

Obrigado a todos que leram cuidadosamente do início ao fim - vocês são meus leitores favoritos.

Source: https://habr.com/ru/post/pt477700/


All Articles