作者: Tom哥
簡介:計算機研究生,校招進阿里,期間還拿過百度、華為、中興、騰訊等6家大廠offer,P7 技術專家,出過專利,CSDN博客專家,
公眾號:微觀技術,分享其他地方看不到的知識與思考,歡迎關注
面對復雜的業務場景,千變萬化的客戶需求,如何以一變應萬變,以最小的開發成本快速落地實作,同時保證系統有著較低的復雜度,能夠保證系統后續de持續迭代能力,讓系統擁有較高的可擴展性,
這些是一個合格的架構師必須修煉的基礎內功,但是如何修煉這門神功???

不要著急,慢慢看下去,學了真本事,拿了阿里、頭條的offer,女神還會遠嗎!??💖💘
接下來我們來系統性匯總下,軟體架構設計需要知曉的設計模式,主要是提煉精髓、核心設計思路、代碼示例、以及應用場景等,
CRUD很多人都會,不懂設計模式也可以開發軟體,但是當開發及維護大型軟體系統程序中就痛苦不堪,懂了人自然聽得懂我在說什么,不懂的人說了你也不會懂,
我將常用的軟體設計模式,做了匯總,目錄如下:

考慮到內容篇幅較大,為了便于大家閱讀,將軟體設計模式系列(共23個)拆分成四篇文章,每篇文章講解六個設計模式,采用不同的顏色區分,便于快速消化記憶
本文是首篇,主要講解單例模式、建造者模式、抽象工廠、工廠方法、原型模式、配接器模式,共6個設計模式,
1、單例模式
定義:
單例模式(Singleton)允許存在一個和僅存在一個給定類的實體,它提供一種機制讓任何物體都可以訪問該實體,

核心思路:
1?? 保證一個類只有一個實體,如果該物件已經被創建, 則回傳已有的物件,為什么要這樣設計呢?因為某些業務場景要控制共享資源 (例如資料庫或檔案) 的訪問權限,
2?? 為該實體提供一個全域訪問入口, 提供一個static訪問方法,
代碼示例:
/**
* @author 微信公眾號:微觀技術
*/
public class Singleton {
private static Singleton instance = new Singleton();
// 讓建構式為 private,這樣該類就不會被實體化
private Singleton() {}
// 獲取唯一可用的物件
public static Singleton getInstance() {
return instance;
}
}
在類中添加一個私有靜態成員變數用于保存單例實體,宣告一個公有靜態構建方法用于獲取單例實體,
注意事項:
多個業務場景,多個執行緒訪問同一個類實體的全域變數,頻發的寫操作,可能會引發執行緒安全問題,另外,為了防止其他物件使用單例類的 new 運算子,編碼時需要將默認建構式設為私有,
如果想要采用延遲初始化物件,多執行緒并發初始化時,可能會有并發安全問題,假如:執行緒A,執行緒B都阻塞在了獲取鎖的步驟上,其中執行緒A獲得鎖—實體化了物件----釋放鎖;之后執行緒B—獲得鎖—實體化物件,此時違反了我們單例模式的初衷,
如何解決?
采用雙重判空檢查,首先保證了安全,且在多執行緒情況下能保持高性能,第一個if判斷避免了其他無用執行緒競爭鎖造成性能浪費,第二個if判斷能攔截除第一個獲得物件鎖執行緒以外的執行緒,
/**
* @author 微信公眾號:微觀技術
*/
public class SingleonLock {
private static SingleonLock doubleLock;
private SingleonLock() {}
// 雙重校驗鎖
public static SingleonLock getInstance() {
if (doubleLock == null) {
synchronized (SingleonLock.class) {
if (doubleLock == null) {
doubleLock = new SingleonLock();
}
}
}
return doubleLock;
}
}
2、建造者模式
定義:
建造者模式,也稱 Builder 模式,
將復雜物件的構造與其表示分離,以便同一構造程序可以創建不同的表示,
簡單來說,建造者模式就是如何一步步構建一個包含多個組成部件的物件,相同的構建程序可以創建不同的產品
核心思路:

