面對複雜業務架構,阿里架構師是如何做的?
大家好,我是Tom哥~
面對複雜的業務場景,千變萬化的客戶需求,如何以一變應萬變,以最小的開發成本快速落地實現,同時保證系統有著較低的複雜度,能夠保證系統後續de持續迭代能力,讓系統擁有較高的可擴充套件性。
這些是一個合格的架構師必須修煉的基礎內功,但是如何修煉這門神功???
不要著急,慢慢看下去。學了真本事,拿了阿里、頭條的offer,女神還會遠嗎!:heart:????????
接下來我們來系統性彙總下,軟體架構設計需要知曉的設計模式,主要是提煉精髓、核心設計思路、程式碼示例、以及應用場景等。
CRUD很多人都會,不懂設計模式也可以開發軟體,但是當開發及維護大型軟體系統過程中就痛苦不堪,懂了人自然聽得懂我在說什麼,不懂的人說了你也不會懂。
我將常用的軟體設計模式,做了彙總,目錄如下:
考慮到內容篇幅較大,為了便於大家閱讀,將軟體設計模式系列(共23個)拆分成四篇文章,每篇文章講解六個設計模式,採用不同的顏色區分,便於快速消化記憶
本文是首篇,主要講解 單例模式
、 建造者模式
、 抽象工廠
、 工廠方法
、 原型模式
、 介面卡模式
,共6個設計模式。
1、單例模式
定義:
單例模式(Singleton)允許存在一個和僅存在一個給定類的例項。它提供一種機制讓任何實體都可以訪問該例項。
核心思路:
:one: 保證一個類只有一個例項。如果該物件已經被建立, 則返回已有的物件。為什麼要這樣設計呢?因為某些業務場景要控制共享資源 (例如資料庫或檔案) 的訪問許可權。
:two: 為該例項提供一個全域性訪問入口, 提供一個 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的物件引用:[email protected] b的物件引用:[email protected]
打印出兩個物件的地址,發現不相同,在記憶體中為兩個物件。
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部落格專家,負責過電商交易、社群生鮮、營銷、金融等業務,多年團隊管理經驗,愛思考,喜歡結交朋友
- RP原型資源分享-購物類App
- 實現各種效果和功能的按鈕,讀這篇文章就夠了
- 5分鐘的時間製作一個反彈球遊戲
- 35歲危機?內捲成程式設計師代名詞了…
- 線上文字實體抽取能力,助力應用解析海量文字資料
- 不買排名,不去SEO,如何做到登上谷歌搜尋首頁?
- HtmlParse:一款超輕量級的HTML檔案解析和爬取工具
- 五款當下超火熱的相親交友APP測評
- 盡一份孝心,為家人做一個老人防摔報警系統
- 作為軟體工程師,給年輕時的自己的建議(下)
- 技術分享| 淺談排程平臺設計
- 組態介面推陳出新:打造新一代再生水廠工藝二維組態系統
- 平頭哥 芯事訪談 | 全志科技CTO丁然:影片、AI市場爆發,RISC-V生態需要產業一起努力
- IDEA SSM Maven實現商品管理系統(超詳細SSM整合專案)
- 如何為迴歸測試選擇測試用例?
- 前端必學——函數語言程式設計(五)
- 40篇學完C語言——(第八篇)【指標陣列以及指向指標的指標】
- 焱融看|非結構化資料場景下,資料湖到底有多香?
- 低程式碼開發的未來~
- Docker容器:將帶UI的程式直接轉為Web應用,so easy