5分鐘了解軟件開發(fā)的20項(xiàng)基本原則

本文詳細(xì)介紹了軟件開發(fā)的20個基本原則,包括抽象握童、封裝姆怪、DRY、KISS澡绩、YAGNI稽揭、LoD、SoC英古、SOLID淀衣、GRASP等,旨在指導(dǎo)軟件開發(fā)人員編寫優(yōu)秀召调、可讀膨桥、可維護(hù)、可擴(kuò)展的代碼唠叛。原文: The 20 Essential Principles of Software Development: LoD, SoC, SOLID, and Beyond.

導(dǎo)言

設(shè)計(jì)原則是軟件開發(fā)的基礎(chǔ)只嚣,作為軟件工程師,可以在工具艺沼、語言册舞、框架、范式和模式中找到這些設(shè)計(jì)原則障般,它們是"優(yōu)秀"调鲸、"可讀"代碼的核心支柱,一旦理解了這些原則挽荡,就可以在任何地方看到藐石。

洞察和應(yīng)用這些基本原則的技能是優(yōu)秀工程師和差勁工程師的區(qū)別所在。如果不了解這些基本原則定拟,任何框架或工具都無法幫助你提高編寫優(yōu)秀代碼的質(zhì)量于微。此外,如果不了解這些基本原則青自,你就會成為工具的人質(zhì)株依。

本文并不是參考指南,而是一份將需要不斷刷新的核心原則系統(tǒng)化的清單延窜。

抽象(Abstraction)

