好莱坞原则(IoC)

在本文中,我将尝试讨论一种称为控制反转/ IoC的设计原则,也称为好莱坞原则。 我将展示这与取代Barbara Liskovo(LSP)原理以及对私人与受保护的圣战有何贡献。



作为序言,我想谈谈自己。 我是一名经过培训的软件工程师,已经在IT行业工作了10多年,最近我很喜欢写主题专业文章。 其中一些是成功的。 较早前,我在另一资源上发表了文章,不幸的是在俄罗斯无法访问(向Roskomnadzor致意)。 如果有人想了解他们,您就会知道该怎么做。

与往常一样,所有代码示例均在本文中以伪代码形式化为“讨厌的php”的形式提供。

初始任务


为了使它更快并且更易于理解,我们立即继续该示例。 我想从销售部门查看指标:我们每月,每天,每小时能赚多少钱。

我们在三个定期运行的团队的帮助下解决了这个问题:

  • MonthlyReportCommand
  • DailyReportCommand
  • HourlyRerortCommand

我们将需要以下接口:

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); } 

我们编写报告小组(省略了后者,因为对于那些想要理解和练习得很好的人来说,这是一个练习,自己编写):

 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 } 

我们看到在所有情况下,calculateTotals()方法的代码将完全相同。 首先想到的是将重复的代码放在一个通用的抽象类中。 像这样:



 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 } 

computeTotals()方法是我们类内部机制的一部分。 我们谨慎关闭它,因为 外部客户不应调用它-我们不是为此设计的。 我们将此方法声明为受保护的,因为 我们计划在继承人中称呼他-这是我们的目标。 显然,这样的抽象类与类似库的类非常相似-它仅提供一些方法(对于php专家:也就是说,它的工作方式类似于Trait)。

抽象类的秘密


现在该休息一下示例,回顾一下抽象类的用途:

抽象类封装了通用机制,同时允许继承人实现自己的特定行为。

抽象(lat。Abstractio-干扰)是对细节和概括的干扰。 目前,AbstractReportCommand类仅对所有报告进行通用计数。 但是我们可以通过使用好莱坞原理使我们的抽象更加有效,这听起来像是:

“不要给我们打电话,我们会给你自己打电话”

为了了解它是如何工作的,让我们在AbstractReportCommand中加入一个通用的报告机制:



 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 } 

我们做了什么? 抽象类的后代都不适用于通用机制(不要称呼我们)。 取而代之的是,抽象为其继承人提供了一种通用的功能方案,并要求它们仅使用结果来实现特定的行为功能(我们将挑战您)。

但是,承诺的IoC,LSP,私有与受保护的情况又如何呢?


那么控制反转与它有什么关系呢? 这个名字从哪里来? 很简单:首先,我们在最终实现中直接设置调用顺序,控制将执行的操作和时间。 后来,我们将此逻辑转换为通用抽象。 现在,抽象控制将调用什么以及何时调用,而实现则只需遵循这一点。 也就是说,我们反转了控件。

要解决此问题并避免Barbara Liskov替换原理(LSP)出现问题 ,您可以通过在方法声明中包含final来关闭createReport()方法。 毕竟,每个人都知道LSP与继承直接相关。

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

然后,AbstractReportCommand类的所有后代将严格地服从于无法重新定义的单个逻辑。 铁律,秩序,前程。

出于同样的原因,私有保护的优势也变得显而易见。 与通用功能机制相关的所有内容都应连接在抽象类中,并且不可重新定义-私有。 在特殊情况下需要重新定义/实现的所有内容均受抽象保护。 任何方法都是为特定目的而设计的。 而且,如果您不知道为方法设置什么样的范围,则意味着您不知道为什么要创建它。 此设计值得修改。

结论


构造抽象类总是最好使用控制反转,因为 使您可以充分利用抽象的思想。 但是在某些情况下使用抽象类作为库也是合理的。

如果您更广泛地看,那么好莱坞原则与抽象图书馆类之间的小镇对抗就变成了争论:框架(成人IoC)与图书馆。 证明其中哪个更好是没有意义的-每个都是为特定目的而创建的。 唯一重要的是有意识地创建此类结构。

感谢所有从头到尾认真阅读的人-您是我最喜欢的读者。

Source: https://habr.com/ru/post/zh-CN477700/


All Articles