Java 設(shè)計(jì)模式
轉(zhuǎn)自https://javadoop.com/post/design-pattern
系列文章將整理到我在GitHub上的《Java面試指南》倉庫腌且,更多精彩內(nèi)容請到我的倉庫里查看
喜歡的話麻煩點(diǎn)下Star竟终、fork哈
文章也將發(fā)表在我的個(gè)人博客,閱讀體驗(yàn)更佳:
本文是微信公眾號【Java技術(shù)江湖】的《夯實(shí)Java基礎(chǔ)系列博文》其中一篇切蟋,本文部分內(nèi)容來源于網(wǎng)絡(luò)统捶,為了把本文主題講得清晰透徹,也整合了很多我認(rèn)為不錯(cuò)的技術(shù)博客內(nèi)容,引用其中了一些比較好的博客文章喘鸟,如有侵權(quán)匆绣,請聯(lián)系作者。
該系列博文會(huì)告訴你如何從入門到進(jìn)階什黑,一步步地學(xué)習(xí)Java基礎(chǔ)知識(shí)崎淳,并上手進(jìn)行實(shí)戰(zhàn),接著了解每個(gè)Java知識(shí)點(diǎn)背后的實(shí)現(xiàn)原理愕把,更完整地了解整個(gè)Java技術(shù)體系拣凹,形成自己的知識(shí)框架。為了更好地總結(jié)和檢驗(yàn)?zāi)愕膶W(xué)習(xí)成果恨豁,本系列文章也會(huì)提供每個(gè)知識(shí)點(diǎn)對應(yīng)的面試題以及參考答案嚣镜。
如果對本系列文章有什么建議,或者是有什么疑問的話橘蜜,也可以關(guān)注公眾號【Java技術(shù)江湖】聯(lián)系作者菊匿,歡迎你參與本系列博文的創(chuàng)作和修訂
一直想寫一篇介紹設(shè)計(jì)模式的文章,讓讀者可以很快看完计福,而且一看就懂跌捆,看懂就會(huì)用,同時(shí)不會(huì)將各個(gè)模式搞混象颖。自認(rèn)為本文還是寫得不錯(cuò)的佩厚,花了不少心思來寫這文章和做圖,力求讓讀者真的能看著簡單同時(shí)有所收獲说订。
設(shè)計(jì)模式是對大家實(shí)際工作中寫的各種代碼進(jìn)行高層次抽象的總結(jié)抄瓦,其中最出名的當(dāng)屬 Gang of Four (GoF) 的分類了,他們將設(shè)計(jì)模式分類為 23 種經(jīng)典的模式克蚂,根據(jù)用途我們又可以分為三大類闺鲸,分別為創(chuàng)建型模式、結(jié)構(gòu)型模式和行為型模式埃叭。是的摸恍,我不善于扯這些有的沒的,還是少點(diǎn)廢話吧~~~
有一些重要的設(shè)計(jì)原則在開篇和大家分享下赤屋,這些原則將貫通全文:
- 面向接口編程立镶,而不是面向?qū)崿F(xiàn)。這個(gè)很重要类早,也是優(yōu)雅的媚媒、可擴(kuò)展的代碼的第一步,這就不需要多說了吧涩僻。
- 職責(zé)單一原則缭召。每個(gè)類都應(yīng)該只有一個(gè)單一的功能栈顷,并且該功能應(yīng)該由這個(gè)類完全封裝起來。
- 對修改關(guān)閉嵌巷,對擴(kuò)展開放萄凤。對修改關(guān)閉是說,我們辛辛苦苦加班寫出來的代碼搪哪,該實(shí)現(xiàn)的功能和該修復(fù)的 bug 都完成了靡努,別人可不能說改就改;對擴(kuò)展開放就比較好理解了晓折,也就是說在我們寫好的代碼基礎(chǔ)上惑朦,很容易實(shí)現(xiàn)擴(kuò)展。
目錄
創(chuàng)建型模式
創(chuàng)建型模式的作用就是創(chuàng)建對象漓概,說到創(chuàng)建一個(gè)對象漾月,最熟悉的就是 new 一個(gè)對象,然后 set 相關(guān)屬性垛耳。但是栅屏,在很多場景下飘千,我們需要給客戶端提供更加友好的創(chuàng)建對象的方式堂鲜,尤其是那種我們定義了類,但是需要提供給其他開發(fā)者用的時(shí)候护奈。
簡單工廠模式
和名字一樣簡單缔莲,非常簡單,直接上代碼吧:
public class FoodFactory {
public static Food makeFood(String name) {
if (name.equals("noodle")) {
Food noodle = new LanZhouNoodle();
noodle.addSpicy("more");
return noodle;
} else if (name.equals("chicken")) {
Food chicken = new HuangMenChicken();
chicken.addCondiment("potato");
return chicken;
} else {
return null;
}
}
}
其中霉旗,LanZhouNoodle 和 HuangMenChicken 都繼承自 Food痴奏。
簡單地說,簡單工廠模式通常就是這樣厌秒,一個(gè)工廠類 XxxFactory读拆,里面有一個(gè)靜態(tài)方法,根據(jù)我們不同的參數(shù)鸵闪,返回不同的派生自同一個(gè)父類(或?qū)崿F(xiàn)同一接口)的實(shí)例對象檐晕。
我們強(qiáng)調(diào)職責(zé)單一原則,一個(gè)類只提供一種功能蚌讼,F(xiàn)oodFactory 的功能就是只要負(fù)責(zé)生產(chǎn)各種 Food辟灰。
工廠模式
簡單工廠模式很簡單,如果它能滿足我們的需要篡石,我覺得就不要折騰了芥喇。之所以需要引入工廠模式,是因?yàn)槲覀兺枰褂脙蓚€(gè)或兩個(gè)以上的工廠凰萨。
public interface FoodFactory {
Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if (name.equals("A")) {
return new ChineseFoodA();
} else if (name.equals("B")) {
return new ChineseFoodB();
} else {
return null;
}
}
}
public class AmericanFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if (name.equals("A")) {
return new AmericanFoodA();
} else if (name.equals("B")) {
return new AmericanFoodB();
} else {
return null;
}
}
}
其中继控,ChineseFoodA械馆、ChineseFoodB、AmericanFoodA武通、AmericanFoodB 都派生自 Food狱杰。
客戶端調(diào)用:
public class APP {
public static void main(String[] args) {
// 先選擇一個(gè)具體的工廠
FoodFactory factory = new ChineseFoodFactory();
// 由第一步的工廠產(chǎn)生具體的對象,不同的工廠造出不一樣的對象
Food food = factory.makeFood("A");
}
}
雖然都是調(diào)用 makeFood("A") 制作 A 類食物厅须,但是仿畸,不同的工廠生產(chǎn)出來的完全不一樣。
第一步朗和,我們需要選取合適的工廠错沽,然后第二步基本上和簡單工廠一樣。
核心在于眶拉,我們需要在第一步選好我們需要的工廠千埃。比如,我們有 LogFactory 接口忆植,實(shí)現(xiàn)類有 FileLogFactory 和 KafkaLogFactory放可,分別對應(yīng)將日志寫入文件和寫入 Kafka 中,顯然朝刊,我們客戶端第一步就需要決定到底要實(shí)例化 FileLogFactory 還是 KafkaLogFactory耀里,這將決定之后的所有的操作。
雖然簡單拾氓,不過我也把所有的構(gòu)件都畫到一張圖上冯挎,這樣讀者看著比較清晰:
![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點(diǎn)擊并拖拽以移動(dòng)")?
抽象工廠模式
當(dāng)涉及到產(chǎn)品族的時(shí)候,就需要引入抽象工廠模式了咙鞍。
一個(gè)經(jīng)典的例子是造一臺(tái)電腦房官。我們先不引入抽象工廠模式,看看怎么實(shí)現(xiàn)续滋。
因?yàn)殡娔X是由許多的構(gòu)件組成的翰守,我們將 CPU 和主板進(jìn)行抽象,然后 CPU 由 CPUFactory 生產(chǎn)疲酌,主板由 MainBoardFactory 生產(chǎn)蜡峰,然后,我們再將 CPU 和主板搭配起來組合在一起徐勃,如下圖:
![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點(diǎn)擊并拖拽以移動(dòng)")?
這個(gè)時(shí)候的客戶端調(diào)用是這樣的:
// 得到 Intel 的 CPU
CPUFactory cpuFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();
// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.make();
// 組裝 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);
單獨(dú)看 CPU 工廠和主板工廠事示,它們分別是前面我們說的工廠模式。這種方式也容易擴(kuò)展僻肖,因?yàn)橐o電腦加硬盤的話肖爵,只需要加一個(gè) HardDiskFactory 和相應(yīng)的實(shí)現(xiàn)即可,不需要修改現(xiàn)有的工廠臀脏。
但是劝堪,這種方式有一個(gè)問題冀自,那就是如果 Intel 家產(chǎn)的 CPU 和 AMD 產(chǎn)的主板不能兼容使用,那么這代碼就容易出錯(cuò)秒啦,因?yàn)榭蛻舳瞬⒉恢浪鼈儾患嫒莅敬郑簿蜁?huì)錯(cuò)誤地出現(xiàn)隨意組合。
下面就是我們要說的產(chǎn)品族的概念余境,它代表了組成某個(gè)產(chǎn)品的一系列附件的集合:
當(dāng)涉及到這種產(chǎn)品族的問題的時(shí)候驻呐,就需要抽象工廠模式來支持了。我們不再定義 CPU 工廠芳来、主板工廠含末、硬盤工廠、顯示屏工廠等等即舌,我們直接定義電腦工廠佣盒,每個(gè)電腦工廠負(fù)責(zé)生產(chǎn)所有的設(shè)備,這樣能保證肯定不存在兼容問題顽聂。
這個(gè)時(shí)候肥惭,對于客戶端來說,不再需要單獨(dú)挑選 CPU廠商紊搪、主板廠商蜜葱、硬盤廠商等,直接選擇一家品牌工廠嗦明,品牌工廠會(huì)負(fù)責(zé)生產(chǎn)所有的東西笼沥,而且能保證肯定是兼容可用的蚪燕。
public static void main(String[] args) {
// 第一步就要選定一個(gè)“大廠”
ComputerFactory cf = new AmdFactory();
// 從這個(gè)大廠造 CPU
CPU cpu = cf.makeCPU();
// 從這個(gè)大廠造主板
MainBoard board = cf.makeMainBoard();
// 從這個(gè)大廠造硬盤
HardDisk hardDisk = cf.makeHardDisk();
// 將同一個(gè)廠子出來的 CPU娶牌、主板、硬盤組裝在一起
Computer result = new Computer(cpu, board, hardDisk);
}
當(dāng)然馆纳,抽象工廠的問題也是顯而易見的诗良,比如我們要加個(gè)顯示器,就需要修改所有的工廠鲁驶,給所有的工廠都加上制造顯示器的方法鉴裹。這有點(diǎn)違反了對修改關(guān)閉,對擴(kuò)展開放這個(gè)設(shè)計(jì)原則钥弯。
單例模式
單例模式用得最多径荔,錯(cuò)得最多。
餓漢模式最簡單:
public class Singleton {
// 首先脆霎,將 new Singleton() 堵死
private Singleton() {};
// 創(chuàng)建私有靜態(tài)實(shí)例总处,意味著這個(gè)類第一次使用的時(shí)候就會(huì)進(jìn)行創(chuàng)建
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
// 瞎寫一個(gè)靜態(tài)方法。這里想說的是睛蛛,如果我們只是要調(diào)用 Singleton.getDate(...)鹦马,
// 本來是不想要生成 Singleton 實(shí)例的胧谈,不過沒辦法,已經(jīng)生成了
public static Date getDate(String mode) {return new Date();}
}
很多人都能說出餓漢模式的缺點(diǎn)荸频,可是我覺得生產(chǎn)過程中菱肖,很少碰到這種情況:你定義了一個(gè)單例的類,不需要其實(shí)例旭从,可是你卻把一個(gè)或幾個(gè)你會(huì)用到的靜態(tài)方法塞到這個(gè)類中稳强。
飽漢模式最容易出錯(cuò):
public class Singleton {
// 首先,也是先堵死 new Singleton() 這條路
private Singleton() {}
// 和餓漢模式相比和悦,這邊不需要先實(shí)例化出來键袱,注意這里的 volatile,它是必須的
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
// 加鎖
synchronized (Singleton.class) {
// 這一次判斷也是必須的摹闽,不然會(huì)有并發(fā)問題
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
雙重檢查蹄咖,指的是兩次檢查 instance 是否為 null。
volatile 在這里是需要的付鹿,希望能引起讀者的關(guān)注澜汤。
很多人不知道怎么寫,直接就在 getInstance() 方法簽名上加上 synchronized舵匾,這就不多說了俊抵,性能太差。
嵌套類最經(jīng)典坐梯,以后大家就用它吧:
public class Singleton3 {
private Singleton3() {}
// 主要是使用了 嵌套類可以訪問外部類的靜態(tài)屬性和靜態(tài)方法 的特性
private static class Holder {
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance() {
return Holder.instance;
}
}
注意徽诲,很多人都會(huì)把這個(gè)嵌套類說成是靜態(tài)內(nèi)部類,嚴(yán)格地說吵血,內(nèi)部類和嵌套類是不一樣的谎替,它們能訪問的外部類權(quán)限也是不一樣的。
最后蹋辅,一定有人跳出來說用枚舉實(shí)現(xiàn)單例钱贯,是的沒錯(cuò),枚舉類很特殊侦另,它在類加載的時(shí)候會(huì)初始化里面的所有的實(shí)例秩命,而且 JVM 保證了它們不會(huì)再被實(shí)例化,所以它天生就是單例的褒傅。不說了弃锐,讀者自己看著辦吧,不建議使用殿托。
建造者模式
經(jīng)常碰見的 XxxBuilder 的類霹菊,通常都是建造者模式的產(chǎn)物。建造者模式其實(shí)有很多的變種碌尔,但是對于客戶端來說浇辜,我們的使用通常都是一個(gè)模式的:
Food food = new FoodBuilder().a().b().c().build();
Food food = Food.builder().a().b().c().build();
套路就是先 new 一個(gè) Builder券敌,然后可以鏈?zhǔn)降卣{(diào)用一堆方法,最后再調(diào)用一次 build() 方法柳洋,我們需要的對象就有了待诅。
來一個(gè)中規(guī)中矩的建造者模式:
class User {
// 下面是“一堆”的屬性
private String name;
private String password;
private String nickName;
private int age;
// 構(gòu)造方法私有化,不然客戶端就會(huì)直接調(diào)用構(gòu)造方法了
private User(String name, String password, String nickName, int age) {
this.name = name;
this.password = password;
this.nickName = nickName;
this.age = age;
}
// 靜態(tài)方法熊镣,用于生成一個(gè) Builder卑雁,這個(gè)不一定要有,不過寫這個(gè)方法是一個(gè)很好的習(xí)慣绪囱,
// 有些代碼要求別人寫 new User.UserBuilder().a()...build() 看上去就沒那么好
public static UserBuilder builder() {
return new UserBuilder();
}
public static class UserBuilder {
// 下面是和 User 一模一樣的一堆屬性
private String name;
private String password;
private String nickName;
private int age;
private UserBuilder() {
}
// 鏈?zhǔn)秸{(diào)用設(shè)置各個(gè)屬性值测蹲,返回 this,即 UserBuilder
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder password(String password) {
this.password = password;
return this;
}
public UserBuilder nickName(String nickName) {
this.nickName = nickName;
return this;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
// build() 方法負(fù)責(zé)將 UserBuilder 中設(shè)置好的屬性“復(fù)制”到 User 中鬼吵。
// 當(dāng)然扣甲,可以在 “復(fù)制” 之前做點(diǎn)檢驗(yàn)
public User build() {
if (name == null || password == null) {
throw new RuntimeException("用戶名和密碼必填");
}
if (age <= 0 || age >= 150) {
throw new RuntimeException("年齡不合法");
}
// 還可以做賦予”默認(rèn)值“的功能
if (nickName == null) {
nickName = name;
}
return new User(name, password, nickName, age);
}
}
}
核心是:先把所有的屬性都設(shè)置給 Builder,然后 build() 方法的時(shí)候齿椅,將這些屬性復(fù)制給實(shí)際產(chǎn)生的對象琉挖。
看看客戶端的調(diào)用:
public class APP {
public static void main(String[] args) {
User d = User.builder()
.name("foo")
.password("pAss12345")
.age(25)
.build();
}
}
說實(shí)話,建造者模式的鏈?zhǔn)?/strong>寫法很吸引人涣脚,但是示辈,多寫了很多“無用”的 builder 的代碼,感覺這個(gè)模式?jīng)]什么用遣蚀。不過矾麻,當(dāng)屬性很多,而且有些必填芭梯,有些選填的時(shí)候险耀,這個(gè)模式會(huì)使代碼清晰很多。我們可以在 Builder 的構(gòu)造方法中強(qiáng)制讓調(diào)用者提供必填字段粥帚,還有胰耗,在 build() 方法中校驗(yàn)各個(gè)參數(shù)比在 User 的構(gòu)造方法中校驗(yàn),代碼要優(yōu)雅一些芒涡。
題外話,強(qiáng)烈建議讀者使用 lombok卖漫,用了 lombok 以后费尽,上面的一大堆代碼會(huì)變成如下這樣:
@Builder
class User {
private String name;
private String password;
private String nickName;
private int age;
}
怎么樣,省下來的時(shí)間是不是又可以干點(diǎn)別的了羊始。
當(dāng)然旱幼,如果你只是想要鏈?zhǔn)綄懛ǎ幌胍ㄔ煺吣J酵晃袀€(gè)很簡單的辦法柏卤,User 的 getter 方法不變冬三,所有的 setter 方法都讓其 return this 就可以了,然后就可以像下面這樣調(diào)用:
User user = new User().setName("").setPassword("").setAge(20);
原型模式
這是我要說的創(chuàng)建型模式的最后一個(gè)設(shè)計(jì)模式了缘缚。
原型模式很簡單:有一個(gè)原型實(shí)例勾笆,基于這個(gè)原型實(shí)例產(chǎn)生新的實(shí)例,也就是“克隆”了桥滨。
Object 類中有一個(gè) clone() 方法窝爪,它用于生成一個(gè)新的對象,當(dāng)然齐媒,如果我們要調(diào)用這個(gè)方法蒲每,java 要求我們的類必須先實(shí)現(xiàn) Cloneable 接口,此接口沒有定義任何方法喻括,但是不這么做的話邀杏,在 clone() 的時(shí)候,會(huì)拋出 CloneNotSupportedException 異常唬血。
protected native Object clone() throws CloneNotSupportedException;
java 的克隆是淺克隆淮阐,碰到對象引用的時(shí)候,克隆出來的對象和原對象中的引用將指向同一個(gè)對象刁品。通常實(shí)現(xiàn)深克隆的方法是將對象進(jìn)行序列化泣特,然后再進(jìn)行反序列化。
原型模式了解到這里我覺得就夠了挑随,各種變著法子說這種代碼或那種代碼是原型模式状您,沒什么意義。
創(chuàng)建型模式總結(jié)
創(chuàng)建型模式總體上比較簡單兜挨,它們的作用就是為了產(chǎn)生實(shí)例對象膏孟,算是各種工作的第一步了,因?yàn)槲覀儗懙氖?strong>面向?qū)ο?/strong>的代碼拌汇,所以我們第一步當(dāng)然是需要?jiǎng)?chuàng)建一個(gè)對象了柒桑。
簡單工廠模式最簡單;工廠模式在簡單工廠模式的基礎(chǔ)上增加了選擇工廠的維度噪舀,需要第一步選擇合適的工廠魁淳;抽象工廠模式有產(chǎn)品族的概念,如果各個(gè)產(chǎn)品是存在兼容性問題的与倡,就要用抽象工廠模式界逛。單例模式就不說了,為了保證全局使用的是同一對象纺座,一方面是安全性考慮息拜,一方面是為了節(jié)省資源;建造者模式專門對付屬性很多的那種類,為了讓代碼更優(yōu)美少欺;原型模式用得最少喳瓣,了解和 Object 類中的 clone() 方法相關(guān)的知識(shí)即可。
參考文章
轉(zhuǎn)自https://javadoop.com/post/design-pattern
微信公眾號
個(gè)人公眾號:程序員黃小斜
?
黃小斜是 985 碩士赞别,阿里巴巴Java工程師畏陕,在自學(xué)編程、技術(shù)求職氯庆、Java學(xué)習(xí)等方面有豐富經(jīng)驗(yàn)和獨(dú)到見解蹭秋,希望幫助到更多想要從事互聯(lián)網(wǎng)行業(yè)的程序員們。
?
作者專注于 JAVA 后端技術(shù)棧堤撵,熱衷于分享程序員干貨仁讨、學(xué)習(xí)經(jīng)驗(yàn)、求職心得实昨,以及自學(xué)編程和Java技術(shù)棧的相關(guān)干貨洞豁。
?
黃小斜是一個(gè)斜杠青年,堅(jiān)持學(xué)習(xí)和寫作荒给,相信終身學(xué)習(xí)的力量丈挟,希望和更多的程序員交朋友,一起進(jìn)步和成長志电!
原創(chuàng)電子書:
關(guān)注微信公眾號【程序員黃小斜】后回復(fù)【原創(chuàng)電子書】即可領(lǐng)取我原創(chuàng)的電子書《菜鳥程序員修煉手冊:從技術(shù)小白到阿里巴巴Java工程師》這份電子書總結(jié)了我2年的Java學(xué)習(xí)之路曙咽,包括學(xué)習(xí)方法、技術(shù)總結(jié)挑辆、求職經(jīng)驗(yàn)和面試技巧等內(nèi)容例朱,已經(jīng)幫助很多的程序員拿到了心儀的offer!
程序員3T技術(shù)學(xué)習(xí)資源: 一些程序員學(xué)習(xí)技術(shù)的資源大禮包鱼蝉,關(guān)注公眾號后洒嗤,后臺(tái)回復(fù)關(guān)鍵字 “資料” 即可免費(fèi)無套路獲取,包括Java魁亦、python渔隶、C++、大數(shù)據(jù)洁奈、機(jī)器學(xué)習(xí)间唉、前端、移動(dòng)端等方向的技術(shù)資料睬魂。
技術(shù)公眾號:Java技術(shù)江湖
如果大家想要實(shí)時(shí)關(guān)注我更新的文章以及分享的干貨的話终吼,可以關(guān)注我的微信公眾號【Java技術(shù)江湖】
這是一位阿里 Java 工程師的技術(shù)小站。作者黃小斜氯哮,專注 Java 相關(guān)技術(shù):SSM、SpringBoot、MySQL喉钢、分布式姆打、中間件、集群肠虽、Linux幔戏、網(wǎng)絡(luò)、多線程税课,偶爾講點(diǎn)Docker闲延、ELK,同時(shí)也分享技術(shù)干貨和學(xué)習(xí)經(jīng)驗(yàn)韩玩,致力于Java全棧開發(fā)垒玲!
Java工程師必備學(xué)習(xí)資源:
關(guān)注公眾號后回復(fù)”Java“即可領(lǐng)取 Java基礎(chǔ)、進(jìn)階找颓、項(xiàng)目和架構(gòu)師等免費(fèi)學(xué)習(xí)資料合愈,更有數(shù)據(jù)庫、分布式击狮、微服務(wù)等熱門技術(shù)學(xué)習(xí)視頻佛析,內(nèi)容豐富,兼顧原理和實(shí)踐彪蓬,另外也將贈(zèng)送作者原創(chuàng)的Java學(xué)習(xí)指南寸莫、Java程序員面試指南等干貨資源
?