| 角色 | 類別 | 說明 |
|---|---|---|
| Builder | 介面或抽象類 | 抽象的建造者,不是必須的 |
| ConcreteBuilder | 具體的建造者 | 可以有多個「因為每個建造風格可能不一樣」,必須要有 |
| Product | 普通類 | 最終構建的物件,必須要有 |
| Director | 指揮者 | 統一指揮建造者去建造目標,不是必須的 |
代碼示例:
/**
* @author 微信公眾號:微觀技術
*/
public class Person {
private String name;
private int age;
private String address;
public static PersonBuilder builder() {
return new PersonBuilder();
}
private Person(PersonBuilder builder) {
this.name = builder.name;
this.age = builder.age;
this.address = builder.address;
}
// 建造者
static class PersonBuilder {
private String name;
private int age;
private String address;
public PersonBuilder() {
}
public PersonBuilder name(String name) {
this.name = name;
return this;
}
public PersonBuilder age(int age) {
this.age = age;
return this;
}
public PersonBuilder address(String address) {
this.address = address;
return this;
}
public Person build() {
return new Person(this);
}
}
}
-
Person中創建一個靜態內部類PersonBuilder,然后將Person中的引數都復制到PersonBuilder類中, -
Person中創建一個private的建構式,入參為PersonBuilder型別 -
PersonBuilder中創建一個public的建構式 -
PersonBuilder中創建設定函式,對Person中那些可選引數進行賦值,回傳值為PersonBuilder型別的實體 -
PersonBuilder中創建一個build()方法,在其中構建Person的實體并回傳
/**
* @author 微信公眾號:微觀技術
*/
public class PersonBuilderTest {
public static void main(String[] args) {
Person person = Person.builder()
.name("Tom哥")
.age(18)
.address("杭州")
.build();
System.out.println(JSON.toJSONString(person));
}
}
客戶端使用鏈式呼叫,一步一步的把物件構建出來,
適用場景:
-
分階段、分步驟的方法更適合多次運算結果類創建場景,比如創建一個類實體的引數并不會一次準備好,有些引數可能需要呼叫多個服務運算后才能拿得到,這時,我們可以根據已知引數,預先對類進行創建,待后續的引數準備好了后,再設定,
-
不關心特定型別的建造者的具體演算法實作,比如,我們并不關心
StringBuilder的具體代碼實作,只關心它提供了字串拼接功能,
使用建造者模式能更方便地幫助我們按需進行物件的實體化,避免寫很多不同引數的建構式,同時還能解決同一型別引數只能寫一個建構式的弊端,
最后,實際專案中,為了簡化編碼,通常可以直接使用lombok的 @Builder 注解實作類自身的建造者模式,
3、抽象工廠模式
定義:
抽象工廠模式圍繞一個超級工廠創建其他工廠,又稱為其他工廠的工廠,是一種創建型設計模式,它能創建一系列相關的物件,而無需指定其具體類,
抽象工廠模式的關鍵點:如何找到正確的抽象,
對于軟體呼叫者來說,他們更關心軟體提供了什么功能,至于內部如何實作的,他們并不關心,另外,考慮到安全問題,一般內部具體的實作細節通常會隱藏掉,
我們以電視、冰箱、洗衣機等家用電器生產為例,很多廠商像Haier、Sony、小米、Hisense等能生產上述電器,不過在外觀、性能、功率、智能化、特色功能等方面會有差異,面對這樣的需求,我們如何借助抽象工廠模式來實作編碼,

