专栏名称: 聊聊架构
在这里煮酒聊架构。
目录
相关文章推荐
架构师之路  ·  我怎么知道自己适不适合做程序员?(很严肃) ·  3 天前  
架构师之路  ·  如何1分钟实现一个分布式锁?(第26讲) ·  6 天前  
美团技术团队  ·  让迪拜,见证中国配送新速度 ·  1 周前  
美团技术团队  ·  2024年美团机器人研究院学术年会议程公布 ·  1 周前  
51好读  ›  专栏  ›  聊聊架构

Uncle Bob:如何成为一名优秀的软件架构师?

聊聊架构  · 公众号  · 架构  · 2017-02-01 14:17

正文

作者| Uncle Bob
译者| 薛命灯
Robert C. Martin,软件开发大师,设计模式和敏捷开发先驱,敏捷联盟首任主席,C++ Report前主编,被后辈程序员尊称为“Bob大叔”。20世纪7 0年代初成为职业程序员,后创办Object Mentor公司并任总裁。Martin还是一名多产的作家,至今已发表数百篇文章、论文和博客文章。除本书外,还著有《代码整洁之道》《敏捷软件开发:原则、模式和实践》《UML:Java程序员指南》等。他创办了cleancoders.com网站,专为软件开发人员提供教育视频。本文经授权翻译自他的博客,点击文末原文链接查看英文原文。

小明:我要成为一名软件架构师。

Uncle Bob:对一个年轻的工程师来说,这是一个很好的目标。

小明:我要领导一个团队,还要做所有关于数据库、框架和Web服务器的重要决定。

Uncle Bob:好吧,如果是这样,你就没必要成为一名软件架构师了。

小明:当然有必要了!我要成为一个能够做所有重要决定的人。

Uncle Bob:这样很好,只是你没有列出哪些才是重要的决定。你刚才说的那些跟重要的决定没有什么关系。

小明:你说什么?难道数据库不重要?你知道我们在数据库上面花了多少钱吗?

Uncle Bob:可能很多。不过数据库仍然不是最重要的。

小明:你怎么能这么说呢?数据库可是整个系统的心脏啊!所有的数据都保存在这里,它们在这里被排序,被索引,被访问。如果没有数据库,整个系统就无法运作!

Uncle Bob:数据库只不过是一个IO设备,它提供了一些有用的工具对数据进行排序、查询,并生成报表,但这些工具都只是整个系统的附属品。

小明:附属品?真是不可思议。

Uncle Bob:是的,附属品。你的系统业务逻辑或许会用到这些工具,但这些工具并非业务逻辑固有的组成部分。如果有必要,你可以随时替换掉这些工具,但业务逻辑还是那些业务逻辑。

小明:好吧,不过如果把这些工具替换掉,我们就要重新实现业务逻辑了。

Uncle Bob:那是你的问题。

小明:为什么这么说?

Uncle Bob:你认为业务逻辑依赖数据库,但实际上不是这样的。如果你的架构足够好,最起码业务逻辑不应该依赖数据库。

小明:这太疯狂了。我怎么可能创建出不使用这些工具的业务逻辑?

Uncle Bob:我并没有说业务逻辑不要使用数据库工具,我的意思是它们不应该依赖这些工具。业务逻辑不应该知道使用的是哪一种数据库。

小明:如果业务逻辑对数据库一无所知,它怎么使用这些工具呢?

Uncle Bob:依赖反转。你要让数据库依赖业务逻辑,而不是让业务逻辑依赖数据库。

小明:你的话让人费解。

Uncle Bob:费解吗?我讲的可是软件架构。这个就是依赖反转原则,让下层策略来依赖上层策略。

小明:那就更加费解了!既然上层策略(假设你指的是业务逻辑)要调用下层策略(假设你指的是数据库),那么就应该是上层策略依赖下层策略,就像调用者依赖被调用者一样。这是众所周知的!

Uncle Bob:在运行时确实是这样的,但在编译时我们要把依赖反转过来。上层策略的代码不要引用任何下层策略的代码。

小明:拜托!不引用代码就无法调用它们。

Uncle Bob:当然可以调用了。面向对象就可以做到。

小明:面向对象对真实世界进行建模,把数据和函数组合到对象里,把代码组织成直观的结构。

Uncle Bob:这是他们告诉你的吗?

小明:所有人都知道啊,这不是很明显的事情吗?

Uncle Bob:确实如此。不过,面向对象是可以做到不引用也能调用的。

小明:好吧,那它是怎么做到的?

Uncle Bob:你应该知道,在面向对象系统里,对象是会给其他对象发送消息的,对吧?

小明:是的,当然。

Uncle Bob:那么你就该知道,消息发送者是不知道消息接收者是什么类型的。

小明:这要看使用的是哪一种语言了。在Java里,发送者最起码要知道接收者的基本类型。在Ruby里,发送者知道接收者一定会处理它所发送的消息。

Uncle Bob:是的。不过,不管是哪一种情况,发送者都不知道接收者具体的类型。

小明:嗯,是的。

Uncle Bob:所以,发送者可以给接收者传递一个函数,让接收者执行这个函数,这样发送者就不需要知道接收者是什么类型了。

小明:没错。我了解你的意思。不过发送者仍然依赖接收者。

Uncle Bob:在运行时确实是的,但在编译时不是这样的。发送者的代码里并没有引用接收者的代码。实际上,是接收者的代码依赖了发送者的代码。

