1. 程式人生 > 其它 >小故事:架構師需要做什麼?

小故事:架構師需要做什麼?

作者:Robert C. Martin

原文:A Little Architecture

譯者:孫薇

責編:錢曙光

本文是一篇模仿問答的小故事,作者用幽默的風格簡單分析了架構師要做的工作:

我想要成為一名軟體架構師。

這是年輕軟體開發者很好的選擇。

我想要帶領團隊,並在資料庫與框架、webserver等方面作出重要的決策。

噢,那你根本就不想成為軟體架構師。

我當然想了,我想要成為重要決策的制定者。

那很好,不過你列出的內容中並不包含重要的決策,這些都是不相關的決策。

什麼意思?你是說資料庫並不是重要的決策,你知道我們在上面花了多少錢嗎?

也許花的太多了。但是,資料庫並不是重要的決策之一。

你怎麼能這樣講?資料庫是系統的核心,是進行所有資料系統化、分類、編入索引和存取工作的地方;沒有資料庫的話,就不會有系統。

資料庫只是一個IO裝置,它恰巧為分類、查詢與資訊報告提供了一些有用的工具,但這些都只是系統架構的輔助功能而已。

輔助?這太離譜了。

沒錯,就是輔助。系統的業務規則也許能夠利用其中的一些工具,不過那些工具卻並非相應業務規則所固有的。需要的話,可以用不同的工具來替換現有的這些;而業務規則不會改變。

嗯,沒錯,不過必須重新進行編碼,因為在原本的資料庫中這些工具都用到了。

那是你的問題。

什麼意思?

你的問題在於,你以為業務規則是依賴資料庫工具的,實際上並不是。或者說至少,在提供優秀架構前並不應當是這樣的。

這簡直是瘋了。如何建立不使用那些工具的業務規則呢?

我不是說它們沒使用資料庫的工具,而是說它們並不依賴於此。業務規則無需知道你使用哪個資料庫。

那麼如何在不瞭解使用什麼工具的情況下,獲得業務規則呢?

讓依賴倒置過來,使得資料庫依賴業務規則。確保業務規則不依賴於資料庫。

你在胡言亂語。

恰恰相反,我在使用軟體架構的語言。這是依賴倒置原則:低層準則應當依賴高層準則。

一派胡言!高層準則(假設指的是業務規則)呼叫低層準則(假設指的是資料庫)。因此高層準則會根據呼叫方依賴被呼叫方的原則,而依賴低層準則。這個誰都知道!

在執行時的確如此。不過在編譯時,我們想要的是依賴倒置。高層準則的原始碼應當不提及低層準則的原始碼。

得了吧!怎麼能在不提及的情況下進行呼叫呢?

當然沒問題。這就是面向物件的所涉及的內容。

面向物件是關於真實世界的模型建立,將資料、功能與有凝聚力的物件相結合。是關於將程式碼組織成直觀的結構。

他們是這麼說的?

大家都知道,這是顯而易見的真相。

沒錯,確實如此,然而,在使用面向物件準則時,的確可以在不提及的情況下進行呼叫。

好吧,那要怎麼做?

在面向物件設計中,各個物件會彼此傳送訊息。

沒錯,這是當然的。

而sender在傳送訊息時,並不知道receiver的型別。

這取決於所使用的語言。在Java中,sender至少知道receiver的基礎型別。在Ruby中,sender至少知道receiver能夠處理所收到的訊息。

沒錯。不過在任何情況下,sender都不知道receiver的具體型別。

是這樣,好吧,確實如此。

因此,sender可以在不提及receiver具體型別的情況下,設計receiver執行某個功能。

是這樣,沒錯。我瞭解了。不過sender仍舊依賴於receiver。

在執行時的確如此。不過編譯時則不同。sender的原始碼並不會提及或者依賴receiver的原始碼。事實上receiver的原始碼依賴於sender的原始碼。

不會吧!sender仍依賴於它所傳送的類。

也許從某些原始碼來看,會更清楚一些。下面這段是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的介面放在sender類中了。

你開始懂了。

懂什麼?

當然是架構的原則。Sender擁有receiver必須實現的介面。

如果這意味著我必須使用巢狀類,那麼……

巢狀類只是實現目的的手段之一,還有其他辦法。

好吧,等一下。這又跟資料庫有什麼關係?我們最開始討論的可是資料庫。

再看一點程式碼吧。首先是一個簡單的業務規則:

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之中。

ok,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
  }
}

另外,注意業務規則在執行時呼叫資料庫;不過在編譯時,資料庫會涉及並依賴於businessRules。

好吧,我想我明白了。你只是在利用多型性來隱藏從業務規則實現資料庫的事實。不過仍需要一個介面,向業務規則提供所有的資料庫工具。

不,完全不是這樣。我們沒有嘗試向業務規則提供資料庫工具。而是通過業務規則,為它們所需要的內容建立介面。實現這些介面就能呼叫合適的工具。

是啊,不過如果所有業務規則需要用到每個工具,那麼只需把工具放在gateway介面中。

啊,我看你還是沒明白。

明白什麼?這已經很清楚了。

每個業務規則只為自己所需的資料訪問工具定義一個介面。

等一下,你說什麼?

這就是介面隔離原則(Interface Segregation Principle)。每個業務規則類只用到資料庫的某些設施。因此,每個業務規則提供的介面只能訪問相應的設施。

不過,這意味著需要很多介面,以及很多的小型實現類,它們又會呼叫其他的資料庫類。

很好,你開始理解了。

不過這太亂了,浪費時間。為什麼要這樣做呢?

這樣做能夠條理分明,節省時間。

得了吧,為了程式碼,弄出來一大堆程式碼。

恰恰相反,通過重要的架構決策,可以延緩不相關的決策。

這是什麼意思?

記得最開始,你說想做軟體架構師不是嗎?你想要作出所有真正重要的決策。

是啊,我是這樣想的。

你想要決策的是資料庫、webserver和框架相關的方面,對嗎?

是啊,你說那些都不重要。只是不相關的內容。

沒錯。就是這樣。軟體架構師所作出的重要決策指的是,讓你不對資料庫、webserver和框架進行決策。

不過必須得先決定那些吧!

不用的。事實上,在開發週期中,這些都可以稍後再決定,在資訊更充足的時候再決定。

如果架構師提前確定框架,卻發現框架無法提供所需的效能,或者帶來了無法忍受的約束,這就成了災難。

只有架構師決定推遲決策,待資訊足夠時才作出決策;在架構師的決策下,不使用緩慢而過於耗費資源的IO裝置和框架的團隊,才能建立快速、輕量級的測試環境;只有其架構師關心真正重要的東西,延緩那些不重要的,這樣的團隊才是幸運的團隊。

胡說,我完全不明白你的意思。

好吧,還是好好看一下本文,不然只能再等10年你才能明白了。