抽象工廠模式體現為定義一個抽象工廠類,多個不同的具體工廠繼承這個抽象工廠類后,再各自實作相同的抽象功能,從而實作代碼上的多型性,
代碼示例:
/**
* @author 微信公眾號:微觀技術
*/
public abstract class AbstractFactory {
// 生產電視
abstract Object createTV();
// 生產洗衣機
abstract Object createWasher();
// 生產冰箱
abstract Object createRefrigerator();
}
public class HaierFactory extends AbstractFactory {
@Override
Object createTV() {
return null;
}
@Override
Object createWasher() {
return null;
}
@Override
Object createRefrigerator() {
return null;
}
}
public class XiaomiFactory extends AbstractFactory {
@Override
Object createTV() {
return null;
}
@Override
Object createWasher() {
return null;
}
@Override
Object createRefrigerator() {
return null;
}
}
AbstractFactory是抽象工廠類,能夠創建電視、洗衣機、冰箱抽象產品;而HaierFactory和XiaomiFactory 是具體的工廠,負責生產具體的產品,當我們要生產具體的產品時,只需要告訴AbstractFactory即可,
解決問題:
-
對于不同產品系列有比較多共性特征時,可以使用抽象工廠模式,有助于提升組件的復用性,
-
當需要提升代碼的擴展性并降低維護成本時,把物件的創建和使用程序分開,能有效地將代碼統一到一個級別上,
適用場景:
-
解決跨平臺兼容性的問題,當一個應用程式需要支持Windows、Mac、Linux等多套作業系統,
-
電商的商品、訂單、物流系統,需要根據區域政策、用戶的購買習慣,差異化處理
-
不同的資料庫產品,JDBC 就是對于資料庫增刪改查建立的抽象工廠類,無論使用什么型別的資料庫,只要具體的資料庫組件能夠支持 JDBC,就能對資料庫進行讀寫操作,
4、工廠方法模式
工廠方法模式與抽象工廠模式類似,工廠方法模式因為只圍繞著一類介面來進行物件的創建與使用,使用場景更加單一,專案中更常見些,
定義**:**
定義一個創建物件的介面,讓其子類自己決定實體化哪一個類,工廠模式使其創建程序延遲到子類進行,
核心點:封裝物件創建的程序,提升創建物件方法的可復用性,

工廠方法模式包含三個關鍵角色:抽象產品、具體產品、工廠類,
定義一個抽象產品介面ITV,HaierTV和XiaomiTV是具體產品類,TVFactory是工廠類,負責生產具體的物件實體,
代碼示例:
/**
* @author 微信公眾號:微觀技術
*/
public interface ITV {
// 描述
Object desc();
}
public class HaierTV implements ITV {
@Override
public Object desc() {
return "海爾電視";
}
}
public class XiaomiTV implements ITV {
@Override
public Object desc() {
return "小米電視";
}
}
public class TVFactory {
public static ITV getTV(String name) {
switch (name) {
case "haier":
return new HaierTV();
case "xiaomi":
return new XiaomiTV();
default:
return null;
}
}
}
public class Client {
public static void main(String[] args) {
ITV tv = TVFactory.getTV("xiaomi");
Object result = tv.desc();
System.out.println(result);
}
}
工廠方法模式是圍繞著特定的抽象產品(介面)來封裝物件的創建程序,Client只需要通過工廠類來創建具體物件實體,然后就可以使用其功能,
工廠方法模式將物件的創建和使用程序分開,降低代碼耦合性,
5、原型模式
原型模式是創建型模式的一種,其特點在于通過“復制”一個已經存在的實體來回傳新的實體,而不是新建實體,被復制的實體就是我們所稱的“原型”,這個原型是可定制的,
定義:
使用原型實體指定創建物件的種類,然后通過拷貝這些原型來創建新的物件,

