下面總結設計模式中的創(chuàng)建型模式:
1.簡單工廠模式
簡單工廠不是設計模式,更像是一種編程習慣。它把實例化的操作單獨放到一個類中,這個類就成為簡單工廠類,讓簡單工廠類來決定應該用哪個具體子類來實例化券敌。
在簡單工廠模式中,我們在創(chuàng)建對象時不會對客戶端暴露創(chuàng)建邏輯柳洋,并且是通過使用一個共同的接口來指向新創(chuàng)建的對象待诅,從而將客戶類和具體子類的實現(xiàn)解耦,客戶類不再需要知道有哪些子類以及應當實例化哪個子類熊镣”把悖客戶類往往有多個立由,如果不使用簡單工廠,那么所有的客戶類都要知道所有子類的細節(jié)序厉。而且一旦子類發(fā)生改變锐膜,例如增加子類,那么所有的客戶類都要進行修改弛房。
介紹
意圖:在創(chuàng)建一個對象時不向客戶暴露內部細節(jié)道盏,并提供一個創(chuàng)建對象的通用接口。
主要解決:主要解決接口選擇的問題文捶。
何時使用:我們明確地計劃不同條件下創(chuàng)建不同實例時荷逞。
如何解決:讓其子類實現(xiàn)工廠接口,返回的也是一個抽象的產品粹排。
關鍵代碼:創(chuàng)建過程在其子類執(zhí)行种远。
應用實例: 1、您需要一輛汽車顽耳,可以直接從工廠里面提貨坠敷,而不用去管這輛汽車是怎么做出來的,以及這個汽車里面的具體實現(xiàn)射富。 2膝迎、Hibernate 換數(shù)據(jù)庫只需換方言和驅動就可以。
優(yōu)點: 1胰耗、一個調用者想創(chuàng)建一個對象限次,只要知道其名稱就可以了。 2柴灯、擴展性高卖漫,如果想增加一個產品,只要擴展一個工廠類就可以赠群。 3羊始、屏蔽產品的具體實現(xiàn),調用者只關心產品的接口乎串。
缺點:每次增加一個產品時店枣,都需要增加一個具體類和對象實現(xiàn)工廠速警,使得系統(tǒng)中類的個數(shù)成倍增加叹誉,在一定程度上增加了系統(tǒng)的復雜度,同時也增加了系統(tǒng)具體類的依賴闷旧。這并不是什么好事长豁。
使用場景: 1、日志記錄器:記錄可能記錄到本地硬盤忙灼、系統(tǒng)事件匠襟、遠程服務器等钝侠,用戶可以選擇記錄日志到什么地方。 2酸舍、數(shù)據(jù)庫訪問帅韧,當用戶不知道最后系統(tǒng)采用哪一類數(shù)據(jù)庫,以及數(shù)據(jù)庫可能有變化時啃勉。 3忽舟、設計一個連接服務器的框架,需要三個協(xié)議淮阐,"POP3"叮阅、"IMAP"、"HTTP"泣特,可以把這三個作為產品類浩姥,共同實現(xiàn)一個接口。
注意事項:作為一種創(chuàng)建類模式状您,在任何需要生成復雜對象的地方勒叠,都可以使用工廠模式。有一點需要注意的地方就是復雜對象適合使用工廠模式膏孟,而簡單對象缴饭,特別是只需要通過 new 就可以完成創(chuàng)建的對象,無需使用工廠模式骆莹。如果使用工廠模式颗搂,就需要引入一個工廠類,會增加系統(tǒng)的復雜度幕垦。
實現(xiàn)
我們將創(chuàng)建一個 Shape 接口和實現(xiàn) Shape 接口的實體類丢氢。下一步是定義工廠類 ShapeFactory。
FactoryPatternDemo先改,我們的演示類使用 ShapeFactory 來獲取 Shape 對象疚察。它將向 ShapeFactory 傳遞信息(CIRCLE / RECTANGLE / SQUARE),以便獲取它所需對象的類型仇奶。
步驟 1
創(chuàng)建一個接口貌嫡。
Shape.java
public interface Shape {
void draw();
}
步驟 2
創(chuàng)建實現(xiàn)接口的實體類。
Rectangle.java
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
Square.java
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
Circle.java
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
步驟 3
創(chuàng)建一個工廠该溯,生成基于給定信息的實體類的對象岛抄。
ShapeFactory.java
public class ShapeFactory {
/*使用 getShape 方法獲取形狀類型的對象,
在實際操作中建議把CIRCLE等定義為常量*/
public static Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
步驟 4
使用該工廠狈茉,通過傳遞類型信息來獲取實體類的對象夫椭。
FactoryPatternDemo.java
public class FactoryPatternDemo {
public static void main(String[] args) {
//獲取 Circle 的對象,并調用它的 draw 方法
Shape shape1 = ShapeFactory.getShape("CIRCLE");
//調用 Circle 的 draw 方法
shape1.draw();
//獲取 Rectangle 的對象氯庆,并調用它的 draw 方法
Shape shape2 = ShapeFactory.getShape("RECTANGLE");
//調用 Rectangle 的 draw 方法
shape2.draw();
//獲取 Square 的對象蹭秋,并調用它的 draw 方法
Shape shape3 = ShapeFactory.getShape("SQUARE");
//調用 Square 的 draw 方法
shape3.draw();
}
}
步驟 5
驗證輸出扰付。
Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.
使用反射機制可以解決每次增加一個產品時,都需要增加一個對象實現(xiàn)工廠接口的缺點仁讨。注意要使用完整包名羽莺,也可以使用Properties文件做映射。
public class ShapeFactory {
public static Shape getShapeByClassName(String className) {
Shape obj = null;
try {
obj = (Shape)Class.forName(clazz.getName()).newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return obj;
}
}
2.工廠方法模式
介紹
意圖:定義了一個創(chuàng)建對象的接口洞豁,但由子類決定要實例化哪個類禽翼。工廠方法把實例化操作推遲到子類。
工廠方法和簡單工廠的區(qū)別:簡單工廠模式的實質是由一個工廠類根據(jù)傳入的參數(shù)族跛,動態(tài)決定應該創(chuàng)建哪一個產品類(這些產品類繼承自一個父類或接口)的實例闰挡。相當于是一個工廠中有各種產品,這樣工廠的職責較重礁哄,而且當產品類型過多時不利于系統(tǒng)的擴展維護长酗。如果增加一類產品,必須修改工廠類的方法桐绒。而工廠方法提供了一個工廠接口夺脾,定義了許多工廠實現(xiàn)類來負責生產不同的產品,這樣如果想要增加一類產品只需定義一個新的工廠實現(xiàn)類即可茉继。且簡單工廠返回產品的方法為static的咧叭,因為簡單工廠不需要創(chuàng)建工廠實例,而工廠方法要創(chuàng)建工廠實例烁竭。
工廠模式的實際應用:
JDBC中Connection的創(chuàng)建
特定的Driver創(chuàng)建對應的connection菲茬。
Spring的Bean管理
實現(xiàn)
在簡單工廠中,創(chuàng)建對象的是另一個類派撕,而在工廠方法中婉弹,是由子類來創(chuàng)建對象。
下圖中终吼,F(xiàn)actory 有一個 doSomething() 方法镀赌,這個方法需要用到一個產品對象,這個產品對象由 factoryMethod() 方法創(chuàng)建际跪。該方法是抽象的商佛,需要由子類去實現(xiàn)。
Factory.java
public abstract class Factory {
abstract public Product factoryMethod();
public void doSomething() {
Product product = factoryMethod();
// do something with the product
}
}
ConcreteFactory.java
public class ConcreteFactory extends Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
BrickFactory.java
public class BrickFactory extends Factory {
public Product factoryMethod() {
return new Brick();
}
}
SteelFactory.java
public class SteelFactory extends Factory {
public Product factoryMethod() {
return new Steel();
}
}
3.抽象工廠模式
抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠創(chuàng)建其他工廠姆打。該超級工廠又稱為其他工廠的工廠良姆。這種類型的設計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式穴肘。
在抽象工廠模式中歇盼,接口是負責創(chuàng)建一個相關對象的工廠舔痕,不需要顯式指定它們的類评抚。每個生成的工廠都能按照工廠模式提供對象豹缀。
抽象工廠模式是所有形態(tài)的工廠模式中最為抽象和最具一般性的一種形態(tài)。
抽象工廠模式創(chuàng)建的是對象家族慨代,也就是很多對象而不是一個對象邢笙,并且這些對象是相關的,也就是說必須一起創(chuàng)建出來侍匙。而工廠方法模式只是用于創(chuàng)建一個對象氮惯,這和抽象工廠模式有很大不同。
為了方便引進抽象工廠模式想暗,引進一個新概念:產品族(Product Family)妇汗。所謂產品族,是指位于不同產品等級結構说莫,功能相關聯(lián)的產品組成的家族杨箭。如圖:
圖中一共有四個產品族,分布于三個不同的產品等級結構中储狭。同一個產品族是同一個工廠生產的互婿,而不同等級結構來自不同的工廠。只要指明一個產品所處的產品族以及它所屬的等級結構辽狈,就可以唯一的確定這個產品慈参。
所謂的抽象工廠是指一個工廠等級結構可以創(chuàng)建出分屬于不同產品等級結構的一個產品族中的所有對象。如果用圖來描述的話刮萌,如下圖:
介紹
意圖:提供一個接口驮配,用于創(chuàng)建相關的對象家族。
主要解決:主要解決接口選擇的問題着茸。
何時使用:系統(tǒng)的產品有多于一個的產品族僧凤,而系統(tǒng)只消費其中某一族的產品。
如何解決:在一個產品族里面元扔,定義多個產品躯保。
關鍵代碼:在一個工廠里聚合多個同類產品。
應用實例:工作了澎语,為了參加一些聚會途事,肯定有兩套或多套衣服吧,比如說有商務裝(成套擅羞,一系列具體產品)尸变、時尚裝(成套,一系列具體產品)减俏,甚至對于一個家庭來說召烂,可能有商務女裝、商務男裝娃承、時尚女裝奏夫、時尚男裝怕篷,這些也都是成套的法梯,即一系列具體產品遭殉。假設一種情況(現(xiàn)實中是不存在的,要不然彩扔,沒法進入共產主義了麻削,但有利于說明抽象工廠模式)蒸痹,在您的家中,某一個衣柜(具體工廠)只能存放某一種這樣的衣服(成套呛哟,一系列具體產品)叠荠,每次拿這種成套的衣服時也自然要從這個衣柜中取出了。用 OO 的思想去理解扫责,所有的衣柜(具體工廠)都是衣柜類的(抽象工廠)某一個蝙叛,而每一件成套的衣服又包括具體的上衣(某一具體產品),褲子(某一具體產品)公给,這些具體的上衣其實也都是上衣(抽象產品)借帘,具體的褲子也都是褲子(另一個抽象產品)。
優(yōu)點:當一個產品族中的多個對象被設計成一起工作時淌铐,它能保證客戶端始終只使用同一個產品族中的對象肺然。
缺點:產品族擴展非常困難,要增加一個系列的某一產品腿准,既要在抽象的 Creator 里加代碼际起,又要在具體的里面加代碼。
使用場景: 1吐葱、QQ 換皮膚街望,一整套一起換。 2弟跑、生成不同操作系統(tǒng)的程序灾前。
注意事項:產品族難擴展,產品等級易擴展孟辑。
上圖的描述用產品族描述如下:
實現(xiàn)
我們來舉這樣一個例子哎甲,QQ秀有不同的裝扮,分為男孩和女孩饲嗽,而男孩又分為圣誕男孩和新年男孩炭玫,女孩分為圣誕女孩和新年女孩。那么就可以有一個抽象工廠生產男孩和女孩貌虾。兩個具體的工廠分別生產圣誕系列的男孩和女孩吞加、新年系列的男孩和女孩。同一系列的男孩和女孩是一個產品族,而不同系列構成不同的產品等級衔憨。
步驟 1
為男孩創(chuàng)建一個接口叶圃。
Boy.java
public interface Boy {
public void drawMan();
}
步驟 2
創(chuàng)建實現(xiàn)接口的實體類。
MCBoy.java
public class MCBoy implements Boy {
@Override
public void drawMan() {
System.out.println("-----------------圣誕系列的男孩子--------------------");
}
}
HNBoy.java
public class HNBoy implements Boy {
@Override
public void drawMan() {
System.out.println("-----------------新年系列的男孩子--------------------");
}
}
步驟 3
為女孩創(chuàng)建一個接口
Girl.java
public interface Girl {
public void drawWomen();
}
步驟 4
創(chuàng)建實現(xiàn)接口的實體類巫财。
MCGirl.java
public class MCGirl implements Girl {
@Override
public void drawWomen() {
System.out.println("-----------------圣誕系列的女孩子--------------------");
}
}
HNGirl.java
public class HNGirl implements Girl {
@Override
public void drawWomen() {
// TODO Auto-generated method stub
System.out.println("-----------------新年系列的女孩子--------------------");
}
}
步驟 5
創(chuàng)建生產男孩女孩的抽象工廠接口
PersonFactory.java
public interface PersonFactory {
//男孩接口
public Boy getBoy();
//女孩接口
public Girl getGirl();
}
步驟 6
創(chuàng)建生產圣誕和新年系列的具體工廠
MCFactory.java
public class MCFctory implements PersonFactory {
@Override
public Boy getBoy() {
return new MCBoy();
}
@Override
public Girl getGirl() {
return new MCGirl();
}
}
HNFactory.java
public class HNFactory implements PersonFactory {
@Override
public Boy getBoy() {
return new HNBoy();
}
@Override
public Girl getGirl() {
return new HNGirl();
}
}
步驟 7
使用工廠生產
AbstractFactoryPatternDemo.java
public class AbstractFactoryPatternDemo{
public static void main(String[] args){
MCFactory mcFactory = new MCFactory();
HNFactory hnFactory = new HNFactory();
Boy mcBoy = mcFactory.getBoy();
Girl mcGirl = mcFactory.getGirl();
Boy hnBoy = hnFactory.getBoy();
Girl hnGirl = hnFactory.getGirl();
mcBoy.drawMan();
mcGirl.drawWomen();
hnBoy.drawMan();
hnGirl.drawWomen();
}
}
步驟 8
驗證輸出
-----------------圣誕系列的男孩子--------------------
-----------------圣誕系列的女孩子--------------------
-----------------新年系列的男孩子--------------------
-----------------新年系列的女孩子--------------------
抽象工廠模式用到了工廠方法模式來創(chuàng)建單一對象盗似,PersonFactory 中的 getBoy() 和 getGirl() 方法都是讓子類來實現(xiàn)哩陕,這兩個方法單獨來看就是在創(chuàng)建一個對象平项,這符合工廠方法模式的定義。
至于創(chuàng)建對象的家族這一概念是在 AbstractFactoryPatternDemo 體現(xiàn)悍及, AbstractFactoryPatternDemo 要通過 PersonFactory 同時調用兩個方法來創(chuàng)建出兩個對象闽瓢,在這里這兩個對象就有很大的相關性, AbstractFactoryPatternDemo 需要同時創(chuàng)建出這兩個對象心赶。
從高層次來看扣讼,抽象工廠使用了組合,即 AbstractFactoryPatternDemo 組合了 PersonFactory缨叫,而工廠方法模式使用了繼承椭符。
注意:抽象工廠也可以運用反射,只不過反射的不再是產品類耻姥,而是不同的具體工廠類销钝。
4.單例模式
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬于創(chuàng)建型模式琐簇,它提供了一種創(chuàng)建對象的最佳方式蒸健。
這種模式涉及到一個單一的類,該類負責創(chuàng)建自己的對象婉商,同時確保只有單個對象被創(chuàng)建似忧。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問丈秩,不需要實例化該類的對象盯捌。
注意:
1、單例類只能有一個實例蘑秽。
2挽唉、單例類必須自己創(chuàng)建自己的唯一實例,而不是在外部隨意地new對象筷狼。
3瓶籽、單例類必須給所有其他對象提供這一實例。
介紹
意圖:保證一個類僅有一個實例埂材,并提供一個訪問它的全局訪問點塑顺。
主要解決:一個全局使用的類頻繁地創(chuàng)建與銷毀。
何時使用:當想控制實例數(shù)目,節(jié)省系統(tǒng)資源的時候严拒。
如何解決:判斷系統(tǒng)是否已經有這個單例扬绪,如果有則返回,如果沒有則創(chuàng)建裤唠。
關鍵代碼:使用一個私有構造函數(shù)挤牛、一個私有靜態(tài)變量以及一個公有靜態(tài)函數(shù)來實現(xiàn)。私有構造函數(shù)保證了不能通過構造函數(shù)來創(chuàng)建對象實例种蘸,只能通過公有靜態(tài)函數(shù)返回唯一的私有靜態(tài)變量墓赴。
應用實例: 1、一個黨只能有一個主席航瞭。 2诫硕、Windows 是多進程多線程的,在操作一個文件的時候刊侯,就不可避免地出現(xiàn)多個進程或線程同時操作一個文件的現(xiàn)象章办,所以所有文件的處理必須通過唯一的實例來進行。 3滨彻、一些設備管理器常常設計為單例模式藕届,比如一個電腦有兩臺打印機,在輸出的時候就要處理不能兩臺打印機打印同一個文件亭饵。
優(yōu)點: 1休偶、在內存里只有一個實例,減少了內存的開銷冬骚。2椅贱、避免頻繁的創(chuàng)建和銷毀實例,提高性能(比如管理學院首頁頁面緩存)只冻。 2庇麦、避免對資源的多重占用(比如寫文件操作)。
缺點:1喜德、擴展比較困難山橄,沒有接口,不能繼承舍悯,與單一職責原則沖突航棱,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化萌衬。2饮醇、如果實例化后的對象長期不利用,系統(tǒng)將默認為垃圾進行回收秕豫,造成對象狀態(tài)丟失朴艰。
使用場景: 1观蓄、當多個實例存在可能引起程序邏輯錯誤,如要求生產唯一序列號祠墅。 2侮穿、對系統(tǒng)內資源要求統(tǒng)一讀寫,如讀寫配置信息毁嗦,又如WEB 中的計數(shù)器亲茅,不用每次刷新都在數(shù)據(jù)庫里加一次,用單例先緩存起來狗准。 3克锣、創(chuàng)建的一個對象需要消耗的資源過多,但同時又需要用到該對象驶俊,比如 I/O 與數(shù)據(jù)庫的連接等娶耍。
注意事項:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多線程同時進入造成 instance 被多次實例化免姿。
實現(xiàn)
我們將創(chuàng)建一個 SingleObject 類饼酿。SingleObject 類有它的私有構造函數(shù)和本身的一個靜態(tài)實例。
SingleObject 類提供了一個靜態(tài)方法胚膊,供外界獲取它的靜態(tài)實例故俐。SingletonPatternDemo,我們的演示類使用 SingleObject 類來獲取 SingleObject 對象紊婉。
步驟 1
創(chuàng)建一個 Singleton 類药版。
SingleObject.java
public class SingleObject {
//創(chuàng)建 SingleObject 的一個對象
private static SingleObject instance = new SingleObject();
//讓構造函數(shù)為 private,這樣該類就不會被實例化
private SingleObject(){}
//獲取唯一可用的對象
public static SingleObject getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
步驟 2
從 singleton 類獲取唯一的對象喻犁。
SingletonPatternDemo.java
public class SingletonPatternDemo {
public static void main(String[] args) {
//不合法的構造函數(shù)
//編譯時錯誤:構造函數(shù) SingleObject() 是不可見的
//SingleObject object = new SingleObject();
//獲取唯一可用的對象
SingleObject object = SingleObject.getInstance();
//顯示消息
object.showMessage();
}
}
步驟 3
驗證輸出槽片。
Hello World!
單例模式的幾種實現(xiàn)方式
單例模式的實現(xiàn)有多種方式,如下所示:
1肢础、懶漢式还栓,線程不安全
是否 Lazy 初始化:是
是否多線程安全:否
實現(xiàn)難度:易
代碼實例:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) { /*懶漢式標志:Lazy 初始化,
在外部第一次請求使用該類對象時才實例化,是時間換空間的模式*/
instance = new Singleton();
}
return instance;
}
}
描述:這種方式是最基本的實現(xiàn)方式传轰,這種實現(xiàn)最大的問題就是不支持多線程剩盒。假設開始線程1進入,判斷instance為空慨蛙,在將要創(chuàng)建實例時辽聊,時間片切換,線程2又進來了期贫,同樣判斷instance為空跟匆,創(chuàng)建了實例,這是CPU調度回到線程1通砍,繼續(xù)創(chuàng)建實例玛臂。因為沒有加鎖 synchronized,所以嚴格意義上它并不算單例模式。
這種方式 lazy loading 很明顯垢揩,不要求線程安全玖绿,在多線程不能正常工作。
接下來介紹的幾種實現(xiàn)方式都支持多線程叁巨,但是在性能上有所差異斑匪。
2、懶漢式锋勺,線程安全
是否 Lazy 初始化:是
是否多線程安全:是
實現(xiàn)難度:易
代碼實例:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
//加同步鎖
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
描述:這種方式具備很好的 lazy loading蚀瘸,能夠在多線程中很好的工作,但是庶橱,效率很低贮勃,99% 情況下不需要同步。
優(yōu)點:第一次調用才初始化苏章,避免內存浪費寂嘉。
缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率枫绅,下一個線程想要取得對象泉孩,必須等上一個線程釋放鎖之后,才可以繼續(xù)執(zhí)行并淋。
getInstance() 的性能對應用程序不是很關鍵(該方法使用不太頻繁)寓搬。
3、餓漢式
是否 Lazy 初始化:否
是否多線程安全:是
實現(xiàn)難度:易
代碼實例:
public class Singleton {
private static Singleton instance = new Singleton();
/*餓漢式標志:在類加載時直接初始化县耽,
是空間換時間的模式*/
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
描述:這種方式比較常用句喷,但容易產生垃圾對象。
優(yōu)點:沒有加鎖兔毙,執(zhí)行效率會提高唾琼。
缺點:類加載時就初始化,浪費內存瞒御。
它基于 classloder 機制避免了多線程的同步問題父叙,不過,instance 在類裝載時就實例化肴裙,雖然導致類裝載的原因有很多種趾唱,在單例模式中大多數(shù)都是調用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導致類裝載蜻懦,這時候初始化 instance 顯然沒有達到 lazy loading 的效果甜癞。
4、靜態(tài)代碼塊
是否 Lazy 初始化:否
是否多線程安全:是
實現(xiàn)難度:易
代碼實例:
public class Singleton {
private static Singleton instance = null;
static{
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
描述:類似于餓漢式宛乃。
優(yōu)點:沒有加鎖悠咱,執(zhí)行效率會提高蒸辆。
缺點:類加載時就初始化,浪費內存析既。
5躬贡、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多線程安全:是
實現(xiàn)難度:較復雜
代碼實例:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
描述:這種方式采用雙鎖機制眼坏,安全且在多線程情況下能保持高性能拂玻。getInstance() 的性能對應用程序很關鍵。這種方法既能保證線程安全又能提高了效率宰译。
假設線程1進入方法檐蚜,instance為空,進入同步代碼塊沿侈,時間片切換闯第,線程2進來,instance為空缀拭,在同步代碼塊外被阻塞咳短,因為此時線程1正在里面。cup切換智厌,線程1執(zhí)行創(chuàng)建實例诲泌,當2再進入代碼塊后盲赊,此時instace不為空铣鹏,直接返回instance。當再有線程進來哀蘑,instance不為空诚卸,不用執(zhí)行同步代碼塊,提高了效率绘迁。
外層 if 語句是為了保證 instance 不為空時不執(zhí)行同步代碼塊合溺,直接返回對象,提高效率缀台;里層 if 語句則是為了防止之前已經進入外層 if 語句的線程重復實例化對象棠赛,保證單例。
注意:singleton 采用 volatile 關鍵字修飾是很有必要的膛腐!
這里涉及到了JVM編譯器的指令重排睛约。
簡單的一句 singleton = new Singleton(); 會被編譯器編譯成如下JVM指令:
memory =allocate(); //1:分配對象的內存空間
ctorInstance(memory); //2:初始化對象
instance =memory; //3:設置instance指向剛分配的內存地址
但是這些指令順序并非一成不變,有可能會經過JVM和CPU的優(yōu)化哲身,指令重排成下面的順序:
memory =allocate(); //1:分配對象的內存空間
instance =memory; //3:設置instance指向剛分配的內存地址
ctorInstance(memory); //2:初始化對象
當線程A執(zhí)行完1,3,時辩涝,instance對象還未完成初始化,但已經不再指向null勘天。此時如果線程B搶占到CPU資源怔揩,執(zhí)行 if(instance == null)的結果會是false捉邢,從而返回一個沒有初始化完成的instance對象。如下圖所示:
使用 volatile 可以禁止 JVM 的指令重排商膊,保證在多線程環(huán)境下也能正常運行伏伐。
6、登記式 / 靜態(tài)內部類
是否 Lazy 初始化:是
是否多線程安全:是
實現(xiàn)難度:一般
代碼實例:
public class Singleton {
private Singleton (){}
private static class LazyHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
描述:這里有幾個需要注意的點:
1晕拆、從外部無法訪問靜態(tài)內部類LazyHolder秘案,只有當調用Singleton.getInstance()方法的時候,才能得到單例對象INSTANCE潦匈。
2阱高、靜態(tài)內部類LazyHolder是懶加載的,它不隨外部類Singleton的加載而加載茬缩。所以INSTANCE對象不會在單例類Singleton被加載的時候就初始化赤惊,而是在調用getInstance()方法時,靜態(tài)內部類LazyHolder被加載的時候被初始化凰锡。因此這種實現(xiàn)方式是利用classloader的加載機制來實現(xiàn)懶加載未舟,并保證構建單例的線程安全。
7掂为、序列化與反序列化
是否 Lazy 初始化:是
是否多線程安全:是
實現(xiàn)難度:一般
代碼實例:
MyObject.java
SaveAndRead.java
運行結果
發(fā)現(xiàn)返回的不是同一個對象
去掉如下代碼的注釋
運行結果
返回的是同一個對象
分析
靜態(tài)內部類可以實現(xiàn)線程安全裕膀,但如果遇到序列化對象,使用默認的方法運行得到的結果還是多例的勇哗。如果既想要做到可序列化昼扛,又想要反序列化為同一對象,則必須實現(xiàn)readResolve方法欲诺。readResolve() 方法會緊挨著 readObject() 之后被調用抄谐,該方法的返回值將會代替原來反序列化的對象,而原來 readObject() 反序列化的對象將會被立即丟棄扰法。
8蛹含、枚舉
JDK 版本:JDK1.5 起
是否 Lazy 初始化:否
是否多線程安全:是
實現(xiàn)難度:易
代碼實例:
public enum Singleton {
INSTANCE;
}
描述:單元素的枚舉類型是實現(xiàn)單例模式的最佳方法。它更簡潔塞颁,自動支持序列化機制浦箱,絕對防止多次實例化。
這種方式是 Effective Java 作者 Josh Bloch 提倡的方式祠锣,它不僅能避免多線程同步問題酷窥,而且還自動支持序列化機制,該實現(xiàn)在多次序列化再進行反序列化之后锤岸,不會得到多個實例竖幔。而其它實現(xiàn),為了保證不會出現(xiàn)反序列化之后出現(xiàn)多個實例是偷,需要使用 transient 修飾所有字段拳氢,并且實現(xiàn)序列化和反序列化的方法募逞,來防止反序列化重新創(chuàng)建新的對象。不過馋评,由于 JDK1.5 之后才加入 enum 特性放接,用這種方式寫不免讓人感覺生疏,在實際工作中留特,也很少用纠脾。
該實現(xiàn)可以防止反射攻擊。在其它實現(xiàn)中蜕青,通過 setAccessible() 方法可以將私有構造函數(shù)的訪問級別設置為 public苟蹈,然后調用構造函數(shù)從而實例化對象,如果要防止這種攻擊右核,需要在構造函數(shù)中添加防止實例化第二個對象的代碼慧脱。但是該實現(xiàn)是由 JVM 保證只會實例化一次,因此不會出現(xiàn)上述的反射攻擊贺喝。
經驗之談:一般情況下菱鸥,不建議使用第 1 種和第 2 種懶漢方式,建議使用第 3 種餓漢方式躏鱼。只有在要明確實現(xiàn) lazy loading 效果時氮采,才會使用第 6 種登記方式。如果涉及到反序列化創(chuàng)建對象時染苛,可以嘗試使用第 8 種枚舉方式鹊漠。如果有其他特殊的需求,可以考慮使用第 5 種雙檢鎖方式殖侵。
5.建造者模式
建造者模式(Builder Pattern)使用多個簡單的對象一步一步構建成一個復雜的對象贸呢。這種類型的設計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式拢军。
一個 Builder 類會一步一步構造最終的對象。該 Builder 類是獨立于其他對象的怔鳖。
介紹
意圖:將一個復雜的構建與其表示相分離茉唉,使得同樣的構建過程可以創(chuàng)建不同的表示。
主要解決:主要解決在軟件系統(tǒng)中结执,有時候面臨著"一個復雜對象"的創(chuàng)建工作度陆,其通常由各個部分的子對象用一定的算法構成;由于需求的變化献幔,這個復雜對象的各個部分經常面臨著劇烈的變化懂傀,但是將它們組合在一起的算法卻相對穩(wěn)定。
何時使用:一些基本部件不會變蜡感,而其組合經常變化的時候蹬蚁。
如何解決:將變與不變分離開恃泪。
關鍵代碼:建造者:創(chuàng)建和提供實例,導演:管理建造出來的實例的依賴關系犀斋。
應用實例:
1贝乎、去肯德基,漢堡叽粹、可樂览效、薯條、炸雞翅等是不變的虫几,而其組合是經常變化的锤灿,生成出所謂的"套餐"。
2辆脸、JAVA 中的 StringBuilder衡招。
優(yōu)點:1、建造者獨立每强,易擴展始腾。 2、便于控制細節(jié)風險空执。
缺點: 1浪箭、產品必須有共同點,范圍有限制辨绊。 2奶栖、如內部變化復雜,會有很多的建造類门坷。
使用場景: 1宣鄙、需要生成的對象具有復雜的內部結構。 2默蚌、需要生成的對象內部屬性本身相互依賴冻晤。
注意事項:與工廠模式的區(qū)別是:建造者模式更加關注與零件裝配的順序。
實現(xiàn)
我們假設一個快餐店的商業(yè)案例绸吸,其中遣钳,一個典型的套餐可以是一個漢堡(Burger)和一杯冷飲(Cold drink)瞪醋。漢堡(Burger)可以是素食漢堡(Veg Burger)或雞肉漢堡(Chicken Burger),它們是包在紙盒中。冷飲(Cold drink)可以是可口可樂(coke)或百事可樂(pepsi)姻成,它們是裝在瓶子中撑毛。
我們將創(chuàng)建一個表示食物條目(比如漢堡和冷飲)的 Item 接口和實現(xiàn) Item 接口的實體類伪阶,以及一個表示食物包裝的 Packing 接口和實現(xiàn) Packing 接口的實體類扬霜,漢堡是包在紙盒中,冷飲是裝在瓶子中稿存。
然后我們創(chuàng)建一個 Meal 類笨篷,帶有 Item 的 ArrayList 和一個通過結合 Item 來創(chuàng)建不同類型的 Meal 對象的 MealBuilder瞳秽。BuilderPatternDemo,我們的演示類使用 MealBuilder 來創(chuàng)建一個 Meal冕屯。
步驟 1
創(chuàng)建一個表示食物條目和食物包裝的接口寂诱。
Item.java
public interface Item {
public String name();
public Packing packing();
public float price();
}
Packing.java
public interface Packing {
public String pack();
}
步驟 2
創(chuàng)建實現(xiàn) Packing 接口的實體類。
Wrapper.java
public class Wrapper implements Packing {
@Override
public String pack() {
return "Wrapper";
}
}
Bottle.java
public class Bottle implements Packing {
@Override
public String pack() {
return "Bottle";
}
}
步驟 3
創(chuàng)建實現(xiàn) Item 接口的抽象類安聘,該類提供了默認的功能痰洒。
Burger.java
public abstract class Burger implements Item {
@Override
public Packing packing() {
return new Wrapper();
}
@Override
public abstract float price();
}
ColdDrink.java
public abstract class ColdDrink implements Item {
@Override
public Packing packing() {
return new Bottle();
}
@Override
public abstract float price();
}
步驟 4
創(chuàng)建擴展了 Burger 和 ColdDrink 的實體類。
VegBurger.java
public class VegBurger extends Burger {
@Override
public float price() {
return 25.0f;
}
@Override
public String name() {
return "Veg Burger";
}
}
ChickenBurger.java
public class ChickenBurger extends Burger {
@Override
public float price() {
return 50.5f;
}
@Override
public String name() {
return "Chicken Burger";
}
}
Coke.java
public class Coke extends ColdDrink {
@Override
public float price() {
return 30.0f;
}
@Override
public String name() {
return "Coke";
}
}
Pepsi.java
public class Pepsi extends ColdDrink {
@Override
public float price() {
return 35.0f;
}
@Override
public String name() {
return "Pepsi";
}
}
步驟 5
創(chuàng)建一個 Meal 類浴韭,帶有上面定義的 Item 對象丘喻。
Meal.java
import java.util.ArrayList;
import java.util.List;
public class Meal {
private List<Item> items = new ArrayList<Item>();
public void addItem(Item item){
items.add(item);
}
public float getCost(){
float cost = 0.0f;
for (Item item : items) {
cost += item.price();
}
return cost;
}
public void showItems(){
for (Item item : items) {
System.out.print("Item : "+item.name());
System.out.print(", Packing : "+item.packing().pack());
System.out.println(", Price : "+item.price());
}
}
}
步驟 6
創(chuàng)建一個 MealBuilder 類,實際的 builder 類負責創(chuàng)建 Meal 對象念颈。
MealBuilder.java
public class MealBuilder {
public Meal prepareVegMeal (){
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
public Meal prepareNonVegMeal (){
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}
步驟 7
BuiderPatternDemo 使用 MealBuider 來演示建造者模式(Builder Pattern)泉粉。
BuilderPatternDemo.java
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();
Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total Cost: " +vegMeal.getCost());
Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost: " +nonVegMeal.getCost());
}
}
步驟 8
執(zhí)行程序,輸出結果:
Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0
Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.5
6.原型模式
原型模式(Prototype Pattern)是用于創(chuàng)建重復的對象榴芳,同時又能保證性能嗡靡。這種類型的設計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式窟感。
這種模式是實現(xiàn)了一個原型接口讨彼,該接口用于創(chuàng)建當前對象的克隆。當直接創(chuàng)建對象的代價比較大時柿祈,則采用這種模式哈误。例如,一個對象需要在一個高代價的數(shù)據(jù)庫操作之后被創(chuàng)建躏嚎。我們可以緩存該對象蜜自,在下一個請求時返回它的克隆,在需要的時候更新數(shù)據(jù)庫卢佣,以此來減少數(shù)據(jù)庫調用重荠。
介紹
意圖:用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象珠漂。
主要解決:在運行期建立和刪除原型晚缩。
何時使用: 1、當一個系統(tǒng)應該獨立于它的產品創(chuàng)建媳危,構成和表示時。 2冈敛、當要實例化的類是在運行時刻指定時待笑,例如,通過動態(tài)裝載抓谴。 3暮蹂、為了避免創(chuàng)建一個與產品類層次平行的工廠類層次時寞缝。 4、當一個類的實例只能有幾個不同狀態(tài)組合中的一種時仰泻。建立相應數(shù)目的原型并克隆它們可能比每次用合適的狀態(tài)手工實例化該類更方便一些荆陆。
如何解決:利用已有的一個原型對象,快速地生成和原型對象一樣的實例集侯。
關鍵代碼: 1被啼、實現(xiàn)克隆操作,繼承 Cloneable棠枉,重寫 clone() 2浓体、原型模式同樣用于隔離類對象的使用者和具體類型(易變類)之間的耦合關系,它同樣要求這些"易變類"擁有穩(wěn)定的接口辈讶。
應用實例: 1命浴、細胞分裂。 2贱除、JAVA 中的 Object clone() 方法生闲。
優(yōu)點:1、性能提高月幌。 2碍讯、逃避構造函數(shù)的約束。
缺點:1飞醉、配備克隆方法需要對類的功能進行通盤考慮冲茸,這對于全新的類不是很難,但對于已有的類不一定很容易缅帘,特別當一個類引用不支持串行化的間接對象轴术,或者引用含有循環(huán)結構的時候。 2钦无、必須實現(xiàn) Cloneable 接口逗栽。
使用場景:1、資源優(yōu)化場景失暂。 2彼宠、類初始化需要消化非常多的資源,這個資源包括數(shù)據(jù)弟塞、硬件資源等凭峡。 3、性能和安全要求的場景决记。 4摧冀、通過 new 產生一個對象需要非常繁瑣的數(shù)據(jù)準備或訪問權限,則可以使用原型模式。 5索昂、一個對象多個修改者的場景建车。 6、一個對象需要提供給其他對象訪問椒惨,而且各個調用者可能都需要修改其值時缤至,可以考慮使用原型模式拷貝多個對象供調用者使用。 7康谆、在實際項目中领斥,原型模式很少單獨出現(xiàn),一般是和工廠方法模式一起出現(xiàn)秉宿,通過 clone 的方法創(chuàng)建一個對象戒突,然后由工廠方法提供給調用者。原型模式已經與 Java 融為渾然一體描睦,大家可以隨手拿來使用膊存。
注意事項:與通過對一個類進行實例化來構造新對象不同的是,原型模式是通過拷貝一個現(xiàn)有對象生成新對象的忱叭。淺拷貝實現(xiàn) Cloneable隔崎,重寫,深拷貝是通過實現(xiàn) Serializable 讀取二進制流韵丑。
實現(xiàn)
我們將創(chuàng)建一個抽象類 Shape 和擴展了 Shape 類的實體類爵卒。下一步是定義類 ShapeCache,該類把 shape 對象存儲在一個 Hashtable 中撵彻,并在請求的時候返回它們的克隆钓株。
PrototypePatternDemo,我們的演示類使用 ShapeCache 類來獲取 Shape 對象陌僵。
步驟 1
創(chuàng)建一個實現(xiàn)了 Clonable 接口的抽象類轴合。
Shape.java
public abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public String getType(){
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
步驟 2
創(chuàng)建擴展了上面抽象類的實體類。
Rectangle.java
public class Rectangle extends Shape {
public Rectangle(){
type = "Rectangle";
}
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
Square.java
public class Square extends Shape {
public Square(){
type = "Square";
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
Circle.java
public class Circle extends Shape {
public Circle(){
type = "Circle";
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
步驟 3
創(chuàng)建一個類碗短,從數(shù)據(jù)庫獲取實體類受葛,并把它們存儲在一個 Hashtable 中。
ShapeCache.java
import java.util.Hashtable;
public class ShapeCache {
private static Hashtable<String, Shape> shapeMap
= new Hashtable<String, Shape>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
// 對每種形狀都運行數(shù)據(jù)庫查詢偎谁,并創(chuàng)建該形狀
// shapeMap.put(shapeKey, shape);
// 例如总滩,我們要添加三種形狀
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(),square);
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(),rectangle);
}
}
步驟 4
PrototypePatternDemo 使用 ShapeCache 類來獲取存儲在 Hashtable 中的形狀的克隆。
PrototypePatternDemo.java
public class PrototypePatternDemo {
public static void main(String[] args) {
ShapeCache.loadCache();
Shape clonedShape = (Shape) ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType());
Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType());
Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
System.out.println("Shape : " + clonedShape3.getType());
}
}
步驟 5
執(zhí)行程序巡雨,輸出結果:
Shape : Circle
Shape : Square
Shape : Rectangle