干净的架构

3 篇文章 0 订阅
2 篇文章 0 订阅

如果您是高级软件工程师,则可以停止阅读。这篇文章不适合你。这篇文章适用于像我这样的普通人,他们编写凌乱的代码并创建意大利面条式的体系结构,但是对构建干净,可维护和适应性强的想法很着迷。

前言

我通常不买计算机书籍,因为它们很快就会过时。此外,我仍然可以在网上找到所有信息。但是,在一年前,我开始阅读Robert Martin的Clean Code。它确实改善了我开发软件的方式,因此当我看到同一作者的另一本书问世时,一本叫做Clean Architecture的书,我很快就学会了。

Clean Code一样,Clean Architecture充满了永恒的原则,无论人们使用哪种语言编写代码,都可以应用它们。如果您在线搜索书籍的标题,就会发现与作者不同意的人。但是,批评他的观点不是我在这里要做的。我只知道Robert Martin(又名Bob叔叔)已经编程了50年,而我还没有。

不过,这本书有点难以理解,因此我将尽力总结和解释重要概念,使普通人可以理解。我仍在以软件架构师的身份成长,因此请认真阅读阅读我编写的所有内容。

什么是干净的架构?

建筑是指项目的总体设计。它是将代码组织成类或文件,组件或模块的组织。这就是所有这些代码组之间相互联系的方式。该体系结构定义了应用程序在哪里执行其核心功能以及该功能如何与数据库和用户界面之类的东西交互。

干净的体系结构是指组织项目,以便随着项目的发展易于理解和更改。这不是偶然发生的。这需要有计划的计划。

干净架构的特征

构建易于维护的大型项目的秘诀是:将文件或类分成可以独立于其他组件进行更改的组件。让我们用几个图像来说明。

 

 

在上图中,如果要用刀替换剪刀,您该怎么办?您必须解开笔,墨水瓶,胶带和指南针的弦。然后,您必须将那些物品绑在刀上。也许这对刀子有效,但是如果笔和胶带说“等等,我们需要剪刀”该怎么办。因此,现在笔和胶带不起作用,必须进行更换,这反过来会影响与它们相连的对象。一团糟。

与此相比:

 

 

现在我们如何更换剪刀?我们只需要从便利贴下面拉出剪刀的绳子,然后添加一条新的绳子即可。更简单。便利贴并不在乎,因为字符串甚至都没有绑定到它。

第二张图代表的体系结构显然更容易更改。只要便利贴不需要经常更改,该系统将非常易于维护。相同的概念是使您的软件易于维护和更改的体系结构。

 

 

内圈是应用程序的域层。这是您放置业务规则的地方。“业务”不一定表示公司。它仅表示您的应用程序执行的本质,代码的核心功能。翻译应用程序翻译。一家在线商店出售产品。这些业务规则往往相当稳定,因为您不太可能更改应用程序经常执行的操作的本质。

外圈是基础设施。它包括UI,数据库,Web API和框架之类的东西。这些事情比领域更可能发生变化。例如,与更改贷款的计算方式相比,您更有可能更改UI按钮的外观。

在域和基础结构之间设置了边界,以使域对基础结构一无所知。这意味着UI和数据库取决于业务规则,但是业务规则不取决于UI或数据库。这使其成为插件架构。用户界面是Web界面,桌面应用程序还是移动应用程序都没有关系。数据是使用SQL还是NoSQL还是存储在云中都没有关系。域不在乎。这使得更改基础架构变得容易。

定义术语

上图中的两个圆圈可以进一步细化。

 

 

在这里,域层又细分为实体用例,而适配器层则构成域和基础结构层之间的边界。这些术语可能会有些混乱。让我们单独看一下。

实体