[抽象](https://en.wikipedia.org/wiki/Abstraction_(computer_science)是最重要的一般性原則之一恋腕。抽象意味著只關(guān)注重要部分,而忽略其他細(xì)節(jié)逆瑞。抽象與封裝是相輔相成的吗坚,封裝是一種隱藏被抽象部分的實(shí)現(xiàn)的方法祈远。

在軟件開發(fā)中,可以將其視為定義一種類型商源、接口或函數(shù)簽名,作為工作合約谋减。主要好處是不需要知道實(shí)現(xiàn)細(xì)節(jié)就可以使用某些東西牡彻,因此可以更好的專注于對開發(fā)者來說至關(guān)重要的東西。

這一原則并不局限于應(yīng)用開發(fā)出爹。作為開發(fā)者庄吼,通過語言語法從操作系統(tǒng)的底層操作中抽象出來。反過來严就,操作系統(tǒng)通過 CPU总寻、內(nèi)存、網(wǎng)卡等將語言從底層操作中抽象出來梢为。越深入研究渐行,你就越會明白這只是一個抽象問題。

封裝變化(Encapsulate what varies)

如你所見铸董,抽象可以表現(xiàn)為不同形式--從數(shù)據(jù)(實(shí)現(xiàn))抽象到分層抽象祟印。使用抽象的一般原則是"封裝變化",即確定可能發(fā)生變化的部分粟害,并為其聲明具體接口蕴忆。這樣,即使內(nèi)部邏輯發(fā)生變化悲幅,客戶端仍可進(jìn)行相同的交互套鹅。

假設(shè)我們需要計(jì)算貨幣兌換,目前只有兩種貨幣汰具,可以這樣計(jì)算:

if (baseCurrency == "USD" and targetCurrency == "EUR") return amount * 0.90;
if (baseCurrency == "EUR" and targetCurrency == "USD") return amount * 1.90;

但將來可能會添加另一種貨幣卓鹿,這就需要修改客戶端代碼。與其這樣郁副,還不如將所有邏輯抽象并封裝在一個單獨(dú)的方法中减牺,并在需要時從客戶端調(diào)用該方法。

function convertCurrency(amount, baseCurrency, targetCurrency) {
  if (baseCurrency == "USD" and targetCurrency == "EUR") return amount * 0.90;
  if (baseCurrency == "EUR" and targetCurrency == "USD") return amount * 1.90;
  if (baseCurrency == "USD" and targetCurrency == "UAH") return amount * 38.24;
  …
}

DRY

DRY(don't repeat yourself存谎,不要重復(fù))拔疚,也被稱為 DIE(duplication is evil,重復(fù)是邪惡的)既荚,指的是不應(yīng)該在代碼庫中重復(fù)信息或知識稚失。

"每項(xiàng)知識在系統(tǒng)中都必須有單一、明確恰聘、權(quán)威的表示"--Andy Hunt句各,Dave Thomas吸占,The Pragmatic Programmer。

減少重復(fù)代碼的好處在于更改和維護(hù)的簡便性凿宾。如果你在多個地方重復(fù)相同的邏輯矾屯,發(fā)現(xiàn)錯誤后,很可能會忘記修改其中的某個地方初厚,這將導(dǎo)致看似相同的功能出現(xiàn)不同的行為件蚕。反之,找到重復(fù)功能产禾,將其抽象為過程排作、類等形式,賦予有意義的名字亚情,并在需要的地方使用妄痪。這樣做可以實(shí)現(xiàn)單點(diǎn)更改,最大限度減少對功能的破壞楞件。

KISS

KISS(keep it simple衫生、stupid,保持簡單履因、愚蠢)一詞是由飛機(jī)工程師Kelly Johnson提出的障簿,他向自己的工程團(tuán)隊(duì)提出挑戰(zhàn),要求他們設(shè)計(jì)的噴氣式飛機(jī)必須能夠在實(shí)戰(zhàn)條件下由普通機(jī)械師僅使用特定工具進(jìn)行維修栅迄。

其主要理念是注重系統(tǒng)的簡潔性站故,在只使用真正需要的工具的同時,增加對系統(tǒng)的理解毅舆,減少過度設(shè)計(jì)西篓。

YAGNI

在設(shè)計(jì)解決方案時,需要考慮兩件事:如何使其更好的適應(yīng)當(dāng)前系統(tǒng)憋活,以及如何使其具有可擴(kuò)展性以滿足未來可能的需求岂津。在第二種情況下,為了更好的可擴(kuò)展性而過早構(gòu)建功能的愿望通常是錯誤的:即使你現(xiàn)在認(rèn)為這樣做可以降低集成成本悦即,但這種代碼的維護(hù)和調(diào)試可能并不容易吮成,而且會帶來不必要的復(fù)雜性。這就違反了前面的原則辜梳,增加了解決當(dāng)前問題的冗余復(fù)雜性粱甫。此外別忘了,你所假定的功能很有可能在將來并不需要,而你只是在浪費(fèi)資源。

這就是 YAGNI (You aren’t gonna need it垢夹,你不會需要它)的意義所在掰派。不要誤解乌庶,你應(yīng)該考慮解決方案將來會有什么用途种蝶,但只有在真正需要的時候才添加代碼。

LoD

得墨忒耳定律(LoD瞒大,Law of Demeter)螃征,也稱為最少知識原則,或者"不要與陌生人說話"糠赦。由于 LoD 通常與 OOP 有關(guān)会傲,因此在這種情況下,"陌生人"指的是與當(dāng)前對象沒有直接關(guān)聯(lián)的任何對象拙泽。

使用 LoD 的好處在于可維護(hù)性,具體表現(xiàn)為避免無關(guān)對象之間的直接接觸裸燎。

因此顾瞻,當(dāng)你與某個對象交互時,如果不符合以下情況之一德绿,就違反了這一原則:

  • 當(dāng)對象是某個類的當(dāng)前實(shí)例時(通過 this 訪問)
  • 當(dāng)對象是類的一部分時
  • 當(dāng)對象通過參數(shù)傳遞給方法時
  • 當(dāng)對象在方法中實(shí)例化時
  • 當(dāng)對象在全局范圍可用時

舉個例子荷荤,考慮客戶要向銀行賬戶存款的情況。我們最終可能會有三個類--Wallet(錢包)移稳、Customer(客戶)和Bank(銀行)蕴纳。

class Wallet {
 private decimal balance;

 public decimal getBalance() {
   return balance;
 }

 public void addMoney(decimal amount) {
   balance += amount
 }

 public void withdrawMoney(decimal amount) {
   balance -= amount
 }
}

class Customer {
 public Wallet wallet;

 Customer() {
   wallet = new Wallet();
 }
}

class Bank {
 public void makeDeposit(Customer customer, decimal amount) {
   Wallet customerWallet = customer.wallet;

   if (customerWallet.getBalance() >= amound) {
     customerWallet.withdrawMoney(amount);
     //...
   } else {
     //...
   } 
 }
}

可以在 makeDeposit 方法中看到違反 LoD 的行為。從 LoD 的角度訪問客戶錢包是正確的(盡管從邏輯角度看這是個奇怪的行為)个粱。但在這里古毛,銀行對象調(diào)用了客戶錢包對象的 getBalancewithdrawMoney,因此是在與陌生人(錢包)而不是朋友(客戶)對話都许。

下面是修復(fù)方法:

class Wallet {
 private decimal balance;

 public decimal getBalance() {
   return balance;
 }

 public boolean canWithdraw(decimal amount) {
   return balance >= amount;
 }

 public boolean addMoney(decimal amount) {
   balance += amount
 }

  public boolean withdrawMoney(decimal amount) {
   if (canWithdraw(amount)) {
     balance -= amount;
   }
 }
}

class Customer {
 private Wallet wallet;

 Customer() {
   wallet = new Wallet();
 }

 public boolean makePayment(decimal amount) {
   return wallet.withdrawMoney(amount);
 }
}

class Bank {
 public void makeDeposit(Customer customer, decimal amount) {
   boolean paymentSuccessful = customer.makePayment(amount);

   if (paymentSuccessful) {
     //...
   } else {
     //...
   }   
 }
}

現(xiàn)在稻薇,與客戶錢包的所有交互都通過客戶對象進(jìn)行。這種抽象有利于松散耦合胶征,能更輕松的修改WalletCustomer類內(nèi)部的邏輯(Bank對象不應(yīng)關(guān)心Customer的內(nèi)部實(shí)現(xiàn))以及測試塞椎。

一般來說,當(dāng)一個對象有兩個以上的點(diǎn)時睛低,LoD 就會失敗案狠,比如 object.friend.stranger 而不是 object.friend

SoC

關(guān)注點(diǎn)分離(SoC钱雷,Separation of Concerns)原則建議根據(jù)關(guān)注點(diǎn)的不同將系統(tǒng)分成較小的部分骂铁,這里的"關(guān)注點(diǎn)"指的是系統(tǒng)的某個顯著特征。

例如急波,如果你正在為某個領(lǐng)域建模从铲,那么每個對象都可以被視為一個特殊的關(guān)注點(diǎn)。在分層系統(tǒng)中澄暮,每一層都有自己的關(guān)注點(diǎn)名段。在微服務(wù)架構(gòu)中阱扬,每個服務(wù)都有自己的目的。這個列表可以無限繼續(xù)下去伸辟。

SoC 的主要特點(diǎn)是:

  • 確定系統(tǒng)關(guān)注的問題麻惶;
  • 將系統(tǒng)分為不同部分,獨(dú)立解決這些問題信夫;
  • 通過明確定義的接口連接這些組件窃蹋。

在這種方式下,關(guān)注點(diǎn)分離與抽象原則非常相似静稻。遵循 SoC 原則的結(jié)果是:易于理解警没、模塊化、可重用振湾、基于穩(wěn)定的接口和可測試代碼杀迹。

SOLID

SOLID 原則是Robert Martin提出的五項(xiàng)設(shè)計(jì)原則,旨在明確面向?qū)ο缶幊痰某跏技s束押搪,并使程序更具靈活性和適應(yīng)性树酪。

單一責(zé)任原則(Single responsibility principle)

"類應(yīng)該有且只有一個變更的理由。"

換句話說:

"把因相同原因而變化的事物聚集在一起大州。把因不同原因而變化的事物分開"续语。

和 SoC 非常像,是吧厦画?這兩個原則的區(qū)別在于疮茄,SRP 的目標(biāo)是類級分離,而 SoC 則是一種通用方法苛白,同時適用于高層(如層娃豹、系統(tǒng)、服務(wù))和低層(類购裙、函數(shù)等)抽象懂版。

單一責(zé)任原則具有 SoC 的所有優(yōu)點(diǎn),尤其是促進(jìn)了高內(nèi)聚和低耦合躏率,避免了"上帝對象"的反模式躯畴。

開閉原則(Open-closed principle)

"軟件實(shí)體應(yīng)該可以擴(kuò)展,但不可修改薇芝。"

實(shí)施新功能時蓬抄,應(yīng)避免對現(xiàn)有代碼進(jìn)行破壞性修改。

當(dāng)你可以擴(kuò)展某個類并添加所需修改時夯到,這個類就被認(rèn)為是開放的嚷缭。如果某個類有明確定義的接口,并且將來不會改變,即可以被別的代碼所使用阅爽,那么這個類就被認(rèn)為是封閉的路幸。

試想一個經(jīng)典的 OOP 繼承:你創(chuàng)建一個父類,然后用附加了功能的子類對其進(jìn)行擴(kuò)展付翁。然后出于某種原因简肴,你決定改變父類的內(nèi)部結(jié)構(gòu)(例如,添加一個新字段或刪除某些方法)百侧,而這個結(jié)構(gòu)也可以訪問或直接影響派生類砰识。這樣做就違反了這一原則,因?yàn)楝F(xiàn)在你不僅需要修改父類佣渴,還需要調(diào)整子類以適應(yīng)新的變化辫狼。出現(xiàn)這種情況是因?yàn)闆]有正確應(yīng)用信息隱藏。相反辛润,如果你通過公共屬性或方法給子類定義穩(wěn)定的契約予借,那么只要不影響該契約,就可以自由改變內(nèi)部結(jié)構(gòu)频蛔。