代碼示例:
/**
* @author 微信公眾號:微觀技術
*/
public interface Prototype extends Cloneable {
public Prototype clone() throws CloneNotSupportedException;
}
public class APrototype implements Prototype {
@Override
public Prototype clone() throws CloneNotSupportedException {
System.out.println("開始克隆《微觀技術》物件");
return (APrototype) super.clone();
}
}
public class Client {
@SneakyThrows
public static void main(String[] args) {
Prototype a = new APrototype();
Prototype b = a.clone();
System.out.println("a的物件參考:" + a);
System.out.println("b的物件參考:" + b);
}
}
執行結果:
開始克隆《微觀技術》物件
a的物件參考:course.p14.p5.APrototype@7cc355be
b的物件參考:course.p14.p5.APrototype@6e8cf4c6
列印出兩個物件的地址,發現不相同,在記憶體中為兩個物件,
Cloneable 介面本身是空方法,呼叫的 clone() 方法其實是 Object.clone() 方法
優點:
-
性能優良,不用重新初始化物件,而是動態地獲取物件運行時的狀態,
-
可以擺脫建構式的約束,
特別注意:
clone()是淺復制,也就是基本型別資料,會給你重新復制一份新的,但是參考型別(物件中包含物件),他就不會重新復制份新的,參考型別如:bean實體參考、集合等一些參考型別,
如何解決?
你需要在執行完super.clone() 獲得淺復制物件后,再手動對其中的全域變數重新構造物件并賦值,當然,經過這個程序,得到的物件我們稱之為深復制,
適用場景:
-
反序列化,比如 fastjson的JSON.parseObject() ,將字串轉變為物件
-
每次創建新物件資源損耗較大
-
物件中的屬性非常多,通過get和set方法創建物件,復制黏貼非常痛苦
加餐:
Spring 框架中提供了一個工具類,BeanUtils.copyProperties 可以方便的完成物件屬性的拷貝,其實也是淺復制,只能對基本型別資料、物件參考拷貝,使用時特別要注意,如果全域變數有物件型別,原型物件和克隆的物件會二次修改,要特殊處理,采用深復制,否則會引發安全問題,
6、配接器模式
我們都知道美國的電壓是110V,而中國是220V,如果你去要美國旅行時,一定要記得帶電源配接器,將不同國家使用的電源電流標準轉化為適合我們自己電器的標準,否則很容易啥訓電子設備,
定義:
將類的介面轉換為客戶期望的另一個介面,配接器可以讓不兼容的兩個類一起協同作業,核心點在于轉換!
核心思路:
在原有的介面或類的外層封裝一個新的配接器層,以實作擴展物件結構的效果,并且這種擴展可以無限擴展下去,

-
Adaptee:源介面,需要適配的介面
-
Target:目標介面,暴露出去的介面
-
Adapter:配接器,將源介面適配成目標介面
適用場景:
-
原有介面無法修改時,又必須快速兼容部分新功能
-
需要依賴外部系統時,一般會單獨封裝
防腐層,降低外部系統的突發風險帶來的影響 -
適配不同資料格式,不同介面協議轉換
-
舊介面過渡升級
案例:
比如查物流資訊,由于物流公司的系統都是各自獨立,在編程語言和互動方式上有很大差異,需要針對不同的物流公司做單獨適配,同時結合不同公司的系統性能,配置不同的回應超時時間

配接器模式號稱為“最好用打補丁模式”,就是因為只要是一個介面,都可以用它來進行適配,
寫在最后
設計模式很多人都學習過,但專案實戰時總是暈暈乎乎,原因在于沒有了解其核心是什么,底層邏輯是什么,《設計模式:可復用面向物件的基礎》有講過,
在設計中思考什么應該變化,并封裝會發生變化的概念,
軟體架構的精髓:找到變化,封裝變化,
業務千變萬化,沒有固定的編碼答案,千萬不要硬套設計模式,無論選擇哪一種設計模式,盡量要能滿足SOLID原則,自我review是否滿足業務的持續擴展性,有句話說的好,“不論白貓黑貓,能抓老鼠就是好貓,”
關于我:前阿里架構師,出過專利,競賽拿過獎,CSDN博客專家,負責過電商交易、社區生鮮、營銷、金融等業務,多年團隊管理經驗,愛思考,喜歡結交朋友
「長按2秒」↓↓↓ 二維碼,拉你進群,BAT大廠大神技術交流
==============================

推薦閱讀
面試題:mysql 一棵 B+ 樹能存多少條資料?
學會這10個設計原則,離架構師又進了一步!!!
億級系統的Redis快取如何設計???
【高并發、高性能、高可用】系統設計經驗
人人都是架構師???談何容易!!
【萬級并發】電商庫存扣減如何設計?不超賣!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/353325.html
標籤:其他
上一篇:使用bash-c逐行決議