实体是一组对应用程序功能至关重要的相关业务规则。在面向对象的编程语言中,实体的规则将作为类中的方法分组在一起。即使没有应用程序,这些规则仍然存在。例如,对银行收取10%的利息是银行可能的规则。无论利息是用纸还是计算机计算的,这都是正确的。这是本书中有关实体类外观的一个示例(第191页):

 

 

实体对其他层一无所知。他们不依赖任何东西。也就是说,它们不使用外层中任何其他类或组件的名称。

用例

用例是特定应用程序的业务规则。他们告诉如何使系统自动化。这确定了应用程序的行为。这是用例业务规则书中的一个示例(第192页,有些修改):

    Gather Info for New Loan

    Input:  Name, Address, Birthdate, etc.
    Output: Same info + credit score

    Rules:
      1. Validate name
      2. Validate address, etc.
      3. Get credit score
      4. If credit score < 500 activate Denial
      5. Else create Customer (entity) and activate Loan Estimation

用例与实体交互并依赖实体,但是他们对更远的层次一无所知。他们不在乎它是网页还是iPhone应用程序。他们不在乎数据是存储在云中还是本地SQLite数据库中。

该层定义接口或具有外部层可以使用的抽象类。

转接器

适配器,也称为接口适配器,是域和基础结构之间的转换器。例如,他们从GUI提取输入数据,并以对用例和实体方便的形式将其重新打包。然后,他们从用例和实体中获取输出,并将其重新打包为便于在GUI中显示或保存在数据库中的形式。

基础设施

该层是所有I / O组件所在的位置:UI,数据库,框架,设备等。它是最易变的层。由于该层中的事物很可能会发生变化,因此它们应与更稳定的域层保持尽可能远的距离。因为它们是分开的,所以进行更改或将一个组件交换为另一个组件相对容易。

实施清洁架构的原则

由于以下某些原则的名称令人困惑,因此我在上面的说明中没有故意使用它们。但是,遵循这些原则是您实现我所描述的体系结构设计的方式。如果此部分使您的头旋转,则可以跳至最后的笔记部分。

下面的前五项原则通常缩写为SOLID,以帮助您记住它们。它们是类级别的原则,但是具有类似的对等原则,适用于组件(相关类的组)。组件级原则遵循SOLID原则。

单一责任原则(SRP)

这是SOLID的S。SRP说,一堂课只能有一份工作。它可能有多种方法,但是这些方法都可以共同完成一项主要任务。班级只有一个改变的理由。例如,如果财务部门有一个要求更改班级的要求,而人力资源部门有另一个要求以不同方式更改班级的要求,则有两个原因需要更改。该类应分为两个单独的类,每个类只有一个更改理由。

开放封闭原则(OCP)

这是SOLID的O。开放意味着开放的扩展。封闭表示封闭以进行修改。因此,您应该能够向类或组件添加功能,但是您无需修改​​现有功能。你是怎样做的?您确保每个类或组件都只承担一个责任,然后将更稳定的类隐藏在接口后面,以便在必须更改不太稳定的类时它们不会受到影响。

里斯科夫替代原理(LSP)

这是SOLID的L。我猜他们需要L来拼写SOLID,但是您只需要记住“替换”即可。该原理意味着可以替换较低级别的类或组件,而不会影响较高级别的类和组件的行为。这可以通过实现抽象类或接口来完成。例如,在Java中,ArrayList和LinkedList都实现了List接口,因此可以彼此替换。如果将此原则应用于体系结构级别,则可以用MongoDB替换MySQL,而不会影响域逻辑。

接口隔离原理(ISP)

这是SOLID的I。ISP指使用接口将一个类与其他使用该类的类分开。该接口仅公开依赖类所需的方法子集。这样,当其他方法发生更改时,它们不会影响依赖类。

依赖倒置原则(DIP)

这是SOLID的D。这意味着不太稳定的类和组件应依赖更稳定的类和组件,而不是相反。如果稳定类依赖于不稳定类,那么每当不稳定类发生变化时,也会影响稳定类。因此,依赖的方向需要颠倒。怎么做?通过使用抽象类或将稳定类隐藏在接口后面。

