![](https://i-blog.csdnimg.cn/blog_migrate/3e3edd51726102d0127fe6f0963d54c3.png)
注意:我最初在Toptal博客上发布了这篇文章。
开发软件很棒,但是……我想我们都可以同意,这可能有点令人激动。 一开始,一切都很棒。 您可以在几天甚至几天内一次又一次地添加新功能。 你滚!
快进几个月,您的开发速度会下降。 是因为您没有像以前那样努力吗? 并不是的。 让我们再快几个月,您的开发速度会进一步下降。 从事这个项目不再是一件有趣的事,并且已经成为一件麻烦事。
![](https://i-blog.csdnimg.cn/blog_migrate/a393d0c932d2316d653d9dac70e38776.png)
情况变得更糟。 您开始发现应用程序中的多个错误。 通常,解决一个错误会创建两个新错误。 此时,您可以开始唱歌:
代码中的99个小错误。
99个小虫子。
取下来,修补一下,
…代码中的127个小错误。
您现在对这个项目的工作感觉如何? 如果你像我,你可能会开始失去动力。 开发此应用程序很痛苦,因为对现有代码的每次更改都可能带来不可预测的后果。
这种经验在软件界很普遍,可以解释为什么这么多的程序员想要扔掉他们的源代码并重写所有内容。
软件开发随着时间的推移而变慢的原因
那么,这个问题的原因是什么呢?
主要原因是复杂性增加。 根据我的经验,整体复杂性的最大贡献者是这样一个事实,即在绝大多数软件项目中,所有事物都是相互联系的。 由于每个类都具有依赖性,因此,如果您更改了该类中发送电子邮件的代码,则您的用户突然无法注册。 这是为什么? 因为您的注册代码取决于发送电子邮件的代码。 现在,如果不引入错误,您将无法进行任何更改。 根本不可能跟踪所有依赖项。
所以你有它; 问题的真正原因是由于代码所具有的所有依赖关系而增加了复杂性。
大泥球及其减少方法
有趣的是,这个问题已经存在多年了。 这是一种常见的反模式,称为“大泥巴”。 多年来,我在多个不同公司从事的几乎所有项目中都看到过这种架构。
那么这个反模式到底是什么呢? 简而言之,当每个元素与其他元素有依赖关系时,您将陷入困境。 在下面,您可以看到著名的开源项目Apache Hadoop的依赖关系图。 为了可视化大泥球(或更确切地说,大纱球),您绘制了一个圆并将项目中的类均匀地放置在其上。 只需在彼此依赖的每对类之间画一条线。 现在,您可以看到问题的根源。
![](https://i-blog.csdnimg.cn/blog_migrate/6cff68d5101ccee0b59c054c709f2d2d.png)
模块化代码的解决方案
所以我问自己一个问题:是否可以降低复杂性,并且仍然像项目开始时一样开心? 说实话,您无法消除所有复杂性。 如果要添加新功能,则始终必须提高代码复杂性。 但是,复杂性可以移动和分离。
其他行业如何解决此问题
考虑一下机械行业。 当一些小型机械制造厂制造机器时,他们购买一组标准元素,创建一些自定义元素,然后将它们组合在一起。 他们可以完全独立地制造这些组件,并在最后组装所有组件,而仅需进行一些调整。 这怎么可能? 他们知道如何通过固定的行业标准(例如螺栓尺寸)以及预先决定(例如安装孔的尺寸和它们之间的距离)将每个元素组合在一起。
![](https://i-blog.csdnimg.cn/blog_migrate/43fa8e38f5a2725f67ef18b6ac42aa53.png)
上面组装中的每个元素都可以由一个单独的公司提供,该公司完全不了解最终产品或其其他零件。 只要每个模块化元素都是根据规格制造的,您就可以按计划创建最终设备。
我们可以在软件行业复制吗?
我们当然可以! 通过使用接口和反转控制原理; 最好的部分是,这种方法可以在任何面向对象的语言中使用:Java,C#,Swift,TypeScript,JavaScript,PHP —清单不胜枚举。 您不需要任何精美的框架即可应用此方法。 您只需要遵守一些简单的规则并保持纪律。
控制反转是你的朋友
当我第一次听说控制反转时,我立即意识到找到了解决方案。 这是获取现有依赖关系并使用接口对其进行反转的概念。 接口是方法的简单声明。 他们没有提供任何具体的实现。 结果,它们可以用作两个元素之间关于如何连接它们的协议。 如果愿意,它们可以用作模块化连接器。 只要一个元素提供接口,另一个元素提供接口的实现,它们就可以一起工作,而彼此之间一无所知。 这个棒极了。
让我们看一个简单的示例,如何将系统解耦以创建模块化代码。 下图已实现为简单的Java应用程序。 您可以在此GitHub存储库中找到它们。
问题
假设我们有一个非常简单的应用程序,仅由Main
类,三个服务和一个Util
类组成。 这些元素以多种方式相互依赖。 在下面,您可以看到使用“大泥巴”方法的实现。 类之间相互简单地调用。 它们紧密耦合,您不能简单地取出一个要素而不接触其他要素。 使用此样式创建的应用程序使您可以快速成长。 我相信这种风格适用于概念验证项目,因为您可以轻松地玩转事物。 但是,它不适合用于生产就绪的解决方案,因为即使维护也很危险,而且任何单个更改都可能产生无法预测的错误。 下图显示了这种泥泞的架构。
![](https://i-blog.csdnimg.cn/blog_migrate/3ee9d05fd9edbfd30ebaab1d8ad9441d.png)
为什么依赖注入完全错误
为了寻求更好的方法,我们可以使用一种称为依赖注入的技术。 该方法假定所有组件都应通过接口使用。 我读过一些声称它使元素分离的说法,但是确实如此吗? 不,请看下面的图表。
![](https://i-blog.csdnimg.cn/blog_migrate/5d3b64bd03dde9cea64a8f729f6031ef.png)
当前情况和一个大麻烦之间的唯一区别是,现在,我们通过它们的接口而不是直接调用类,而不是直接调用类。 它稍微改善了彼此分离的元素。 例如,如果您想在其他项目中重用Service A
,则可以通过取出Service A
本身以及Interface A
, Interface B
和Interface Util
。 如您所见, Service A
仍然取决于其他元素。 结果,我们仍然遇到在一个地方更改代码而在另一个地方弄乱行为的问题。 它仍然产生一个问题,即如果您修改Service B
和Interface B
,则需要更改所有依赖于它的元素。 这种方法并不能解决任何问题。 在我看来,它只是在元素之上添加了一层接口。 您永远都不应注入任何依赖关系,而应该一劳永逸地摆脱它们。 为独立而欢呼!
模块化代码的解决方案
我认为解决所有主要依赖问题的方法是完全不使用依赖关系。 您创建一个组件及其侦听器。 侦听器是一个简单的界面。 每当需要从当前元素外部调用方法时,只需将方法添加到侦听器并改为调用它即可。 该元素仅允许使用文件,其包内的调用方法以及使用主框架或其他使用的库提供的类。 在下面,您可以看到将应用程序修改为使用元素架构的图表。
![](https://i-blog.csdnimg.cn/blog_migrate/8866c7ee6709b2b26e76173c1557fc02.png)
请注意,在这种体系结构中,只有Main
类具有多个依赖关系。 它将所有元素连接在一起,并封装了应用程序的业务逻辑。
另一方面,服务是完全独立的元素。 现在,您可以从该应用程序中取出每个服务,然后在其他地方重用它们。 他们不依赖其他任何东西。 但是,等待,它会变得更好:只要您不更改服务的行为,就无需再次修改这些服务。 只要这些服务按其应有的方式工作,就可以一直使用它们,直到时间结束。 它们可以由专业的软件工程师创建,也可以是首次编写的编码器,它折衷到任何有混合goto
语句的人都煮过的最糟糕的意大利面条代码。这没关系,因为它们的逻辑是封装的。 尽管可能如此可怕,但它永远不会扩散到其他阶层。 这也使您能够在多个开发人员之间拆分项目中的工作,每个开发人员都可以独立地在自己的组件上工作,而无需打扰另一个开发人员甚至不知道其他开发人员的存在。
最后,您可以再开始编写一次独立的代码,就像在上一个项目开始时一样。
元素图案
让我们定义结构元素模式,以便我们能够以可重复的方式创建它。
元素的最简单版本由两部分组成:主元素类和侦听器。 如果要使用元素,则需要实现侦听器并调用主类。 这是最简单的配置图:
![](https://i-blog.csdnimg.cn/blog_migrate/1c621a68578ea2e35b74f15d2d9fdbe9.png)
显然,最终您将需要为元素添加更多的复杂性,但是您可以轻松地做到这一点。 只要确保您的逻辑类都不依赖项目中的其他文件即可。 他们只能在此元素中使用主框架,导入的库和其他文件。 当涉及诸如图像,视图,声音等资产文件时,它们也应该封装在元素中,以便将来易于重用。 您只需将整个文件夹复制到另一个项目中就可以了!
在下面,您可以看到显示更高级元素的示例图。 请注意,它包含正在使用的视图,并且不依赖于其他任何应用程序文件。 如果您想知道一种检查依赖关系的简单方法,只需查看导入部分。 当前元素之外是否有文件? 如果是这样,则需要通过将这些依赖关系移到元素中或通过向侦听器添加适当的调用来删除这些依赖关系。
![](https://i-blog.csdnimg.cn/blog_migrate/30d387faf72fa2238849dc03db6cc54a.png)
让我们看一个用Java创建的简单“ Hello World”示例。
public class Main {
interface ElementListener {
void printOutput(String message);
}
static class Element {
private ElementListener listener;
public Element(ElementListener listener) {
this.listener = listener;
}
public void sayHello() {
String message = "Hello World of Elements!";
this.listener.printOutput(message);
}
}
static class App {
public App() {
}
public void start() {
// Build listener
ElementListener elementListener = message -> System.out.println(message);
// Assemble element
Element element = new Element(elementListener);
element.sayHello();
}
}
public static void main(String[] args) {
App app = new App();
app.start();
}
}
最初,我们定义ElementListener
来指定打印输出的方法。 元素本身在下面定义。 在元素上调用sayHello
时,它仅使用ElementListener
打印一条消息。 注意,该元素完全独立于printOutput
方法的实现。 可以将其打印到控制台,物理打印机或精美的UI中。 该元素不依赖于该实现。 由于这种抽象,该元素可以轻松地在不同的应用程序中重用。
现在看看主要的App
类。 它实现了侦听器,并将元素与具体实现组合在一起。 现在我们可以开始使用它了。
元素架构
让我们看一下在大型应用程序中使用元素模式。 在一个小项目中展示它是一回事,将它应用于现实世界是另一回事。
我喜欢使用的全栈Web应用程序的结构如下:
src
├── client
│ ├── app
│ └── elements
│
└── server
├── app
└── elements
在源代码文件夹中,我们最初将客户端文件和服务器文件分开。 这样做是合理的,因为它们在两种不同的环境中运行:浏览器和后端服务器。
然后,我们将每一层中的代码分成名为app和elements的文件夹。 元素由具有独立组件的文件夹组成,而app文件夹将所有元素连接在一起并存储所有业务逻辑。
这样,可以在不同的项目之间重用元素,而所有特定于应用程序的复杂性都封装在一个文件夹中,并且通常可以简化为对元素的简单调用。
动手实例
相信实践总是胜过理论,让我们看一下在Node.js和TypeScript中创建的真实示例。
这是一个非常简单的Web应用程序,可以用作更高级解决方案的起点。 它的确遵循元素架构,并且使用了广泛的结构元素模式。
从高亮显示,您可以看到主页已被区分为一个元素。 此页面包含其自己的视图。 因此,例如,当您要重用它时,您可以简单地复制整个文件夹并将其放入另一个项目中。 只需将所有内容连接在一起,就可以设置好。
这是一个基本示例,说明您可以立即在自己的应用程序中引入元素。 您可以开始区分独立的组件并分离其逻辑。 您当前正在处理的代码有多混乱都没有关系。
开发更快,更经常重复使用!
我希望,通过这套新工具,您将能够更轻松地开发可维护性更高的代码。 在实际使用元素模式之前,让我们快速回顾一下所有要点:
- 由于多个组件之间的依赖关系,导致软件中出现许多问题。
- 通过在一处进行更改,您可以在其他地方引入不可预测的行为。
三种常见的体系结构方法是:
- 大泥球。 这对于快速发展非常有用,但对于稳定生产而言却不是那么重要。
- 依赖注入。 您应该避免这种半熟的解决方案。
- 元素架构。 该解决方案使您可以创建独立的组件,并在其他项目中重复使用它们。 它具有可维护性和出色的性能,可以稳定地发布产品。
基本元素模式包括一个具有所有主要方法的主类以及一个侦听器,该侦听器是一个简单的接口,可以与外部世界进行通信。
为了实现全栈元素体系结构,首先需要将前端代码与后端代码分开。 然后在每个应用程序和元素中创建一个文件夹。 elements文件夹由所有独立元素组成,而app文件夹则将所有内容连接在一起。
现在,您可以开始创建和共享自己的元素。 从长远来看,它将帮助您创建易于维护的产品。 祝你好运,让我知道你创造了什么!
From: https://hackernoon.com/creating-truly-modular-code-with-no-dependencies-16f8f784d4a6