原文 《A Little Architecture》 by Robert C. Martin (Uncle Bob)
我想要成为一个软件架构师。
对于年轻的软件开发工程师来说,那是一个好的目标。
我想要领导一个团队,并决定使用什么数据库,框架和 web 服务器还有类似这些很重要的东西。
好吧,那你就不是想要成为一个软件架构师。
是真的!我想要成为做所有重要决定的那个人。
好吧,但是你没有列出那些重要的决定。你提到的都是一些不相关的事情。
什么意思?数据库不是重要的决定?你知道我们每年要在上面要花掉多少钱吗?
可能很多吧。但数据库也不是最重要的决定。
为什么?数据库是整个系统的核心!在那里数据被组织,排序,索引并被访问。没有数据库就没有系统!
数据库仅仅是个 IO 设备。它只是碰巧提供了一些有用的工具用于排序,查询还有报告。但是相对于系统架构这些只是辅助功能(ancillary)。
辅助功能?这太扯了。
是的,只是辅助功能。你系统的业务逻辑(business rules)可能会使用到一些这类工具;但这些工具不是业务逻辑。如果你愿意,你可以使用别的工具替代这些工具;但是你的业务逻辑还是之前那样,没有改变。
好吧,是那样的,但我不得不把那些之前使用数据库的代码重写一遍。
嗯,但那是你的问题。
怎么讲?
你的问题是,你相信业务逻辑依赖数据库这些工具的使用。但不是这样的。至少是如果你提供了一个好的架构是不应该这样的。
我不明白。怎么才能创建一个业务逻辑,不依赖使用到的工具?
我不是说他们不使用数据库的这些工具;我说的是他们不应该依赖这些工具。业务逻辑不应该知道你使用的是哪种数据库。
怎么才能让业务逻辑使用到这些工具但是不知道他们呢?
你可以反转这些依赖关系。让数据库依赖业务逻辑。并确保业务逻辑不依赖数据库。
你在说什么鬼话?
相反的,我说的是软件架构的语言。这就是依赖反转的原则(Dependency Inversion Principle)。低层的策略应该倚赖上层的策略。
在胡说什么!高层的策略(假设是你说的业务逻辑)调用低层策略(假设是你说的数据库)。那么高层的策略依赖低层的策略,就像调用者依赖被调用者。大家都知道这点!
在运行时是这样的。但是在编译时我们想要把依赖反转。高层策略的代码不应该声明低层策略中代码。
哦,得了!你不可能不声明他就调用它。
当然可以。这就是面相对象所要处理的。
面相对象是关于创建真实世界的模型,把数据和方法整合进相关的对象中。以更直观的结构组织代码。
这就是他们告诉你的?
大家都知道。很明显就是这样的。
没问题。当然没问题。而且,使用面相对象的原则你就可以在不声明它的时候调用某些方法。
好吧。怎么做?
你知道使用面相对象设计的对象们相互之间发送消息吗?
是的,当然知道。
并且也知道消息的发送者并不知道接受者的确切类型。
这和语言有关。对于 Java 语言来说发送者至少要知道接收者的基本类型。对于 Ruby 语言来说发送者至少要知道接收者是否可以处理被发送的消息。
是这样的。但是对这两种情况,发送者都不需要知道接收者的确切类型。
对,是这样的。
因为这样,发送者就可以让接收者在内部执行一个方法,而不必要声明接收者的确切类型。
没错。我知道这个。但是发送者仍旧依赖接收者。
在运行时,是这样的。但是在编译时不是这样。发送者的源代码既没有声明也没有依赖接收者的源代码。实际上是接收者的源代码依赖发送者的源代码。
不!发送者仍旧依赖他要发送消息的那个类。
可能看些代码会更清楚些。我会用 Java 实现。首先是 sender 包:
package sender; public class Sender { private Receiver receiver; public Sender(Receiver r) { receiver = r; } public void doSomething() { receiver.receiveThis(); } public interface Receiver { void receiveThis(); } }
接着是 receiver 包。
package receiver; import sender.Sender; public class SpecificReceiver implements Sender.Receiver { public void receiveThis() { //do something interesting. } }
注意下 receiver 包依赖 sender 包。同样的 SpecificReceiver 依赖 Sender。注意下,sender 包一点也不知道 receiver 包。
好吧,但你耍赖了。你把 receiver 的 interface 放到了 sender 的类中。
你开始明白了,年轻人。
明白什么?
架构的原则。发送者包含有接收者必须要实现的接口(interface)。
好吧,要是这意味着我不得不实现一个嵌套类,那么……
嵌套类只是实现的一种方式。还有好多种其他的。
好吧,等等。这和数据库有什么关系。这才是我们最开始讨论的。
让我们再多看些代码。首先是一个简单的业务逻辑:
package businessRules; import entities.Something; public class BusinessRule { private BusinessRuleGateway gateway; public BusinessRule(BusinessRuleGateway gateway) { this.gateway = gateway; } public void execute(String id) { gateway.startTransaction(); Something thing = gateway.getSomething(id); thing.makeChanges(); gateway.saveSomething(thing); gateway.endTransaction(); } }
这个业务逻辑也没做什么啊。
这只是个例子。你可能有很多像这样的类,实现了很多不同的业务逻辑。
好吧,那么那个 Gateway 是干什么的?
它为业务逻辑提供所有的数据访问方法。这是它的实现:
package businessRules; import entities.Something; public interface BusinessRuleGateway { Something getSomething(String id); void startTransaction(); void saveSomething(Something thing); void endTransaction(); }
注意,这个在 businessRules 包中。
好吧。Something 类是什么?
那个代表一个简单的业务对象。我把它放进了一个叫 entities 的包中。
package entities; public class Something { public void makeChanges() { //... } }
最后这个是 BusinessRuleGateway 的实现。这是知道真实的数据库的那个类:
package database; import businessRules.BusinessRuleGateway; import entities.Something; public class MySqlBusinessRuleGateway implements BusinessRuleGateway { public Something getSomething(String id) { // use MySql to get a thing. } public void startTransaction() { // start MySql transaction } public void saveSomething(Something thing) { // save thing in MySql } public void endTransaction() { // end MySql transaction } }
然后,注意这个,在运行时业务逻辑调用数据库;但是在编译时 database 包声明它依赖 businessRules 包。
好吧,好吧,我想我知道了。你只是使用多态从业务逻辑中隐藏了数据库的实现。但是你还是得有一个接口(interface)提供全部的数据库工具给业务逻辑。
对,但也不完全正确。我们不用尝试把全部的数据库工具都提供给业务逻辑。而是,我们让业务逻辑创建他们需要的接口。这些接口的实现可以调用适合的工具。
好吧,但要是全部的业务逻辑需要所有的工具,那么你就不得不把所有的工具都放到 gateway 接口中。
额,我猜你还没明白。
明白什么?我很清楚。
每一条业务逻辑只规定了自己访问数据需要的接口。
等等。什么?
这叫做接口隔离原则(Interface Segregation Principle)。每个业务逻辑的类,只使用了数据库的一部分功能。那么每个业务逻辑只需要定义接口提供自己访问数据所需要的功能。
但那意味着你将要多写好多的接口,还有好多小的实现类调用其他的数据库类。
嗯,很好。我看你开始明白了。
但是那真是一团糟,还浪费时间!我为什么要这么做?
你这么做是为了保持整洁,还有节省时间。
哦,得了。这只是为了写代码而写代码。
相反的,这些是重要的架构决定,可以让你推迟做一些无关紧要的决定(irrelevant decisions)。
什么意思?
还记得你开始说的想要成为一个软件架构师吗?你想要做所有重要的决定?
是的,我是这么想的。
在这些决定中,你想决定的是数据库,web 服务器和框架相关的。
没错,但你说这些不是重要的决定。你说它们是无关紧要的。
没错。就是这样。软件架构师的重要决定就是让你不用决定使用哪种数据库,web 服务器,还有框架。
但是你得先做这些决定啊!
不需要。事实上,你需要在之后的开发循环中再决定 —— 当你有更多的信息。 有一个架构师叫 Woe, 过早的决定使用数据库,但是最终发现使用 flat file 就够用了。 有一个架构师叫 Woe,过早的决定使用 web 服务器,但最终发现团队需要的只是一个简单的 socket 接口。 有一个团队叫 Woe,它们的架构师过早的引入了一个框架,最后发现这个框架提供的功能他们根本就用不到,还引入了一些新的限制。 有一个团队叫 Blessed,他们的架构师提供了一种方法可以在信息明朗之后再做这些决定。 有一个团队叫 Blessed,他们的架构师把他们从低速的,资源不足的 IO 设备还有框架中隔离,这样他们就能创建快速的,轻量级的测试环境。 有一个叫 Blessed 的团队,他们的架构师关注的是真正重要的事情,把那些不重要的事情放到了一边。
尽胡说。我跟本不明白你在说什么。
好吧,可能十年之后你就会明白...... 要是那时你还没转行去做管理