這一原則鼓勵客戶端依賴抽象(如接口或抽象類)而非實(shí)現(xiàn)(具體類)。通過這種方式秦叛,依賴抽象的客戶端被認(rèn)為是封閉的晦溪,但同時又是開放的,因?yàn)樗蟹铣橄蟮男滦薷亩伎梢詿o縫集成到客戶端中挣跋。

再舉一個例子三圆。假設(shè)我們正在開發(fā)折扣計(jì)算邏輯。到目前為止避咆,只有兩種折扣舟肉。在應(yīng)用開閉原則之前:

class DiscountCalculator {
 public double calculateDiscountedPrice(double amount, DiscountType discount) {
   double discountAmount = 15.6;
   double percentage = 4.0;
   double appliedDiscount;

   if (discount == 'fixed') {
     appliedDiscount = amount - discountAmount;
   }

   if (discount == 'percentage') {
     appliedDiscount = amount * (1 - (percentage / 100))  ;
   }

   // logic
 }
}

現(xiàn)在,客戶端(DiscountCalculator)依賴于外部 DiscountType查库。如果添加新的折扣類型路媚,就需要進(jìn)入客戶端邏輯并對其進(jìn)行擴(kuò)展。這是不可取的行為樊销。

在應(yīng)用了開閉原則之后:

interface Discount {
 double applyDiscount(double amount);
}

class FixedDiscount implements Discount {
 private double discountAmount;

 public FixedDiscount(double discountAmount) {
     this.discountAmount = discountAmount;
 }

 public double applyDiscount(double amount) {
     return amount - discountAmount;
 }
}

class PercentageDiscount implements Discount {
 private double percentage;

 public PercentageDiscount(double percentage) {
     this.percentage = percentage;
 }

 public double applyDiscount(double amount) {
     return amount * (1 - (percentage / 100));
 }
}

class DiscountCalculator {
 public double calculateDiscountedPrice(double amount, Discount discount) {
   double appliedDiscount = discount.applyDiscount(amount);
   // logic
 }
}

這樣就可以利用開閉原則和多態(tài)性整慎,而不是添加多個 if 語句來確定某些實(shí)體的類型和未來行為。所有實(shí)現(xiàn) Discount 接口的類在公共 applyDiscount 方法方面都是封閉的围苫,但與此同時裤园,在修改內(nèi)部數(shù)據(jù)時又是開放的。

里氏替代原則(Liskov substitution)

"派生類必須可替代基類剂府。"

或者更正式的表述:

"讓 φ(x) 成為 T 類型對象 x 的可證明屬性拧揽。那么對于 S 類型的對象 y,φ(y) 應(yīng)該為真,其中 S 是 T 的子類型"(Barbara Liskov 和 Jeannette Wing淤袜,1994 年)

簡單來說痒谴,當(dāng)你擴(kuò)展某個類時,不應(yīng)破壞它所建立的契約饮怯。所謂"毀約"闰歪,是指未能滿足以下要求:

  • 不要更改派生類中的參數(shù):子類應(yīng)符合父類的方法簽名,即接受與父類相同的參數(shù)蓖墅,或接受更抽象的參數(shù)库倘。
  • 不要改變派生類的返回類型:子類應(yīng)返回與父類相同的類型,或返回更具體的(子類型)參數(shù)论矾。
  • 不要在派生類中拋出異常:除非父類拋出異常教翩,否則子類不應(yīng)在其方法中拋出異常。這種情況下贪壳,異常類型應(yīng)與父類的異常類型相同或?qū)儆诟割惍惓5淖宇愋汀?/li>
  • 不要在派生類中強(qiáng)化先決條件:子類不應(yīng)通過將其工作限制在某些條件下來改變預(yù)期客戶端的行為饱亿,例如,在父類中闰靴,能夠接受任意長度字符串彪笼,但在子類中,只能接受不超過 100 個字符的字符串蚂且。
  • 不要弱化派生類中的后置條件:子類不應(yīng)改變預(yù)期行為配猫,允許減少某些工作,例如杏死,操作后不清理狀態(tài)泵肄,不關(guān)閉套接字等。
  • 不要削弱派生類中的不變性:子類不應(yīng)改變父類中定義的條件淑翼,例如腐巢,不要重新分配父類的字段,因?yàn)樽宇惪赡軟]有意識到圍繞該字段的全局邏輯概念玄括。

接口隔離(Interface segregation)

"提供客戶端專用的細(xì)粒度接口冯丙。"

