白話梳理創(chuàng)建型設(shè)計(jì)模式:?jiǎn)卫?、原型,?gòu)建
寫(xiě)在前面
分享一些設(shè)計(jì)模式的筆記。陸續(xù)整理,按照設(shè)計(jì)模式類(lèi)型,創(chuàng)建型,結(jié)構(gòu)型,行為型發(fā)布
博文會(huì)用通俗的話梳理一些自己的理解,結(jié)合開(kāi)發(fā)中的實(shí)際場(chǎng)景,
關(guān)于設(shè)計(jì)模式,個(gè)人覺(jué)得,在理解上要站著它要解決的問(wèn)題域的角度,而不是它的實(shí)現(xiàn)方式,因?yàn)閷W(xué)完全部的設(shè)計(jì)模式,你會(huì)感覺(jué),好像大多設(shè)計(jì)模式實(shí)現(xiàn)上基本一樣。往往有這一種被欺騙的感覺(jué)....哈
食用方式:適合設(shè)計(jì)模式溫習(xí),需要對(duì)設(shè)計(jì)模式有大概的了解
理解不足小伙伴幫忙指正,虛心接受 ^_^
「 傍晚時(shí)分,你坐在屋檐下,看著天慢慢地黑下去,心里寂寞而凄涼,感到自己的生命被剝奪了。當(dāng)時(shí)我是個(gè)年輕人,但我害怕這樣生活下去,衰老下去。在我看來(lái),這是比死亡更可怕的事。--------王小波」
在23種設(shè)計(jì)模式中,創(chuàng)建型的設(shè)計(jì)模式有了5種,分別為:?jiǎn)卫?、原型、建造、工廠方法和抽象工廠。
今天要溫習(xí)的是前三個(gè)
單例
關(guān)于單例的實(shí)現(xiàn)方式,先不講,聊聊為什么需要單例?單例的優(yōu)點(diǎn)是什么,有哪些地方使用了單例?
單例用通俗的話講,即在某個(gè)作用域內(nèi),不管如何操作,某個(gè)類(lèi)的實(shí)例只能是同一個(gè),創(chuàng)建的這種類(lèi)實(shí)例稱(chēng)為單例模式。
為什么需要單例?
一種情況是有些實(shí)例沒(méi)理由重新創(chuàng)建或者丟棄它,它需要一直存在著,同時(shí),在內(nèi)存里面只有一個(gè)實(shí)例,可以減少內(nèi)存開(kāi)銷(xiāo),頻繁的創(chuàng)建和銷(xiāo)毀實(shí)例需要考慮GC等其他的的問(wèn)題,比如常見(jiàn)的一些工廠類(lèi)實(shí)例,只是希望通過(guò)他來(lái)生成一些實(shí)例,沒(méi)必多個(gè)實(shí)例存在,沒(méi)必要?jiǎng)?chuàng)建銷(xiāo)毀,而且多次重建,編碼角度考慮,是很壞的編碼,比如一些ORM框架中生成SqlSession的SqlSessionFactory實(shí)例,一般使用單例模式或者靜態(tài)單例模式,粗了減少加載配置的同時(shí),考慮數(shù)據(jù)庫(kù)連接數(shù)性能問(wèn)題。
二是某些實(shí)例,希望它在整個(gè)生命周期內(nèi)是不可變得,全局唯一的。不管在什么情況下,它的構(gòu)成能夠被嚴(yán)格的控制,可以始終保證他是共享且安全的。同時(shí)使用單例可以避免那些存儲(chǔ)唯一實(shí)例的全局變量污染命名空間。比如Java中某個(gè)類(lèi)對(duì)應(yīng)的class實(shí)例,都是單例模式,一個(gè)Class實(shí)例用于描述一個(gè)類(lèi)加載到內(nèi)存中的數(shù)據(jù),只描述一個(gè)類(lèi),即一個(gè)類(lèi)只有一個(gè)Class實(shí)例。同時(shí)它沒(méi)有構(gòu)造函數(shù),不能主動(dòng)實(shí)例化,而是在類(lèi)在加載時(shí)由java虛擬機(jī)通過(guò)類(lèi)加載器中的defineClass自動(dòng)構(gòu)造的。
如何實(shí)現(xiàn)單例
對(duì)于單例,本質(zhì)的問(wèn)題是如何保證只能被實(shí)例化一次,所以不管如何實(shí)現(xiàn)都需要構(gòu)造函數(shù)私有化.或者沒(méi)有構(gòu)造函數(shù)由JVM自動(dòng)構(gòu)造
最簡(jiǎn)單的實(shí)現(xiàn)是餓漢式單例 ,singleton作為類(lèi)變量并且直接得到了初始化,即類(lèi)中所有的變量都會(huì)被初始化 singleton作為類(lèi)變量在初始化的過(guò)程中會(huì)被收集進(jìn)<clinit>()方法中,雖然這樣能夠百分之百的保證同步,但是因?yàn)椴皇菓屑虞d,singleton被加載后可能很長(zhǎng)一段時(shí)間不被使用,即實(shí)例所開(kāi)辟的空間會(huì)存在很長(zhǎng)時(shí)間,內(nèi)存角度考慮,不是好的實(shí)現(xiàn)。
private Singleton(){ }
private static final Singleton singleton = new Singleton();
public static Singleton getInstance(){
return singleton;
}
所以為了實(shí)現(xiàn)懶加載,有了懶漢式單例模式,雖然懶漢式可以保證懶加載,但是線程不安全, 當(dāng)有兩個(gè)線程訪問(wèn)時(shí),不能保證單例的唯一性
private Singleton(){ }
private static Singleton singleton =null;
public static Singleton getInstance(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
為了保證線程安全,有了懶漢式+同步方法,即能保證懶加載,又可以保證singleton實(shí)例的唯一性,但是synchronizeed關(guān)鍵字的排他性導(dǎo)致getInstance()方法只能在同一時(shí)間被一個(gè)線程訪問(wèn)。性能低下。
private Singleton(){ }
private static Singleton singleton =null;
public static synchronized Singleton getInstance(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
這個(gè)時(shí)候,有人對(duì)懶漢式+同步方法做了改進(jìn),雙重校驗(yàn)鎖單例(Double-Check)+Volatile ,當(dāng)有兩個(gè)線程發(fā)現(xiàn)singleton為null時(shí),只有一個(gè)線程可以進(jìn)入到同步代碼塊里。即滿足了懶加載,又保證了線程的唯一性,不加volition的缺點(diǎn),有時(shí)候可能會(huì)報(bào)NPE,(JVM運(yùn)行指令重排序),有可能實(shí)例實(shí)例的變量未完成實(shí)例化其他線程去獲取到singleton變量。未完成初始化的實(shí)例調(diào)用其方法會(huì)拋出空指針異常。
private Singleton(){ }
private static volatile Singleton singleton = null;
public static Singleton getInstance() {
if (singleton == null){
synchronized (Singleton.class){
if (singleton ==null){
singleton = new Singleton();
}
}
}
return singleton2;
}
有人覺(jué)得這樣寫(xiě)好麻煩,有些有沒(méi)有簡(jiǎn)單的寫(xiě)法,當(dāng)然有,靜態(tài)內(nèi)部類(lèi)單例,靜態(tài)內(nèi)部類(lèi)的單例模式在Singleton類(lèi)初始化時(shí)并不會(huì)創(chuàng)建Singleton實(shí)例,在靜態(tài)內(nèi)部類(lèi)中定義了singleton實(shí)例。當(dāng)給靜態(tài)內(nèi)部類(lèi)被主動(dòng)創(chuàng)建時(shí)則會(huì)創(chuàng)建Singleton靜態(tài)變量,是最好的單例模式之一,;類(lèi)似于靜態(tài)工廠,將類(lèi)的創(chuàng)建延遲到靜態(tài)內(nèi)部類(lèi),外部類(lèi)的初始化不會(huì)實(shí)例化靜態(tài)內(nèi)部類(lèi)的靜態(tài)變量。
private Singleton(){ }
private static class Singtetons{
private static Singleton SINGLETON = new Singleton();
/* static {
final Singleton SINGLETON = new Singleton();
}*/
}
public static Singleton getInstance(){
return Singtetons.SINGLETON;
}
當(dāng)然還有其他的一些寫(xiě)法,比如基于枚舉的單例等,感興趣小伙伴可以去了解下
原型
為什么需要原型?
原型模式,用通俗的話講,即在原有實(shí)例的基礎(chǔ)上創(chuàng)建多個(gè)新的實(shí)例,減少多實(shí)例和復(fù)雜實(shí)例創(chuàng)建的內(nèi)存消耗。
原型模式和享元模式有些類(lèi)似,都是嘗試重用現(xiàn)有的同類(lèi)實(shí)例,但是他們本質(zhì)是不相同的,原型模式對(duì)現(xiàn)有實(shí)例的再加工,比如JS里的原型設(shè)計(jì),原型鏈,或者克隆當(dāng)前實(shí)例;而享元模式是對(duì)現(xiàn)有實(shí)例的重復(fù)使用,比如Java里的整形池、字符串池(String Pool),另一個(gè)角度,原型是創(chuàng)建型設(shè)計(jì)模式,而享元是結(jié)構(gòu)型設(shè)計(jì)模式。
所以可以這樣理解記憶,如果希望重用現(xiàn)有實(shí)例,再加工或者直接克隆,屬于實(shí)例創(chuàng)建是原型模式,如果直接使用,屬于結(jié)構(gòu)型享元模式。
原型模式一般用于克隆生成實(shí)例,會(huì)結(jié)合工廠模式使用,換一種角度考慮,其實(shí)和一種叫寫(xiě)時(shí)復(fù)制(copy-on-write)的技術(shù)特別類(lèi)似,在容器、虛擬化技術(shù)中都有應(yīng)用。
在容器技術(shù)中,應(yīng)用級(jí)別考慮,內(nèi)核共用本身就是一種原型設(shè)計(jì),同時(shí)一個(gè)運(yùn)行的容器分為鏡像層和容器層,這里的鏡像層可以理解為原型層,在對(duì)容器層的數(shù)據(jù)進(jìn)行修改時(shí),如果是update會(huì)把文件復(fù)制到容器層update,如果是新增會(huì)在容器層新增,如果刪除,會(huì)屏蔽鏡像層的文件。如果讀取,會(huì)在由容器層到鏡像層自上而下的查找。
在虛擬化技術(shù)中,如果系統(tǒng)級(jí)別考慮,硬件資源共享本身也是一種原型設(shè)計(jì),同時(shí)OpenStack 利用其 Glance組件,把虛機(jī)的分為原始后端盤(pán)和增量前端盤(pán),這里的原始后端盤(pán)即可以理解為原型盤(pán),一個(gè)標(biāo)準(zhǔn)系統(tǒng)鏡像,在創(chuàng)建的虛機(jī)里。當(dāng)修改系統(tǒng)文件的時(shí)候,會(huì)復(fù)制原始的文件到增量盤(pán)修改。創(chuàng)建的文件只在增量盤(pán)創(chuàng)建
原型設(shè)計(jì)有許多和抽象工廠和建造者一樣的效果:它隱藏了具體的實(shí)例類(lèi),因此減少了實(shí)例的數(shù)目。可以在運(yùn)行時(shí)刻增加和刪除實(shí)例,通過(guò)改變結(jié)構(gòu)、改變值來(lái)指向新的實(shí)例。減少了子類(lèi)的構(gòu)造,也減少?gòu)?fù)雜實(shí)例的重復(fù)構(gòu)建。
如何實(shí)現(xiàn)原型?
整體上講,原型設(shè)計(jì)模式的應(yīng)用有兩種:
第一種是通過(guò)改變結(jié)構(gòu)、改變值來(lái)指向新的實(shí)例,把實(shí)例中公共的部分作為原型,比如Js實(shí)例中的一些事件函數(shù),這種情況下,可以減少對(duì)同類(lèi)實(shí)例中相同部分的創(chuàng)建,減少內(nèi)存開(kāi)銷(xiāo),比如JS的原型設(shè)計(jì)
另一種情況是復(fù)雜實(shí)例的克隆,這種情況一般用于實(shí)例構(gòu)建需要依賴(lài)外部數(shù)據(jù),涉及第三方數(shù)據(jù),比如數(shù)據(jù)庫(kù),IO讀取等情況,構(gòu)建相對(duì)復(fù)雜耗時(shí),所以把整個(gè)實(shí)例作為原型,重寫(xiě)克隆方法實(shí)現(xiàn)對(duì)實(shí)例的克隆
這里那JS原生的原型模式Demo來(lái)看下。
// 在原型基礎(chǔ)上的新實(shí)例
function Person() {
this.sex = 'man';
}
// 原型實(shí)例
Person.prototype = {
name: 'Nicholas',
age: 29,
job: "Software Engineer",
sayName : function () {
console.log(this.name)
}
}
var person1 = new Person();
person1.name = 'liruilong';
console.log(person1)
var person2 = new Person();
console.log(person2)
console.log(person1.sayName == person2.sayName);//true
Person.prototype指向原型,而Person本身即為原型擴(kuò)展后的實(shí)例。通對(duì)name的賦值也可以看到,修改屬性會(huì)直接覆蓋原型的值。
[object Object] {
age: 29,
job: "Software Engineer",
name: "liruilong",
sayName: function () {
window.runnerWindow.proxyConsole.log(this.name)
},
sex: "man"
}
[object Object] {
age: 29,
job: "Software Engineer",
name: "Nicholas",
sayName: function () {
window.runnerWindow.proxyConsole.log(this.name)
},
sex: "man"
}
true
關(guān)于重寫(xiě)克隆方法的原型設(shè)計(jì)模式利用,可以通過(guò)深度遍歷,或者序列化的方式實(shí)現(xiàn),感興趣小伙伴可以下去了解下
建造者
為什么需要建造者
建造者設(shè)計(jì)模式也被稱(chēng)為為生成器模式,個(gè)人覺(jué)得,這是編碼中使用最多的一個(gè)設(shè)計(jì)模式了,用通俗的話講,即使用多個(gè)簡(jiǎn)單的實(shí)例一步一步構(gòu)建成一個(gè)復(fù)雜的實(shí)例,為什么需要建造者,通過(guò)建造者,可以將一個(gè)復(fù)雜的構(gòu)建與其表示相分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示。
通過(guò)一個(gè)最簡(jiǎn)單的建造者設(shè)計(jì)模式應(yīng)用體會(huì)下,比如一個(gè)Bean的生成,涉及的屬性較多,構(gòu)造函數(shù)很不方便,可以使用建造者設(shè)計(jì)模式,通setter 方法對(duì)屬性賦值,返回一個(gè)this的方式(區(qū)別于傳統(tǒng)的setter方法),每個(gè)setter 方法的調(diào)用即是一次構(gòu)建,返回的是不同的實(shí)例(屬性不同)。
public Bulider setD(int d) {
this.d = d;
return this;
}
復(fù)雜一點(diǎn)的,比如 Java 中 SpringBoot 系列安全組件 SpringSecurity配置的生成,即通過(guò)建造者的設(shè)計(jì)模式構(gòu)建了一個(gè)配置類(lèi),可以有選擇通過(guò)構(gòu)建的方式配置鑒權(quán),授權(quán),比如表單驗(yàn)證等,以及各種事件的處理器。關(guān)于配置的Demo感興趣小伙伴可以了解下。
開(kāi)發(fā)中,需要生成一些復(fù)雜多變的東西,比如doc,excel等,設(shè)計(jì)策略較多,利用策略模式往往會(huì)有重疊的部分,就可以使用建造者模式來(lái)實(shí)現(xiàn)。比如這是一個(gè)生產(chǎn)中生成復(fù)雜Excel的Demo:https://liruilong.blog.csdn.net/article/details/113191009
如何實(shí)現(xiàn)建造者
這個(gè)一個(gè)通過(guò)建造者設(shè)計(jì)模式生成實(shí)例的Demo。這樣寫(xiě)的好處:
一是相比傳統(tǒng)的先使用構(gòu)造函數(shù)生成實(shí)例,然后通過(guò)setter 方法一個(gè)個(gè)賦值來(lái)講,他是一個(gè)整體,即整個(gè)構(gòu)建過(guò)程是一個(gè)鏈?zhǔn)秸{(diào)用,傳統(tǒng)的構(gòu)造方式,構(gòu)造過(guò)程被分配到了個(gè)多個(gè)setter方法調(diào)用中,在構(gòu)造過(guò)程中 Bean 可能處于一種不一致的狀態(tài),真的出現(xiàn)這種情況往往很難調(diào)試
另一種情況,這樣的寫(xiě)法,可以把一個(gè)Bean定義為不可變得,而使用傳統(tǒng)的形式則很難實(shí)現(xiàn)。
package com.liruilong.common.demo;
/**
* @Auther Liruilong
* @Date 2020/8/4 12:34
* @Description:
*/
public class Demo {
private final int a;
private final int d;
private Demo(Bulider bulider) {
a = bulider.a;
d = bulider.d;
}
public static class Bulider {
private int a;
private int d;
public Bulider(int a) {
this.a = a;
}
public Demo build() {
return new Demo(this);
}
public Bulider setA(int a) {
this.a = a;
return this;
}
public Bulider setD(int d) {
this.d = d;
return this;
}
}
public static void main(String[] args) {
Demo build = new Demo.Bulider(3).setD(4).build();
}
}
嗯,創(chuàng)建型前三個(gè)設(shè)計(jì)模式就和小伙伴們分享到這里,之前有時(shí)間會(huì)陸續(xù)分享剩下的2個(gè),生活加油
博文參考
《Java并發(fā)編程詳解》
《JavaScript高級(jí)程序設(shè)計(jì)》(第3版)
《Effective Java》 (中文版第3版)
《Head First設(shè)計(jì)模式》(中文版)
《設(shè)計(jì)模式 可復(fù)用面向?qū)ο筌浖幕A(chǔ)》(中文版)
作者:山河已無(wú)恙
歡迎關(guān)注微信公眾號(hào) :山河已無(wú)恙