小明:啊!但发送者仍然会依赖接收者的类。

Uncle Bob:看来需要用代码来说明了,我用Java来写些代码。首先是发送者代码:

package sender;

public class Sender {
  private Receiver receiver;

  public Sender(Receiver r) {
    receiver = r;
  }

  public void doSomething() {
    receiver.receiveThis();
  }

  public interface Receiver {
    void receiveThis();
  }
}

下面是接收者代码:

package receiver;

import sender.Sender;

public class SpecificReceiver implements Sender.Receiver {

  public void receiveThis() {

    //这里会做一些有趣的事情

  }

}

可以看到,接收者代码依赖了发送者代码,也就是说SpecificReceiver依赖了Sender。同时可以看到,发送者代码对接收者代码一无所知。

小明:哈,你作弊了。你把接收者的接口放到了发送者的类里了。

Uncle Bob:你开始明白了。

小明:明白什么?

Uncle Bob:当然是架构原则啊。发送者持有接收者必须实现的接口。

小明:如果这意味着我要使用内部类,那么……

Uncle Bob:使用内部类只是方法之一,还有其他的方法。

小明:请等一下。最开始我们讨论的是数据库,那这些跟数据库又有什么关系呢?

Uncle Bob:让我们来看一下其他代码吧。首先是一个简单的业务逻辑:

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

  }

}

小明:这个业务逻辑没有做什么事情啊。

Uncle Bob:这只是个例子。在实际实现业务逻辑的时候,不会有很多类似这样的类的。

小明:好吧。那么Gateway是用来做什么的呢?

Uncle Bob:它为业务逻辑提供了所有访问数据的方法。下面是它的代码:

import entities.Something;

public interface BusinessRuleGateway {

  Something getSomething(String id);

  void startTransaction();

  void saveSomething(Something thing);

  void endTransaction();

}

要注意,这个接口是在businessRules包里面的。

小明:好吧。那Something这个类又是用来做什么的呢?

Uncle Bob:它代表一个简单的业务对象。我把它放在另一个叫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) {
    // 从MySQL里读取一些数据
  }

  public void startTransaction() {
    // 开始一个事务
  }

  public void saveSomething(Something thing) {
    // 把数据保存到MySQL
  }

  public void endTransaction() {
    // 结束事务
  }
}

可以看到,业务逻辑是在运行时对数据库进行调用的。而在编译时,是database包引用了businessRules包。

小明:好吧,我想我明白了。你用多态性隐藏了数据库实现。不过在业务逻辑里,仍然引用了数据库的工具接口。

Uncle Bob:不,不是这样的。我们并没有打算为业务逻辑提供所有的数据库工具接口,而是业务逻辑创建了它们所需要的接口。在实现这些接口的时候,可以调用相应的工具。

小明:嗯,这样的话,如果业务逻辑需要所有的工具,那么你必须把所有工具都放到Gateway接口里。

Uncle Bob:哈,我觉得你还是没有明白。

小明:不明白什么?我觉得已经很清楚了。

Uncle Bob:每个业务逻辑只定义它所需要的接口。

小明:等等,什么意思?

Uncle Bob:这个叫作接口分离原则。每个业务逻辑只使用一部分数据库工具,所以每个业务逻辑只定义能够满足需要的接口。

小明:这样的话,你就会有很多接口,而且有很多实现类。

Uncle Bob:哈,是的。你开始明白了。

小明:这样子很浪费时间!我为什么要这样做呢?

Uncle Bob:这样做是为了让代码更干净,并且节省时间。

小明:算了吧,这样只会增加更多的代码。

Uncle Bob:非也,这其实是很重要的架构决定,这跟你之前所说的那些所谓的重要决定是不一样的。

小明:什么意思?

Uncle Bob:还记得你刚开始说你要成为一名软件架构师吗?你还想要做所有重要的决定?

小明:是啊,我是这么想过。

Uncle Bob:你想做所有关于数据库、Web服务和框架的决定。

小明:是啊,而你却说它们都不重要,还说它们其实跟重要的决定不相干。

Uncle Bob:没错,它们确实跟重要的决定不相干。一名软件架构师真正要做的重要决定都在数据库、Web服务器和框架之外。

小明:但首先要先决定使用什么数据库、Web服务器或框架啊!

Uncle Bob:不,实际上应该在开发后期才开始做这些事情——在你掌握了更多信息之后。

哀哉,当架构师草率地决定要使用一个数据库,后来却发现使用文件系统效率更高。

哀哉,当架构师草率地决定使用一个Web服务器,后来却发现团队需要的不过是一个Socket接口。

哀哉,当架构师草率地决定使用一个框架,后来却发现框架提供的功能是团队不需要的,反而给团队带来了诸多约束。

幸哉,当架构师在掌握了足够多的信息后才决定该用什么数据库、Web服务器或框架。

幸哉,当架构师为团队鉴别出运行缓慢、耗费资源的IO设备和框架,这样他们就可以构建飞速运行的轻量级测试环境。

幸哉,当架构师把注意力放在那些真正重要的事情上,并把那些不重要的事情放在一边。

小明:我完全不知道你在说什么了。

Uncle Bob:好吧,如果在若干年后你还没有转做管理,你或许会明白的……

今日荐文

点击下方图片即可阅读

大年初一,我们来聊聊过去一年技术的发展与变化