任何代碼都不應(yīng)依賴不需要的方法。如果客戶端不使用對象的某些行為惠豺,為什么要強(qiáng)迫它依賴這些行為银还?同樣,如果客戶端不使用某些方法洁墙,為什么要強(qiáng)迫實(shí)現(xiàn)者提供這些功能蛹疯?

將"胖"的接口分解為更具體的接口。如果更改了具體的接口热监,不會影響到無關(guān)的客戶端捺弦。

依賴倒置(Dependency inversion)

"依賴抽象,而非實(shí)現(xiàn)。"

Bob大叔將這一原則描述為嚴(yán)格遵守 OCP 和 LSP:

"在本專欄中列吼,我們將討論 OCP 和 LSP 的結(jié)構(gòu)化定義幽崩。嚴(yán)格使用這些原則所產(chǎn)生的結(jié)構(gòu)本身可以概括為一個原則。我稱之為"依賴倒置原則"(DIP寞钥,The Dependency Inversion Principle)"慌申。- Robert Martin

依賴倒置主要包括兩層概念:

  • 高層模塊不應(yīng)依賴于低層模塊,兩者都應(yīng)依賴于抽象理郑。
  • 抽象不應(yīng)依賴細(xì)節(jié)蹄溉,細(xì)節(jié)應(yīng)該依賴于抽象。

舉例來說您炉,假設(shè)我們正在開發(fā)一個用戶管理服務(wù)柒爵,決定使用 PostgreSQL 作為數(shù)據(jù)持久化。

class UserService {
 private PostgresDriver postgresDriver;

 public UserService(PostgresDriver postgresDriver) {
     this.postgresDriver = postgresDriver;
 }

 public void saveUser(User user) {
   postgresDriver.query("INSERT INTO USER (id, username, email) VALUES (" + user.getId() + ", '" + user.getUsername() + "', '" + user.getEmail() + "')");
 }

 public User getUserById(int id) {
   ResultSet resultSet = postgresDriver.query("SELECT * FROM USER WHERE id = " + id);
   User user = null;
   try {
       if (resultSet.next()) {
           user = new User(resultSet.getInt("id"), resultSet.getString("username"), resultSet.getString("email"));
       }
   } catch (SQLException e) {
       e.printStackTrace();
   }
   return user;
 }

 // ...
}

目前赚爵,UserService 與其依賴關(guān)系(PostgresDriver)緊密耦合棉胀。但后來我們決定遷移到 MongoDB 數(shù)據(jù)庫。由于 MongoDB 與 PostgreSQL 不同冀膝,我們需要重寫 UserService 類中的每個方法唁奢。

解決辦法是引入接口:

interface UserRepository {
 void saveUser(User user);
 User getUserById(int id);
 // ...
}

class UserPGRepository implements UserRepository {
 private PostgresDriver driver;

 public UserPGRepository(PostgresDriver driver) {
   this.driver = driver;
 }

 public void saveUser(User user) {
   // ...
 }

 public User getUserById(int id) {
   // ...
 }
 // ...
}

class UserMongoRepository implements UserRepository {
 private MongoDriver driver;
  public UserPGRepository(MongoDriver driver) {
   this.driver = driver;
 }

 public void saveUser(User user) {
   // ...
 }

 public User getUserById(int id) {
   // ...
 }
 // ...
}

class UserService {
 private UserRepository repository;

 public UserService(UserRepository database) {
     this.repository = database;
 }

 public void saveUser(User user) {
   repository.saveUser(user);
 }

 public User getUserById(int id) {
   return repository.getUserById(id);
 }
 // ...
}

現(xiàn)在,高層模塊(UserService)依賴于抽象模塊(UserRepository)窝剖,而抽象模塊并不依賴于細(xì)節(jié)(PostgreSQL 的 SQL API 和 MongoDB 的 Query API)驮瞧,而只依賴于為客戶端構(gòu)建的接口。

最后提一下枯芬,要實(shí)現(xiàn)依賴反轉(zhuǎn),可以使用依賴注入技術(shù):Demystifying Dependency Injection: An Essential Guide for Software Developers

GRASP

一般責(zé)任分配原則(GRASP采郎,General Responsibility Assignment Principles)是 Craig Larman 在其著作 Applying UML and Patterns 中提出的9項(xiàng)用于面向?qū)ο笤O(shè)計(jì)的原則千所。

與 SOLID 類似,這些原則并不是從零開始建立蒜埋,而是在 OOP 的背景下由久經(jīng)考驗(yàn)的編程準(zhǔn)則組成的淫痰。

高內(nèi)聚(High cohesion)

"將相關(guān)功能和職責(zé)集中在一起。"