因此,不要像使用稳定类那样使用易失类的名称:

    class StableClass {
        void myMethod(VolatileClass param) {
            param.doSomething();
        }
    }

您可以创建一个由volatile类实现的接口:

    class StableClass {
        interface StableClassInterface {
            void doSomething();
        }
        void myMethod(StableClassInterface param) {
            param.doSomething();
        }
    }

    class VolatileClass implements StableClass.StableClassInterface {
        @Override
        public void doSomething() {
        }
    }

这使依赖关系方向反转。volatile类知道稳定类的名称,但是稳定类对volatile类一无所知。

使用抽象工厂模式是实现此目的的另一种方法。

重用/发布等效原则(REP)

REP是组件级原则。重用是指一组可重用的类或模块。发布是指使用版本号发布它。这个原则说,无论您发布什么内容,都可以作为内聚单元重用。它不应该是不相关类的随机集合。

通用闭包原则(CCP)

CCP是组件级原则。它说组件应该是同时出于相同原因而改变的类的集合。如果有不同的更改原因或类别以不同的速率更改,则应拆分组件。这基本上与上面的“单一责任原则”相同。

通用重用原则(CRP)

CRP是组件级原则。它说,您不应该依赖具有不需要类的组件。这些组件应该分开,以使用户不必依赖于他们不使用的类。这基本上与上面的接口隔离原理相同。

这三个原则(REP,CCP和CRP)相互矛盾。拆分过多或分组过多都会导致问题。需要根据情况平衡这些原则。

非循环依赖原则(ADP)

ADP意味着您的项目中不应有任何依赖周期。例如,如果组件A依赖于组件B,并且组件B依赖于组件C,并且组件C依赖于组件A,则您将具有一个依赖周期。

 

 

尝试对系统进行更改时,具有这样的周期会产生重大问题。打破这种循环的一种解决方案是使用依赖性反转原理,并在组件之间添加接口。如果不同的个人或团队对不同的组件负责,则应使用自己的版本号单独发布组件。这样一来,一个组成部分的变更就无需立即影响其他团队。

稳定依赖原则(SDP)

这个原则说,依赖关系应该朝着稳定的方向发展。也就是说,稳定性较差的组件应依赖于稳定性更强的组件。这使更改的影响最小化。某些组件是易失的。可以,但是您不应该使稳定的组件依赖于它们。

稳定抽象原理(SAP)

SAP说,组件越稳定,它应该越抽象,也就是说,它应该包含的抽象类越多。抽象类更易于扩展,因此可以防止稳定组件变得过于僵化。

最后的笔记

上面的内容总结了《清洁建筑》一书的主要原理,但是我想补充一些其他要点。

测试中

创建插件架构的好处是使您的代码更具可测试性。当有很多依赖项时,很难测试您的代码。但是,当您具有插件体系结构时,只需用模拟对象替换数据库依赖项(或任何组件)就很容易。

我一直很痛苦地测试UI。我进行了遍历GUI的测试,但对UI进行更改后,测试即告中断。所以我最后只是删除测试。但是,我了解到应该在适配器层中创建一个Presenter对象。演示者将获取业务规则的输出,并根据UI视图的需要格式化所有内容。然后,UI视图对象除了显示Presenter提供的预格式化数据外,什么也不做。通过此设置,您可以独立于UI测试Presenter代码。

创建一个特殊的测试API来测试业务规则。它应该与接口适配器分开,以便在应用程序的结构发生更改时测试不会中断。

按用例划分组件

如何编写 干净的架构

我讨论了上面的域和基础结构层。如果将它们视为水平层,则可以根据应用程序可能具有的不同用例将它们垂直切成组件组。这就像一个分层的蛋糕,其中每个切片都是一个用例,而切片中的每个层都是一个组件。

 

 

