創(chuàng)建型模式
用于描述“怎樣創(chuàng)建對象”,它的主要特點是“將對象的創(chuàng)建與使用分離”。GoF(四人組)書中提供了單例、原型、工廠方法、抽象工廠、建造者等 5 種創(chuàng)建型模式。
1.單例設(shè)計模式
單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。
這種模式涉及到一個單一的類,該類負(fù)責(zé)創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
1.1 單例模式的結(jié)構(gòu)
單例模式的主要有以下角色:
- 單例類。只能創(chuàng)建一個實例的類
- 訪問類。使用單例類
1.2 單例模式的實現(xiàn)
單例設(shè)計模式分類兩種:
? 餓漢式:類加載就會導(dǎo)致該單實例對象被創(chuàng)建
? 懶漢式:類加載不會導(dǎo)致該單實例對象被創(chuàng)建,而是首次使用該對象時才會創(chuàng)建
1.2.1. 餓漢式-方式1(靜態(tài)變量方式)
/**
* 餓漢式
* 靜態(tài)變量創(chuàng)建類的對象
*/
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
//在成員位置創(chuàng)建該類的對象
private static Singleton instance = new Singleton();
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
return instance;
}
}
說明:
? 該方式在成員位置聲明Singleton類型的靜態(tài)變量,并創(chuàng)建Singleton類的對象instance。instance對象是隨著類的加載而創(chuàng)建的。如果該對象足夠大的話,而一直沒有使用就會造成內(nèi)存的浪費。
1.2.2. 餓漢式-方式2(靜態(tài)代碼塊方式)
/**
* 餓漢式
* 在靜態(tài)代碼塊中創(chuàng)建該類對象
*/
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
//在成員位置創(chuàng)建該類的對象
private static Singleton instance;
static {
instance = new Singleton();
}
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
return instance;
}
}
說明:
? 該方式在成員位置聲明Singleton類型的靜態(tài)變量,而對象的創(chuàng)建是在靜態(tài)代碼塊中,也是對著類的加載而創(chuàng)建。所以和餓漢式的方式1基本上一樣,當(dāng)然該方式也存在內(nèi)存浪費問題。
1.2.3. 餓漢式-枚舉方式
枚舉類實現(xiàn)單例模式是極力推薦的單例實現(xiàn)模式,因為枚舉類型是線程安全的,并且只會裝載一次,設(shè)計者充分的利用了枚舉的這個特性來實現(xiàn)單例模式,枚舉的寫法非常簡單,而且枚舉類型是所用單例實現(xiàn)中唯一一種不會被破壞的單例實現(xiàn)模式。
/**
* 枚舉方式
*/
public enum Singleton {
INSTANCE;
}
說明:
? 枚舉方式屬于惡漢式方式。
1.2.4. 懶漢式-方式1(線程不安全)
/**
* 懶漢式
* 線程不安全
*/
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
//在成員位置創(chuàng)建該類的對象
private static Singleton instance;
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
說明:
? 從上面代碼我們可以看出該方式在成員位置聲明Singleton類型的靜態(tài)變量,并沒有進行對象的賦值操作,那么什么時候賦值的呢?當(dāng)調(diào)用getInstance()方法獲取Singleton類的對象的時候才創(chuàng)建Singleton類的對象,這樣就實現(xiàn)了懶加載的效果。但是,如果是多線程環(huán)境,會出現(xiàn)線程安全問題。
1.2.5. 懶漢式-方式2(線程安全)
/**
* 懶漢式
* 線程安全
*/
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
//在成員位置創(chuàng)建該類的對象
private static Singleton instance;
//對外提供靜態(tài)方法獲取該對象
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
說明:
? 該方式也實現(xiàn)了懶加載效果,同時又解決了線程安全問題。但是在getInstance()方法上添加了synchronized關(guān)鍵字,導(dǎo)致該方法的執(zhí)行效果特別低。從上面代碼我們可以看出,其實就是在初始化instance的時候才會出現(xiàn)線程安全問題,一旦初始化完成就不存在了。
1.2.6. 懶漢式-方式3(雙重檢查鎖)
再來討論一下懶漢模式中加鎖的問題,對于 getInstance()
方法來說,絕大部分的操作都是讀操作,讀操作是線程安全的,所以我們沒必讓每個線程必須持有鎖才能調(diào)用該方法,我們需要調(diào)整加鎖的時機。由此也產(chǎn)生了一種新的實現(xiàn)模式:雙重檢查鎖模式
/**
* 雙重檢查方式
*/
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
private static Singleton instance;
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
//第一次判斷,如果instance不為null,不進入搶鎖階段,直接返回實例
if(instance == null) {
synchronized (Singleton.class) {
//搶到鎖之后再次判斷是否為null
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
雙重檢查鎖模式是一種非常好的單例實現(xiàn)模式,解決了單例、性能、線程安全問題,上面的雙重檢測鎖模式看上去完美無缺,其實是存在問題,在多線程的情況下,可能會出現(xiàn)空指針問題,出現(xiàn)問題的原因是JVM在實例化對象的時候會進行優(yōu)化和指令重排序操作。
要解決雙重檢查鎖模式帶來空指針異常的問題,只需要使用 volatile
關(guān)鍵字, volatile
關(guān)鍵字可以保證可見性和有序性。
/**
* 雙重檢查方式
*/
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
private static volatile Singleton instance;
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
//第一次判斷,如果instance不為null,不進入搶鎖階段,直接返回實際
if(instance == null) {
synchronized (Singleton.class) {
//搶到鎖之后再次判斷是否為空
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
小結(jié):
添加 volatile
關(guān)鍵字之后的雙重檢查鎖模式是一種比較好的單例實現(xiàn)模式,能夠保證在多線程的情況下線程安全也不會有性能問題。
1.2.7 懶漢式-方式4(靜態(tài)內(nèi)部類方式)
靜態(tài)內(nèi)部類單例模式中實例由內(nèi)部類創(chuàng)建,由于 JVM 在加載外部類的過程中, 是不會加載靜態(tài)內(nèi)部類的, 只有內(nèi)部類的屬性/方法被調(diào)用時才會被加載, 并初始化其靜態(tài)屬性。靜態(tài)屬性由于被 static
修飾,保證只被實例化一次,并且嚴(yán)格保證實例化順序。
/**
* 靜態(tài)內(nèi)部類方式
*/
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
說明:
? 第一次加載Singleton類時不會去初始化INSTANCE,只有第一次調(diào)用getInstance,虛擬機加載SingletonHolder
并初始化INSTANCE,這樣不僅能確保線程安全,也能保證 Singleton 類的唯一性。
小結(jié):
? 靜態(tài)內(nèi)部類單例模式是一種優(yōu)秀的單例模式,是開源項目中比較常用的一種單例模式。在沒有加任何鎖的情況下,保證了多線程下的安全,并且沒有任何性能影響和空間的浪費。
1.3 單例存在的問題
1.3.1 序列化破壞單例(枚舉方式除外)
序列化反序列化
Singleton類:
public class Singleton implements Serializable {
//私有構(gòu)造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Test類:
public class Test {
public static void main(String[] args) throws Exception {
//往文件中寫對象
//writeObject2File();
//從文件中讀取對象
Singleton s1 = readObjectFromFile();
Singleton s2 = readObjectFromFile();
//判斷兩個反序列化后的對象是否是同一個對象
System.out.println(s1 == s2);
}
private static Singleton readObjectFromFile() throws Exception {
//創(chuàng)建對象輸入流對象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
//第一個讀取Singleton對象
Singleton instance = (Singleton) ois.readObject();
return instance;
}
public static void writeObject2File() throws Exception {
//獲取Singleton類的對象
Singleton instance = Singleton.getInstance();
//創(chuàng)建對象輸出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
//將instance對象寫出到文件中
oos.writeObject(instance);
}
}
上面代碼運行結(jié)果是false
,表明序列化和反序列化已經(jīng)破壞了單例設(shè)計模式。
1.3.2 反射破壞單例
Singleton類:
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
private static volatile Singleton instance;
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
if(instance != null) {
return instance;
}
synchronized (Singleton.class) {
if(instance != null) {
return instance;
}
instance = new Singleton();
return instance;
}
}
}
Test類:
public class Test {
public static void main(String[] args) throws Exception {
//獲取Singleton類的字節(jié)碼對象
Class clazz = Singleton.class;
//獲取Singleton類的私有無參構(gòu)造方法對象
Constructor constructor = clazz.getDeclaredConstructor();
//取消訪問檢查
constructor.setAccessible(true);
//創(chuàng)建Singleton類的對象s1
Singleton s1 = (Singleton) constructor.newInstance();
//創(chuàng)建Singleton類的對象s2
Singleton s2 = (Singleton) constructor.newInstance();
//判斷通過反射創(chuàng)建的兩個Singleton對象是否是同一個對象
System.out.println(s1 == s2);
}
}
上面代碼運行結(jié)果是false
,表明序列化和反序列化已經(jīng)破壞了單例設(shè)計模式
1.3.3 序列化、反序列方式破壞單例模式的解決方法
在Singleton類中添加readResolve()
方法,在反序列化時被反射調(diào)用,如果定義了這個方法,就返回這個方法的值,如果沒有定義,則返回新new出來的對象。
Singleton類:
public class Singleton implements Serializable {
//私有構(gòu)造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 下面是為了解決序列化反序列化破解單例模式
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
源碼解析:
ObjectInputStream類
public final Object readObject() throws IOException, ClassNotFoundException{
...
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false);//重點查看readObject0方法
.....
}
private Object readObject0(boolean unshared) throws IOException {
...
try {
switch (tc) {
...
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));//重點查看readOrdinaryObject方法
...
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
private Object readOrdinaryObject(boolean unshared) throws IOException {
...
//isInstantiable 返回true,執(zhí)行 desc.newInstance(),通過反射創(chuàng)建新的單例類,
obj = desc.isInstantiable() ? desc.newInstance() : null;
...
// 在Singleton類中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法執(zhí)行結(jié)果為true
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
// 通過反射調(diào)用 Singleton 類中的 readResolve 方法,將返回值賦值給rep變量
// 這樣多次調(diào)用ObjectInputStream類中的readObject方法,繼而就會調(diào)用我們定義的readResolve方法,所以返回的是同一個對象。
Object rep = desc.invokeReadResolve(obj);
...
}
return obj;
}
1.3.4 反射方式破解單例的解決方法
public class Singleton {
//私有構(gòu)造方法
private Singleton() {
/*
反射破解單例模式需要添加的代碼
*/
if(instance != null) {
throw new RuntimeException();
}
}
private static volatile Singleton instance;
//對外提供靜態(tài)方法獲取該對象
public static Singleton getInstance() {
if(instance != null) {
return instance;
}
synchronized (Singleton.class) {
if(instance != null) {
return instance;
}
instance = new Singleton();
return instance;
}
}
}
說明:
? 這種方式比較好理解。當(dāng)通過反射方式調(diào)用構(gòu)造方法進行創(chuàng)建創(chuàng)建時,直接拋異常。不運行此中操作。
1.4 jdk源碼中用到單例的例子
Runtime類就是使用的單例設(shè)計模式。
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
從上面源代碼中可以看出Runtime類使用的是惡漢式(靜態(tài)屬性)方式來實現(xiàn)單例模式的。
使用Runtime類中的方法
public class RuntimeDemo {
public static void main(String[] args) throws IOException {
//獲取Runtime類對象
Runtime runtime = Runtime.getRuntime();
//返回 Java 虛擬機中的內(nèi)存總量。
System.out.println(runtime.totalMemory());
//返回 Java 虛擬機試圖使用的最大內(nèi)存量。
System.out.println(runtime.maxMemory());
//創(chuàng)建一個新的進程執(zhí)行指定的字符串命令,返回進程對象
Process process = runtime.exec("ipconfig");
//獲取命令執(zhí)行后的結(jié)果,通過輸入流獲取
InputStream inputStream = process.getInputStream();
byte[] arr = new byte[1024 * 1024* 100];
int b = inputStream.read(arr);
System.out.println(new String(arr,0,b,"gbk"));
}
}
2.工廠模式
在java中,萬物皆對象,這些對象都需要創(chuàng)建,如果創(chuàng)建的時候直接new該對象,就會對該對象耦合嚴(yán)重,假如我們要更換對象,所有new對象的地方都需要修改一遍,這顯然違背了軟件設(shè)計的開閉原則。如果我們使用工廠來生產(chǎn)對象,我們就只和工廠打交道就可以了,徹底和對象解耦,如果要更換對象,直接在工廠里更換該對象即可,達(dá)到了與對象解耦的目的;所以說,工廠模式最大的優(yōu)點就是:解耦。
需求:設(shè)計一個咖啡店點餐系統(tǒng)。
設(shè)計一個咖啡類(Coffee),并定義其兩個子類(美式咖啡【AmericanCoffee】和拿鐵咖啡【LatteCoffee】);再設(shè)計一個咖啡店類(CoffeeStore),咖啡店具有點咖啡的功能。
Coffee
public abstract class Coffee {
public abstract String getName();
//加糖
public void addsugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
AmericanCoffee
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式咖啡";
}
}
LatteCoffee
public class LatteCoffee extends Coffee {
public String getName() {
return "拿鐵咖啡";
}
}
CoffeeStore
public class CoffeeStore {
public Coffee orderCoffee(String type) {
//聲明Coffee類型的變量,根據(jù)不同類型創(chuàng)建不同的coffee子類對象
Coffee coffee = null;
if("american".equals(type)) {
coffee = new AmericanCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("對不起,您所點的咖啡沒有");
}
//加配料
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
Client
public class Client {
public static void main(String[] args) {
//1,創(chuàng)建咖啡店類
CoffeeStore store = new CoffeeStore();
//2,點咖啡
Coffee coffee = store.orderCoffee("american");
System.out.println(coffee.getName());
}
}
2.1 簡單工廠模式(不屬于GOF的23種經(jīng)典設(shè)計模式)
簡單工廠不是一種設(shè)計模式,反而比較像是一種編程習(xí)慣。
結(jié)構(gòu):
簡單工廠包含如下角色:
- 抽象產(chǎn)品 :定義了產(chǎn)品的規(guī)范,描述了產(chǎn)品的主要特性和功能。
- 具體產(chǎn)品 :實現(xiàn)或者繼承抽象產(chǎn)品的子類
- 具體工廠 :提供了創(chuàng)建產(chǎn)品的方法,調(diào)用者通過該方法來獲取產(chǎn)品。
2.1.1 實現(xiàn)
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}
工廠(factory)處理創(chuàng)建對象的細(xì)節(jié),一旦有了SimpleCoffeeFactory,CoffeeStore類中的orderCoffee()就變成此對象的客戶,后期如果需要Coffee對象直接從工廠中獲取即可。這樣也就解除了CoffeeStore和Coffee實現(xiàn)類的耦合,同時又產(chǎn)生了新的耦合,CoffeeStore對象和SimpleCoffeeFactory工廠對象的耦合,工廠對象和商品對象的耦合。
后期如果再加新品種的咖啡,我們勢必要需求修改SimpleCoffeeFactory的代碼,違反了開閉原則。工廠類的客戶端可能有很多,比如創(chuàng)建美團外賣等,這樣只需要修改工廠類的代碼,省去其他的修改操作。
2.1.2 優(yōu)缺點
優(yōu)點:
封裝了創(chuàng)建對象的過程,可以通過參數(shù)直接獲取對象。把對象的創(chuàng)建和業(yè)務(wù)邏輯層分開,這樣以后就避免了修改客戶代碼,如果要實現(xiàn)新產(chǎn)品直接修改工廠類,而不需要在原代碼中修改,這樣就降低了客戶代碼修改的可能性,更加容易擴展。
缺點:
增加新產(chǎn)品時還是需要修改工廠類的代碼,違背了“開閉原則”。
2.1.3 擴展
在開發(fā)中也有一部分人將工廠類中的創(chuàng)建對象的功能定義為靜態(tài)的,這個就是靜態(tài)工廠模式,它也不是23種設(shè)計模式中的。代碼如下:
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffe;
}
}
2.2 工廠方法模式
定義一個用于創(chuàng)建對象的接口,讓子類決定實例化哪個產(chǎn)品類對象。工廠方法使一個產(chǎn)品類的實例化延遲到其工廠的子類。
針對上例中的缺點,使用工廠方法模式就可以完美的解決,完全遵循開閉原則。
結(jié)構(gòu)
工廠方法模式的主要角色:
- 抽象工廠(Abstract Factory):提供了創(chuàng)建產(chǎn)品的接口,調(diào)用者通過它訪問具體工廠的工廠方法來創(chuàng)建產(chǎn)品。
- 具體工廠(ConcreteFactory):主要是實現(xiàn)抽象工廠中的抽象方法,完成具體產(chǎn)品的創(chuàng)建。
- 抽象產(chǎn)品(Product):定義了產(chǎn)品的規(guī)范,描述了產(chǎn)品的主要特性和功能。
- 具體產(chǎn)品(ConcreteProduct):實現(xiàn)了抽象產(chǎn)品角色所定義的接口,由具體工廠來創(chuàng)建,它同具體工廠之間一一對應(yīng)。
2.2.1 實現(xiàn)
抽象工廠:
public interface CoffeeFactory {
Coffee createCoffee();
}
具體工廠
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
咖啡店類
public class CoffeeStore {
private CoffeeFactory factory;
public CoffeeStore(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee(String type) {
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
2.2.2 優(yōu)缺點
優(yōu)點:
- 用戶只需要知道具體工廠的名稱就可得到所要的產(chǎn)品,無須知道產(chǎn)品的具體創(chuàng)建過程;
- 在系統(tǒng)增加新的產(chǎn)品時只需要添加具體產(chǎn)品類和對應(yīng)的具體工廠類,無須對原工廠進行任何修改,滿足開閉原則;
缺點:
- 每增加一個產(chǎn)品就要增加一個具體產(chǎn)品類和一個對應(yīng)的具體工廠類,這增加了系統(tǒng)的復(fù)雜度。
2.3 抽象工廠模式(仔細(xì)看一下)
是一種為訪問類提供一個創(chuàng)建一組相關(guān)或相互依賴對象的接口,且訪問類無須指定所要產(chǎn)品的具體類就能得到同族的不同等級的產(chǎn)品的模式結(jié)構(gòu)。
抽象工廠模式是工廠方法模式的升級版本,工廠方法模式只生產(chǎn)一個等級的產(chǎn)品,而抽象工廠模式可生產(chǎn)多個等級的產(chǎn)品。
結(jié)構(gòu)
抽象工廠模式的主要角色如下:
- 抽象工廠(Abstract Factory):提供了創(chuàng)建產(chǎn)品的接口,它包含多個創(chuàng)建產(chǎn)品的方法,可以創(chuàng)建多個不同等級的產(chǎn)品。
- 具體工廠(Concrete Factory):主要是實現(xiàn)抽象工廠中的多個抽象方法,完成具體產(chǎn)品的創(chuàng)建。
- 抽象產(chǎn)品(Product):定義了產(chǎn)品的規(guī)范,描述了產(chǎn)品的主要特性和功能,抽象工廠模式有多個抽象產(chǎn)品。
- 具體產(chǎn)品(ConcreteProduct):實現(xiàn)了抽象產(chǎn)品角色所定義的接口,由具體工廠來創(chuàng)建,它 同具體工廠之間是多對一的關(guān)系。
2.3.1 實現(xiàn)
現(xiàn)咖啡店業(yè)務(wù)發(fā)生改變,不僅要生產(chǎn)咖啡還要生產(chǎn)甜點,如提拉米蘇、抹茶慕斯等,要是按照工廠方法模式,需要定義提拉米蘇類、抹茶慕斯類、提拉米蘇工廠、抹茶慕斯工廠、甜點工廠類,很容易發(fā)生類爆炸情況。其中拿鐵咖啡、美式咖啡是一個產(chǎn)品等級,都是咖啡;提拉米蘇、抹茶慕斯也是一個產(chǎn)品等級;拿鐵咖啡和提拉米蘇是同一產(chǎn)品族(也就是都屬于意大利風(fēng)味),美式咖啡和抹茶慕斯是同一產(chǎn)品族(也就是都屬于美式風(fēng)味)。
抽象工廠:
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
}
具體工廠:
//美式甜點工廠
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
public Dessert createDessert() {
return new MatchaMousse();
}
}
//意大利風(fēng)味甜點工廠
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Tiramisu();
}
}
如果要加同一個產(chǎn)品族的話,只需要再加一個對應(yīng)的工廠類即可,不需要修改其他的類。
2.3.2 優(yōu)缺點
優(yōu)點:
當(dāng)一個產(chǎn)品族中的多個對象被設(shè)計成一起工作時,它能保證客戶端始終只使用同一個產(chǎn)品族中的對象。
缺點:
當(dāng)產(chǎn)品族中需要增加一個新的產(chǎn)品時,所有的工廠類都需要進行修改。
2.3.3 使用場景
-
當(dāng)需要創(chuàng)建的對象是一系列相互關(guān)聯(lián)或相互依賴的產(chǎn)品族時,如電器工廠中的電視機、洗衣機、空調(diào)等。
-
系統(tǒng)中有多個產(chǎn)品族,但每次只使用其中的某一族產(chǎn)品。如有人只喜歡穿某一個品牌的衣服和鞋。
-
系統(tǒng)中提供了產(chǎn)品的類庫,且所有產(chǎn)品的接口相同,客戶端不依賴產(chǎn)品實例的創(chuàng)建細(xì)節(jié)和內(nèi)部結(jié)構(gòu)。
如:輸入法換皮膚,一整套一起換。生成不同操作系統(tǒng)的程序。
2.4 模式擴展
簡單工廠+配置文件解除耦合
可以通過工廠模式+配置文件的方式解除工廠對象和產(chǎn)品對象的耦合。在工廠類中加載配置文件中的全類名,并創(chuàng)建對象進行存儲,客戶端如果需要對象,直接進行獲取即可。
第一步:定義配置文件
為了演示方便,我們使用properties文件作為配置文件,名稱為bean.properties
american=com.itheima.pattern.factory.config_factory.AmericanCoffee
latte=com.itheima.pattern.factory.config_factory.LatteCoffee
第二步:改進工廠類
public class CoffeeFactory {
private static Map<String,Coffee> map = new HashMap();
static {
Properties p = new Properties();
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
p.load(is);
//遍歷Properties集合對象
Set<Object> keys = p.keySet();
for (Object key : keys) {
//根據(jù)鍵獲取值(全類名)
String className = p.getProperty((String) key);
//獲取字節(jié)碼對象
Class clazz = Class.forName(className);
Coffee obj = (Coffee) clazz.newInstance();
map.put((String)key,obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Coffee createCoffee(String name) {
return map.get(name);
}
}
靜態(tài)成員變量用來存儲創(chuàng)建的對象(鍵存儲的是名稱,值存儲的是對應(yīng)的對象),而讀取配置文件以及創(chuàng)建對象寫在靜態(tài)代碼塊中,目的就是只需要執(zhí)行一次。
2.5 JDK源碼解析-Collection.iterator方法
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("令狐沖");
list.add("風(fēng)清揚");
list.add("任我行");
//獲取迭代器對象
Iterator<String> it = list.iterator();
//使用迭代器遍歷
while(it.hasNext()) {
String ele = it.next();
System.out.println(ele);
}
}
}
使用迭代器遍歷集合,獲取集合中的元素。而單列集合獲取迭代器的方法就使用到了工廠方法模式.
Collection接口是抽象工廠類,ArrayList是具體的工廠類;Iterator接口是抽象商品類,ArrayList類中的Iter內(nèi)部類是具體的商品類。在具體的工廠類中iterator()方法創(chuàng)建具體的商品類的對象。
另:
? 1,DateForamt類中的getInstance()方法使用的是工廠模式;
? 2,Calendar類中的getInstance()方法使用的是工廠模式;
3. 原型模式(克隆模式)
用一個已經(jīng)創(chuàng)建的實例作為原型,通過復(fù)制該原型對象來創(chuàng)建一個和原型對象相同的新對象。
結(jié)構(gòu)
原型模式包含如下角色:
- 抽象原型類:規(guī)定了具體原型對象必須實現(xiàn)的的 clone() 方法。
- 具體原型類:實現(xiàn)抽象原型類的 clone() 方法,它是可被復(fù)制的對象。
- 訪問類:使用具體原型類中的 clone() 方法來復(fù)制新的對象。
使用場景
- 對象的創(chuàng)建非常復(fù)雜,可以使用原型模式快捷的創(chuàng)建對象。
- 性能和安全要求比較高。
3.1 實現(xiàn)
原型模式的克隆分為淺克隆和深克隆。
淺克?。簞?chuàng)建一個新對象,新對象的屬性和原來對象完全相同,對于非基本類型屬性,仍指向原有屬性所指向的對象的內(nèi)存地址。
深克?。簞?chuàng)建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。
Java中的Object類中提供了 clone()
方法來實現(xiàn)淺克隆。 Cloneable 接口是上面的類圖中的抽象原型類,而實現(xiàn)了Cloneable接口的子實現(xiàn)類就是具體的原型類。代碼如下:
Realizetype(具體的原型類):
public class Realizetype implements Cloneable {
public Realizetype() {
System.out.println("具體的原型對象創(chuàng)建完成!");
}
@Override
protected Realizetype clone() throws CloneNotSupportedException {
System.out.println("具體原型復(fù)制成功!");
return (Realizetype) super.clone();
}
}
PrototypeTest(測試訪問類):
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype r1 = new Realizetype();
Realizetype r2 = r1.clone();
System.out.println("對象r1和r2是同一個對象?" + (r1 == r2));
}
}
3.2 案例
用原型模式生成“三好學(xué)生”獎狀
同一學(xué)校的“三好學(xué)生”獎狀除了獲獎人姓名不同,其他都相同,可以使用原型模式復(fù)制多個“三好學(xué)生”獎狀出來,然后在修改獎狀上的名字即可。
//獎狀類
public class Citation implements Cloneable {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return (this.name);
}
public void show() {
System.out.println(name + "同學(xué):在2020學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評為三好學(xué)生。特發(fā)此狀!");
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
//測試訪問類
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
Citation c1 = new Citation();
c1.setName("張三");
//復(fù)制獎狀
Citation c2 = c1.clone();
//將獎狀的名字修改李四
c2.setName("李四");
c1.show();
c2.show();
}
}
3.3 擴展(深克?。?/h4>
將上面的“三好學(xué)生”獎狀的案例中Citation類的name屬性修改為Student類型的屬性。
//獎狀類
public class Citation implements Cloneable {
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
void show() {
System.out.println(stu.getName() + "同學(xué):在2020學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評為三好學(xué)生。特發(fā)此狀!");
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
//學(xué)生類
public class Student {
private String name;
private String address;
public Student(String name, String address) {
this.name = name;
this.address = address;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
//測試類
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
Citation c1 = new Citation();
Student stu = new Student("張三", "西安");
c1.setStu(stu);
//復(fù)制獎狀
Citation c2 = c1.clone();
//獲取c2獎狀所屬學(xué)生對象
Student stu1 = c2.getStu();
stu1.setName("李四");
//判斷stu對象和stu1對象是否是同一個對象
System.out.println("stu和stu1是同一個對象?" + (stu == stu1));
c1.show();
c2.show();
}
}
淺復(fù)制對于對象中的引用類型只是將類型的地址復(fù)制到新對象中,指向的還是同一個對象。必須將指向的對象也重新復(fù)制一份,才是深復(fù)制
說明:
? stu對象和stu1對象是同一個對象,就會產(chǎn)生將stu1對象中name屬性值改為“李四”,兩個Citation(獎狀)對象中顯示的都是李四。這就是淺克隆的效果,對具體原型類(Citation)中的引用類型的屬性進行引用的復(fù)制。這種情況需要使用深克隆,而進行深克隆需要使用對象流。代碼如下:
public class CitationTest1 {
public static void main(String[] args) throws Exception {
Citation c1 = new Citation();
Student stu = new Student("張三", "西安");
c1.setStu(stu);
//創(chuàng)建對象輸出流對象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\b.txt"));
//將c1對象寫出到文件中
oos.writeObject(c1);
oos.close();
//創(chuàng)建對象出入流對象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\b.txt"));
//讀取對象
Citation c2 = (Citation) ois.readObject();
//獲取c2獎狀所屬學(xué)生對象
Student stu1 = c2.getStu();
stu1.setName("李四");
//判斷stu對象和stu1對象是否是同一個對象
System.out.println("stu和stu1是同一個對象?" + (stu == stu1));
c1.show();
c2.show();
}
}
注意:Citation類和Student類必須實現(xiàn)Serializable接口,否則會拋NotSerializableException異常。
4.建造者模式
將一個復(fù)雜對象的構(gòu)建與表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
- 分離了部件的構(gòu)造(由Builder來負(fù)責(zé))和裝配(由Director負(fù)責(zé))。 從而可以構(gòu)造出復(fù)雜的對象。這個模式適用于:某個對象的構(gòu)建過程復(fù)雜的情況。
- 由于實現(xiàn)了構(gòu)建和裝配的解耦。不同的構(gòu)建器,相同的裝配,也可以做出不同的對象;相同的構(gòu)建器,不同的裝配順序也可以做出不同的對象。也就是實現(xiàn)了構(gòu)建算法、裝配算法的解耦,實現(xiàn)了更好的復(fù)用。
- 建造者模式可以將部件和其組裝過程分開,一步一步創(chuàng)建一個復(fù)雜的對象。用戶只需要指定復(fù)雜對象的類型就可以得到該對象,而無須知道其內(nèi)部的具體構(gòu)造細(xì)節(jié)。
結(jié)構(gòu)
建造者(Builder)模式包含如下角色:
-
抽象建造者類(Builder):這個接口規(guī)定要實現(xiàn)復(fù)雜對象的那些部分的創(chuàng)建,并不涉及具體的部件對象的創(chuàng)建。
-
具體建造者類(ConcreteBuilder):實現(xiàn) Builder 接口,完成復(fù)雜產(chǎn)品的各個部件的具體創(chuàng)建方法。在構(gòu)造過程完成后,提供產(chǎn)品的實例。
-
產(chǎn)品類(Product):要創(chuàng)建的復(fù)雜對象。
-
指揮者類(Director):調(diào)用具體建造者來創(chuàng)建復(fù)雜對象的各個部分,在指導(dǎo)者中不涉及具體產(chǎn)品的信息,只負(fù)責(zé)保證對象各部分完整創(chuàng)建或按某種順序創(chuàng)建。
4.1 實現(xiàn)
創(chuàng)建共享單車
生產(chǎn)自行車是一個復(fù)雜的過程,它包含了車架,車座等組件的生產(chǎn)。而車架又有碳纖維,鋁合金等材質(zhì)的,車座有橡膠,真皮等材質(zhì)。對于自行車的生產(chǎn)就可以使用建造者模式。
這里Bike是產(chǎn)品,包含車架,車座等組件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具體的建造者;Director是指揮者。
//自行車類
public class Bike {
private String frame;
private String seat;
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
// 抽象 builder 類
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}
//摩拜單車Builder類
public class MobikeBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("鋁合金車架");
}
@Override
public void buildSeat() {
mBike.setSeat("真皮車座");
}
@Override
public Bike createBike() {
return mBike;
}
}
//ofo單車Builder類
public class OfoBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("碳纖維車架");
}
@Override
public void buildSeat() {
mBike.setSeat("橡膠車座");
}
@Override
public Bike createBike() {
return mBike;
}
}
//指揮者類
public class Director {
private Builder mBuilder;
public Director(Builder builder) {
mBuilder = builder;
}
public Bike construct() {
mBuilder.buildFrame();
mBuilder.buildSeat();
return mBuilder.createBike();
}
}
//測試類
public class Client {
public static void main(String[] args) {
showBike(new OfoBuilder());
showBike(new MobikeBuilder());
}
private static void showBike(Builder builder) {
Director director = new Director(builder);
Bike bike = director.construct();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
注意:
上面示例是 Builder模式的常規(guī)用法,指揮者類 Director 在建造者模式中具有很重要的作用,它用于指導(dǎo)具體構(gòu)建者如何構(gòu)建產(chǎn)品,控制調(diào)用先后次序,并向調(diào)用者返回完整的產(chǎn)品類,但是有些情況下需要簡化系統(tǒng)結(jié)構(gòu),可以把指揮者類和抽象建造者進行結(jié)合
// 抽象 builder 類
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
public Bike construct() {
this.buildFrame();
this.BuildSeat();
return this.createBike();
}
}
說明:
這樣做確實簡化了系統(tǒng)結(jié)構(gòu),但同時也加重了抽象建造者類的職責(zé),也不是太符合單一職責(zé)原則,如果construct() 過于復(fù)雜,建議還是封裝到 Director 中。
4.2 優(yōu)缺點
優(yōu)點:
- 建造者模式的封裝性很好。使用建造者模式可以有效的封裝變化,在使用建造者模式的場景中,一般產(chǎn)品類和建造者類是比較穩(wěn)定的,因此,將主要的業(yè)務(wù)邏輯封裝在指揮者類中對整體而言可以取得比較好的穩(wěn)定性。
- 在建造者模式中,客戶端不必知道產(chǎn)品內(nèi)部組成的細(xì)節(jié),將產(chǎn)品本身與產(chǎn)品的創(chuàng)建過程解耦,使得相同的創(chuàng)建過程可以創(chuàng)建不同的產(chǎn)品對象。
- 可以更加精細(xì)地控制產(chǎn)品的創(chuàng)建過程 。將復(fù)雜產(chǎn)品的創(chuàng)建步驟分解在不同的方法中,使得創(chuàng)建過程更加清晰,也更方便使用程序來控制創(chuàng)建過程。
- 建造者模式很容易進行擴展。如果有新的需求,通過實現(xiàn)一個新的建造者類就可以完成,基本上不用修改之前已經(jīng)測試通過的代碼,因此也就不會對原有功能引入風(fēng)險。符合開閉原則。
缺點:
造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點,其組成部分相似,如果產(chǎn)品之間的差異性很大,則不適合使用建造者模式,因此其使用范圍受到一定的限制。
4.3 使用場景
建造者(Builder)模式創(chuàng)建的是復(fù)雜對象,其產(chǎn)品的各個部分經(jīng)常面臨著劇烈的變化,但將它們組合在一起的算法卻相對穩(wěn)定,所以它通常在以下場合使用。
- 創(chuàng)建的對象較復(fù)雜,由多個部件構(gòu)成,各部件面臨著復(fù)雜的變化,但構(gòu)件間的建造順序是穩(wěn)定的。
- 創(chuàng)建復(fù)雜對象的算法獨立于該對象的組成部分以及它們的裝配方式,即產(chǎn)品的構(gòu)建過程和最終的表示是獨立的。
4.4 模式擴展
建造者模式除了上面的用途外,在開發(fā)中還有一個常用的使用方式,就是當(dāng)一個類構(gòu)造器需要傳入很多參數(shù)時,如果創(chuàng)建這個類的實例,代碼可讀性會非常差,而且很容易引入錯誤,此時就可以利用建造者模式進行重構(gòu)。
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Phone(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
....get/set/toString
public class Client {
public static void main(String[] args) {
//構(gòu)建Phone對象
Phone phone = new Phone("intel","三星屏幕","金士頓","華碩");
System.out.println(phone);
}
}
上面在客戶端代碼中構(gòu)建Phone對象,傳遞了四個參數(shù),如果參數(shù)更多呢?代碼的可讀性及使用的成本就是比較高。
重構(gòu)后
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
private Phone(Builder builder) {
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder() {}
public Builder cpu(String val) {
cpu = val;
return this;
}
public Builder screen(String val) {
screen = val;
return this;
}
public Builder memory(String val) {
memory = val;
return this;
}
public Builder mainboard(String val) {
mainboard = val;
return this;
}
public Phone build() {
return new Phone(this);}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone.Builder()
.cpu("intel")
.mainboard("華碩")
.memory("金士頓")
.screen("三星")
.build();
System.out.println(phone);
}
}
4.5對比
工廠方法模式VS建造者模式
工廠方法模式注重的是整體對象的創(chuàng)建方式;而建造者模式注重的是部件構(gòu)建的過程,意在通過一步一步地精確構(gòu)造創(chuàng)建出一個復(fù)雜的對象。
我們舉個簡單例子來說明兩者的差異,如要制造一個超人,如果使用工廠方法模式,直接產(chǎn)生出來的就是一個力大無窮、能夠飛翔、內(nèi)褲外穿的超人;而如果使用建造者模式,則需要組裝手、頭、腳、軀干等部分,然后再把內(nèi)褲外穿,于是一個超人就誕生了。
抽象工廠模式VS建造者模式
抽象工廠模式實現(xiàn)對產(chǎn)品家族的創(chuàng)建,一個產(chǎn)品家族是這樣的一系列產(chǎn)品:具有不同分類維度的產(chǎn)品組合,采用抽象工廠模式則是不需要關(guān)心構(gòu)建過程,只關(guān)心什么產(chǎn)品由什么工廠生產(chǎn)即可。
建造者模式則是要求按照指定的藍(lán)圖建造產(chǎn)品,它的主要目的是通過組裝零配件而產(chǎn)生一個新產(chǎn)品。文章來源:http://www.zghlxwxcb.cn/news/detail-526512.html
如果將抽象工廠模式看成汽車配件生產(chǎn)工廠,生產(chǎn)一個產(chǎn)品族的產(chǎn)品,那么建造者模式就是一個汽車組裝工廠,通過對部件的組裝可以返回一輛完整的汽車。文章來源地址http://www.zghlxwxcb.cn/news/detail-526512.html
到了這里,關(guān)于設(shè)計模式--------創(chuàng)建型模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!