高內(nèi)聚原則的重點(diǎn)是保持復(fù)雜度可控整份。在這種情況下待错,內(nèi)聚度是對象的職責(zé)緊密程度。如果一個類的內(nèi)聚度較低烈评,就意味著它在做與其主要目的無關(guān)的工作火俄,或者在做可以委托給其他子系統(tǒng)的工作。

一般來說讲冠,高內(nèi)聚設(shè)計(jì)的類只有少量方法瓜客,而且方法的功能都高度相關(guān)。這樣做可以提高代碼的可維護(hù)性、可讀性和重用性谱仪。

低耦合(Low coupling)

"減少不穩(wěn)定組件之間的聯(lián)系玻熙。"

這一原則旨在降低組件之間的依賴性,從而防止代碼變更帶來副作用疯攒。這里的耦合度是指一個組件對另一個組件的依賴程度(知道或依賴)嗦随。

具有高耦合度的程序組件彼此依賴性非常強(qiáng)。如果有一個耦合度很高的類敬尺,它的變化會導(dǎo)致系統(tǒng)其他部分的局部變化枚尼,反之亦然。這樣的設(shè)計(jì)限制了代碼復(fù)用筷转,并且需要花更多時間來理解姑原。另一方面,低耦合支持設(shè)計(jì)獨(dú)立性更強(qiáng)的類呜舒,從而減少變更帶來的影響锭汛。

耦合和內(nèi)聚原則是相輔相成的。如果兩個類的內(nèi)聚性很高袭蝗,那么它們之間的聯(lián)系通常很弱唤殴。同樣,如果這些類之間的耦合度較低到腥,顧名思義朵逝,它們的內(nèi)聚度也較高。

信息專家(Information expert)

"以數(shù)據(jù)定責(zé)任乡范。"

信息專家模式回答了應(yīng)該如何分配了解某一信息或完成某些工作的責(zé)任這一問題配名。按照這種模式,可以直接獲取所需信息的對象被視為該信息的信息專家晋辆。

還記得在客戶和銀行之間應(yīng)用得墨忒耳定律的例子嗎渠脉?本質(zhì)上是一樣的:

  • Wallet 類是了解余額和管理余額的信息專家。
  • Customer 類是有關(guān)其內(nèi)部結(jié)構(gòu)和行為的信息專家瓶佳。
  • Bank 類是銀行領(lǐng)域的信息專家芋膘。

履行一項(xiàng)職責(zé)往往需要收集系統(tǒng)不同部分的信息。因此霸饲,應(yīng)該有中介信息專家为朋。有了他們,對象就能保存自己的內(nèi)部信息厚脉,從而提高封裝性习寸,降低耦合度。

構(gòu)建者(Creator)

"將構(gòu)建對象的責(zé)任分配給密切相關(guān)的類傻工。"

誰應(yīng)該負(fù)責(zé)構(gòu)建新的對象實(shí)例融涣?根據(jù)構(gòu)建者模式童番,要構(gòu)建 x 類的新實(shí)例,構(gòu)建者類應(yīng)具備以下屬性之一:

  • 聚合 x威鹿;
  • 包含 x剃斧;
  • 記錄 x
  • 密切使用 x忽你;
  • 擁有 x 所需的初始化數(shù)據(jù)幼东。

這一原則促進(jìn)了低耦合,因?yàn)槿绻銥槟硞€對象找到了合適的構(gòu)建者科雳,即一個已經(jīng)與該對象有某種關(guān)聯(lián)的類根蟹,就不會增加彼此之間的關(guān)聯(lián)性。

控制器(Controller)

"將處理系統(tǒng)信息的責(zé)任分配給特定的類糟秘。"

控制器是系統(tǒng)對象简逮,負(fù)責(zé)接收用戶事件并將其委托給領(lǐng)域?qū)印K堑谝粋€從用戶界面接收服務(wù)請求的元素尿赚∩⑹控制器通常用于處理類似的用例,例如 UserController 用于管理用戶實(shí)體交互凌净。

記住悲龟,控制器不應(yīng)從事任何業(yè)務(wù)工作”埃控制器應(yīng)盡可能精簡须教,應(yīng)將工作委托給相應(yīng)的類,而不是自己負(fù)責(zé)斩芭。

例如轻腺,我們可以在類似 MVC 的設(shè)計(jì)模式中找到控制器。MVC 引入了控制器划乖,作為負(fù)責(zé)處理視圖和模型之間交互的中間部分约计,而不是讓模型和視圖直接通信。有了這個組件迁筛,模型就不再受外部交互的影響。

間接(Indirection)

"為了降低耦合耕挨,將責(zé)任分配給中介類细卧。"