例如,在视频站点上,一种用例是观看者观看视频。因此,您有一个ViewerUseCase组件,一个ViewerPresenter组件,一个ViewerView组件,等等。另一个用例是将视频上传到站点的发布者。对于他们,您将拥有PublisherUseCase组件,PublisherPresenter组件,PublisherView组件等。其他用例可能是针对站点管理员的。通过这种方式,可以通过垂直切片水平层来创建各个组件。

部署应用程序后,可以按照最合理的方式对组件进行分组。

强制分层

您可能拥有世界上最好的体系结构,但是如果有新的开发人员出现并添加了一个绕过您的界限的依赖项,那么这将完全达不到目的。防止这种情况的最好方法是使用编译器来帮助您保护体系结构。例如,在Java中,您可以将类包设为私有,以便将其隐藏在不了解它们的模块中。另一种选择是使用第三方软件,该软件将帮助您检查是否有任何东西正在使用它不应该使用的东西。

仅在需要时增加复杂性

从一开始就不要过度设计系统。当时只使用所需的架构。但是在您的体系结构中,维护着边界,这些边界使组件更容易在将来出现。例如,首先,您可以从外部部署整体应用程序,但在类内部保持适当的边界。稍后,您可以将它们分解为单独的模块。再后来,您可以将它们部署为服务。只要沿途维护层和边界,就可以自由调整它们的部署方式。这样,您就不会造成不必要的复杂性,而这种复杂性永远不会被使用。

细节

启动项目时,您应该首先处理业务规则。其他所有东西都是细节。数据库是一个细节。用户界面是一个细节。操作系统是一个细节。Web API是一个细节。框架是一个细节。尽可能长时间不做决定。这样,当您需要它们时,您将处于更好的位置来做出明智的选择。这对您的最初开发并不重要,因为域层对基础架构一无所知。准备好选择数据库时,请填写数据库适配器代码并将其插入。准备好UI时,请填写UI适配器代码,然后将其插入。

最后一点建议

  • 不要将您的Entity对象用作在外部层中传递的数据结构。为此,请创建单独的数据模型对象。
  • 您的项目的顶层组织应该清楚地告诉人们您的项目是什么。这称为尖叫架构。
  • 下车,开始将这些课程付诸实践。只有使用原理,您才能真正学习它们。

练习:制作依赖图

打开您当前的项目之一,并在一张纸上制作一个依赖图。为项目中的每个组件或类绘制一个框。然后遍历每个班级,看看该班级所依赖的内容。任何命名的类都是依赖项。从要检查的类的框到命名的类或组件的框画一个箭头。

完成所有课程后,请考虑以下问题:

  • 您的业​​务规则在哪里(实体和用例)?
  • 您的业​​务规则是否还取决于其他任何内容?
  • 如果必须使用其他数据库,它将影响多少个类或组件?UI平台?框架?
  • 有依赖周期吗?
  • 您需要做什么重构才能创建插件架构?

结论

清洁架构书的本质是您需要创建一个插件架构。出于相同原因可能同时更改的类应归为一组。业务规则组件更加稳定,并且对于易变的基础架构组件一无所知,这些组件处理UI,数据库,Web,框架和其他详细信息。组件层之间的边界是通过使用接口适配器来维护的,该接口适配器在各层之间转换数据,并使相关性指向更稳定的内部组件。

我学到了很多东西。我希望你也有。如果我在任何时候都歪曲了这本书,请告诉我。您可以在GitHub个人资料上找到我的联系信息。

进一步研究

我已尽力全面总结了Clean Architecture,但您会在本书中找到更多信息。值得您花时间阅读它。实际上,我推荐罗伯特·马丁(Robert Martin)撰写的以下三本书。我会在亚马逊上链接到它们,但是如果您购买二手的书,您可能会发现它们更便宜。我按照建议阅读的顺序列出了它们。这些书不会很快过时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gleason.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值