Butler Lampson 有一句名言:"計(jì)算機(jī)科學(xué)中的所有問題都可以通過加一個間接層來解決"。

間接原則與依賴反轉(zhuǎn)原則的理念相同:在兩個組件之間引入中介筒占,使它們不產(chǎn)生直接交互贪庙。這樣做的目的是支持弱耦合,以及其他一些優(yōu)點(diǎn)翰苫。

多態(tài)(Polymorphism)

"當(dāng)類似行為因類型不同而不同時止邮,通過多態(tài)將職責(zé)分配給不同行為的類型这橙。"

如果看到使用 if/switch 語句檢查對象類型的代碼,那就說明可能欠缺對多態(tài)性的應(yīng)用导披。當(dāng)需要加入新功能來擴(kuò)展代碼時屈扎,如果必須在條件檢查的地方添加新的 if 語句,說明這是個糟糕的設(shè)計(jì)撩匕。

對具有類似行為的不同類應(yīng)用多態(tài)性原則鹰晨,可以統(tǒng)一不同類型,構(gòu)造可互換的軟件組件止毕,每個組件負(fù)責(zé)特定功能模蜡。根據(jù)語言的不同,可以有多種方法扁凛,但常見的是實(shí)現(xiàn)相同的接口或使用繼承忍疾,特別是為不同對象的方法賦予相同的名稱。最終可獲得可插拔谨朝、易于擴(kuò)展的組件卤妒,并且無需更改無關(guān)代碼。

與開閉原則一樣叠必,正確使用多態(tài)也很重要荚孵。只有在確定某些組件將會或可能發(fā)生變化時,才需要使用多態(tài)纬朝。例如收叶,不需要在語言內(nèi)部類或框架之上創(chuàng)建一個實(shí)現(xiàn)多態(tài)的抽象概念。它們已經(jīng)很穩(wěn)定了共苛,你只是在做無謂的工作判没。

純粹構(gòu)件(Pure Fabrication)

"為了支持高內(nèi)聚,將責(zé)任分配給適當(dāng)?shù)念悺?

有時隅茎,為了遵循高內(nèi)聚/低耦合原則澄峰,需要實(shí)現(xiàn)現(xiàn)實(shí)世界中并不存在的實(shí)體。從這個角度看辟犀,你是在創(chuàng)造一個虛構(gòu)的東西俏竞,一個在領(lǐng)域中并不存在的東西。之所以說它純粹堂竟,是因?yàn)檫@個實(shí)體的職責(zé)設(shè)計(jì)得很清楚魂毁。Craig Larman 建議在信息專家應(yīng)用邏輯錯誤時使用這一原則。

例如出嘹,控制器模式就是一種純粹構(gòu)件席楚,DAO 或存儲庫也是一種構(gòu)件。這些類并不是某些領(lǐng)域獨(dú)有税稼,但卻方便了開發(fā)人員烦秩。雖然我們可以將數(shù)據(jù)訪問邏輯直接放在領(lǐng)域類中(因?yàn)橛羞@方面的專家)垮斯,但這會違反高內(nèi)聚,因?yàn)閿?shù)據(jù)管理邏輯與領(lǐng)域?qū)ο蟮男袨榉绞經(jīng)]有直接關(guān)系只祠。同時因?yàn)樾枰蕾嚁?shù)據(jù)庫接口兜蠕,耦合度也會增加。不同領(lǐng)域?qū)嶓w的數(shù)據(jù)管理邏輯相似铆农,代碼重復(fù)率可能會很高牺氨。換句話說,這會導(dǎo)致在一個地方混合不同的抽象概念墩剖。

使用純粹構(gòu)件類的好處是將相關(guān)行為歸類到對象中猴凹,這在現(xiàn)實(shí)世界中是無可替代的,可以實(shí)現(xiàn)有助于代碼重用的良好設(shè)計(jì)岭皂,并降低對不同職責(zé)的依賴性郊霎。

受控變更(Protected variations)

"通過引入穩(wěn)定的契約來保護(hù)可預(yù)測的變更。"

為了在不破壞其他部分的情況下提供未來的變化爷绘,需要引入穩(wěn)定的契約來阻止不確定的影響书劝。這一原則強(qiáng)調(diào)了前面討論過的在不同對象之間分離責(zé)任的原則的重要性:需要通過間接依賴來輕松的在不同實(shí)現(xiàn)之間切換,需要通過信息專家來決定誰應(yīng)該負(fù)責(zé)滿足需求土至,需要在設(shè)計(jì)系統(tǒng)時考慮到多態(tài)性购对,以引入不同的可插拔解決方案,等等陶因。

受控變更原則是一個核心概念骡苞,驅(qū)動著其他設(shè)計(jì)模式和原則。

"從一個層面上講楷扬,開發(fā)人員或架構(gòu)師的成熟體現(xiàn)在他們對實(shí)現(xiàn)受控變更的更廣泛機(jī)制的了解不斷增加解幽,能夠選擇值得付出努力的合適的受控變更,并有能力選擇合適的受控變更解決方案烘苹。在早期階段躲株,人們學(xué)習(xí)數(shù)據(jù)封裝、接口和多態(tài)性镣衡,這些都是實(shí)現(xiàn)受控變更的核心機(jī)制霜定。之后,人們會學(xué)習(xí)到基于規(guī)則的語言廊鸥、規(guī)則解釋器望浩、反射和元數(shù)據(jù)設(shè)計(jì)、虛擬機(jī)等技術(shù)黍图,所有這些技術(shù)都可以用來防止某些變更"。- Craig Larman奴烙,Applying UML and Patterns助被。

結(jié)論

我相信剖张,在閱讀本文之前,你已經(jīng)在不知不覺中運(yùn)用了其中的某些原則】罚現(xiàn)在搔弄,你知道了更多細(xì)節(jié),溝通起來就更容易了丰滑。

你可能已經(jīng)注意到顾犹,其中某些原則有相同的基本理念。本質(zhì)上來說褒墨,確實(shí)如此炫刷。例如,信息專家郁妈、SoC浑玛、高內(nèi)聚低耦合、SRP噩咪、接口隔離等顾彰,都有相同的觀點(diǎn),即分離不同軟件組件之間的關(guān)注點(diǎn)胃碾。這樣做是為了實(shí)現(xiàn)受控變更涨享、依賴反轉(zhuǎn)和間接依賴,以獲得可維護(hù)仆百、可擴(kuò)展厕隧、可理解和可測試的代碼。

與任何工具一樣儒旬,這只是一個指導(dǎo)方針栏账,而不是嚴(yán)格的規(guī)則。關(guān)鍵是要了解其中的利弊得失栈源,并做出明智的決定挡爵。


你好,我是俞凡甚垦,在Motorola做過研發(fā)茶鹃,現(xiàn)在在Mavenir做技術(shù)工作,對通信艰亮、網(wǎng)絡(luò)闭翩、后端架構(gòu)、云原生迄埃、DevOps疗韵、CICD、區(qū)塊鏈侄非、AI等技術(shù)始終保持著濃厚的興趣蕉汪,平時喜歡閱讀流译、思考,相信持續(xù)學(xué)習(xí)者疤、終身成長福澡,歡迎一起交流學(xué)習(xí)。為了方便大家以后能第一時間看到文章驹马,請朋友們關(guān)注公眾號"DeepNoMind"革砸,并設(shè)個星標(biāo)吧,如果能一鍵三連(轉(zhuǎn)發(fā)糯累、點(diǎn)贊算利、在看),則能給我?guī)砀嗟闹С趾蛣恿芪茫钗页掷m(xù)寫下去笔时,和大家共同成長進(jìn)步!

本文由mdnice多平臺發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仗岸,一起剝皮案震驚了整個濱河市允耿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扒怖,老刑警劉巖较锡,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盗痒,居然都是意外死亡蚂蕴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門俯邓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骡楼,“玉大人,你說我怎么就攤上這事稽鞭∧裾” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵朦蕴,是天一觀的道長篮条。 經(jīng)常有香客問我,道長吩抓,這世上最難降的妖魔是什么涉茧? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮疹娶,結(jié)果婚禮上伴栓,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好钳垮,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布除师。 她就那樣靜靜地躺著,像睡著了一般扔枫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锹安,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天短荐,我揣著相機(jī)與錄音,去河邊找鬼叹哭。 笑死忍宋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的风罩。 我是一名探鬼主播糠排,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼超升!你這毒婦竟也來了入宦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤室琢,失蹤者是張志新(化名)和其女友劉穎乾闰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盈滴,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涯肩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了巢钓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片病苗。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖症汹,靈堂內(nèi)的尸體忽然破棺而出硫朦,到底是詐尸還是另有隱情,我是刑警寧澤烈菌,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布阵幸,位于F島的核電站,受9級特大地震影響芽世,放射性物質(zhì)發(fā)生泄漏挚赊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一济瓢、第九天 我趴在偏房一處隱蔽的房頂上張望荠割。 院中可真熱鬧,春花似錦、人聲如沸蔑鹦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嚎朽。三九已至铺纽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哟忍,已是汗流浹背狡门。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锅很,地道東北人其馏。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像爆安,于是被迫代替她去往敵國和親叛复。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容