Christopher Alexander說過:“每一個模式描述了一個在我們周圍不斷重復(fù)發(fā)生的問題煌珊,以及該問題的解決方案的核心区宇。這樣你就能一次又一次地使用該方案而不必做重復(fù)勞動∫阌撸”
一般而言西篓,一個模式有四個基本要素:模式名稱、問題憋活、解決方案岂津、效果。
總體來說悦即,設(shè)計模式分為三大類:創(chuàng)建型模式吮成、結(jié)構(gòu)型模式橱乱、行為型模式。其中創(chuàng)建型模式抽象了實例化過程粱甫,它們幫助一個系統(tǒng)獨立于如何創(chuàng)建泳叠、組合和表示它的那些對象。一個類創(chuàng)建型模式使用繼承改變被實例化的類茶宵,而一個對象創(chuàng)建型模式將實例化委托給另一個對象危纫。創(chuàng)建型模式共五種:抽象工廠模式、建造者模式乌庶、工廠方法模式种蝶、原型模式、單例模式安拟。
一蛤吓、 抽象工廠模式
1.1 目的
抽象工廠模式是提供一個創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而無需指定它們具體的類糠赦。
1.2 適用性
(1) 一個系統(tǒng)要獨立于它的產(chǎn)品的創(chuàng)建会傲、組合和表示時;
(2) 一個系統(tǒng)要由多個產(chǎn)品系列中的一個來配置時拙泽;
(3) 當你強調(diào)一系列相關(guān)的產(chǎn)品對象的設(shè)計以便進行聯(lián)合使用時淌山;
(4) 當你提供一個產(chǎn)品類庫,而只想顯示它們的接口而不是實現(xiàn)時顾瞻。
1.3 結(jié)構(gòu)和實現(xiàn)
public interface Sender {
public void Send();
}
//兩個實現(xiàn)類:
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
//兩個工廠類:
public class SendMailFactory implements Provider {
@Override
public Sender produce(){
return new MailSender();
}
}
public class SendSmsFactory implements Provider{
@Override
public Sender produce() {
return new SmsSender();
}
}
//接口:
public interface Provider {
public Sender produce();
}
//測試類
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
1.4 優(yōu)缺點
(1) 分離了具體的類泼疑。如果你現(xiàn)在想增加一個功能:發(fā)及時信息,則只需做一個實現(xiàn)類荷荤,實現(xiàn)Sender接口退渗,同時做一個工廠類,實現(xiàn)Provider接口蕴纳,就OK了会油,無需去改動現(xiàn)成的代碼。這樣做古毛,拓展性較好翻翩。
(2) 使得易于交換產(chǎn)品系列。一個具體工廠類在一個應(yīng)用中僅出現(xiàn)一次稻薇。
(3) 有利于產(chǎn)品的一致性嫂冻。當一個系列中的產(chǎn)品對象被設(shè)計成一起工作時,一個應(yīng)用只能使用同一個系列中的對象塞椎。
(4) 難以支持新種類的產(chǎn)品桨仿。難以擴展抽象工廠以生產(chǎn)新種類的產(chǎn)品。因為抽象工廠方法確定了可以被創(chuàng)建的產(chǎn)品集合案狠,但是支持新種類的產(chǎn)品需要擴展該工廠接口服傍,這涉及抽象工廠類及其所有子類的改變暇昂。
二、 建造者模式
2.1 目的
工廠類模式提供的是創(chuàng)建單個類的模式伴嗡,而建造者模式則是將各種產(chǎn)品集中起來進行管理,用來創(chuàng)建復(fù)合對象从铲,所謂復(fù)合對象就是指某個類具有不同的屬性瘪校,其實建造者模式就是前面抽象工廠模式和最后結(jié)合起來得到的。將一個復(fù)雜對象的構(gòu)建與它的表示分離名段,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示阱扬。
2.2 適用性
(1) 當創(chuàng)建復(fù)雜對象的算法應(yīng)該獨立于該對象的組成部分以及它們的裝配方式時;
(2) 當構(gòu)造過程必須允許被構(gòu)造的對象有不同的表示時伸辟。
2.3 代碼實現(xiàn)
public class Builder {
private List<Sender> list = new ArrayList<Sender>();
public void produceMailSender(int count){
for(int i=0; i<count; i++){
list.add(new MailSender());
}
}
public void produceSmsSender(int count){
for(int i=0; i<count; i++){
list.add(new SmsSender());
}
}
}
//測試類:
public class Test {
public static void main(String[] args) {
Builder builder = new Builder();
builder.produceMailSender(10);
}
}
2.4 優(yōu)點
(1) 它使你可以改變一個產(chǎn)品的內(nèi)部表示麻惶。Builder對象提供給導(dǎo)向器一個構(gòu)造產(chǎn)品的抽象接口。該接口使得生成器可以隱藏這個產(chǎn)品的表示和內(nèi)部結(jié)構(gòu)信夫,同時也隱藏了該產(chǎn)品是如何裝配的窃蹋。
(2) 它將構(gòu)造代碼和表示代碼分開。Builder模式通過封裝一個復(fù)雜對象的創(chuàng)建和表示方式提高了對象的模塊性静稻【唬客戶不需要知道定義產(chǎn)品內(nèi)部結(jié)構(gòu)的類的所有信息。
(3) 它使你可對構(gòu)造過程進行更精細的控制振湾。
三杀迹、 工廠方法模式
3.1 目的
定義一個用于創(chuàng)建對象的接口,讓子類決定實例化哪一個類押搪,工廠方法使一個類的實例化延遲到其子類树酪。
3.2 適用性
(1) 當一個類不知道它所必須創(chuàng)建的對象的類的時候;
(2) 當一個類希望由它的子類來指定它所創(chuàng)建的對象的時候大州。
3.3 結(jié)構(gòu)與實現(xiàn)
(1) 普通工廠模式续语,就是建立一個工廠類,對實現(xiàn)了同一接口的一些類進行實例的創(chuàng)建摧茴。首先看下關(guān)系圖:
//共同接口:
public interface Sender {
public void Send();
}
//創(chuàng)建實現(xiàn)類:
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
//建工廠類:
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("請輸入正確的類型!");
return null;
}
}
}
//測試:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("sms");
sender.Send();
}
}
(2)多個工廠方法模式绵载,是對普通工廠方法模式的改進,在普通工廠方法模式中苛白,如果傳遞的字符串出錯娃豹,則不能正確創(chuàng)建對象,而多個工廠方法模式是提供多個工廠方法购裙,分別創(chuàng)建對象懂版。關(guān)系圖:
將上面的代碼做下修改,改動下SendFactory類就行躏率,如下:
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
//測試類如下:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
(3)靜態(tài)工廠方法模式躯畴,將上面的多個工廠方法模式里的方法置為靜態(tài)的民鼓,不需要創(chuàng)建實例,直接調(diào)用即可蓬抄。
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
大多數(shù)情況下丰嘉,我們會選用第三種——靜態(tài)工廠方法模式。
四嚷缭、 原型模式
4.1 目的
原始模型模式通過給出一個原型對象來指明所要創(chuàng)建的對象的類型饮亏,然后用復(fù)制這個原型對象的方法創(chuàng)建出更多同類型的對象。原始模型模式允許動態(tài)的增加或減少產(chǎn)品類阅爽,產(chǎn)品類不需要非得有任何事先確定的等級結(jié)構(gòu)路幸,原始模型模式適用于任何的等級結(jié)構(gòu)。
4.2 適用性
(1) 當要實例化的類是運行時刻指定時付翁,例如简肴,通過動態(tài)裝載;或者
(2) 為了避免創(chuàng)建一個與產(chǎn)品類層次平行的工廠類層次時百侧;或者
(3) 當一個類的實例只能有幾個不同狀態(tài)組合中的一種時砰识。建立相應(yīng)數(shù)目的原型并克隆它們可能比每次用合適的狀態(tài)手工實例化該類更方便一些。
4.3 代碼實現(xiàn)
//創(chuàng)建一個原型類:
public class Prototype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
}
一個原型類只需要實現(xiàn)Cloneable接口移层,覆寫clone方法仍翰,重點是調(diào)用super.clone(),進而調(diào)用Object的clone()方法观话。結(jié)合對象的淺復(fù)制和深復(fù)制詳細介紹一下:
- 淺復(fù)制:將一個對象復(fù)制后予借,基本數(shù)據(jù)類型的變量都會重新創(chuàng)建,而引用類型频蛔,指向的還是原對象所指向的灵迫。
- 深復(fù)制:將一個對象復(fù)制后,不論是基本數(shù)據(jù)類型還有引用類型晦溪,都是重新創(chuàng)建的瀑粥。簡單來說,就是深復(fù)制進行了完全徹底的復(fù)制三圆,而淺復(fù)制不徹底狞换。要實現(xiàn)深復(fù)制,需要采用流的形式讀入當前對象的二進制輸入舟肉,再寫出二進制數(shù)據(jù)對應(yīng)的對象修噪。
//深淺復(fù)制的例子:
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String string;
private SerializableObject obj;
/* 淺復(fù)制 */
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
/* 深復(fù)制 */
public Object deepClone() throws IOException, ClassNotFoundException {
/* 寫入當前對象的二進制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 讀出二進制流產(chǎn)生的新對象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
}
class SerializableObject implements Serializable {
private static final long serialVersionUID = 1L;
}
4.4 優(yōu)點
(1) 運行時刻增加和刪除產(chǎn)品。原型模式允許只通過客戶注冊原型實例就可以將一個新的具體產(chǎn)品類并入系統(tǒng)路媚。
(2) 改變值以指定新對象黄琼。高度動態(tài)的系統(tǒng)允許你通過對象復(fù)合定義新的行為。
(3) 改變構(gòu)造以指定新對象整慎。許多應(yīng)用由部件和子部件來創(chuàng)建對象脏款。
(4) 減少子類的構(gòu)造围苫。
(5) 用類動態(tài)配置應(yīng)用。
五撤师、 單例模式
5.1 目的
保證一個類僅有一個實例剂府,并提供一個訪問它的全局訪問點。單例對象能保證在一個JVM中剃盾,該對象只有一個實例存在周循。單例模式具有以下幾個特點:
1、單例類有且僅有一個實例万俗。
2饮怯、單例類必須自己創(chuàng)建自己的唯一實例闰歪。
3蓖墅、單例類必須給所有其他對象提供單一實例库倘。
5.2 適用性
(1) 對唯一實例的受控訪問。單例類封裝它的唯一實例论矾,所以它可以嚴格的控制客戶怎么以及何時訪問它教翩。
(2) 縮小名空間。避免了那些存儲唯一實例的全局變量污染名空間贪壳。
(3) 允許對操作和表示的精華饱亿。單例類可以有子類,而且用這個擴展類的實例來配置一個應(yīng)用是很容易的闰靴。
(4) 允許可變數(shù)目的實例彪笼。
(5) 比類操作更靈活。
5.3 實現(xiàn)
單例分為兩種:懶漢式單例蚂且、餓漢式單例配猫。
5.3.1 懶漢單例
首先介紹下什么是延遲加載,延遲加載是程序真正需要使用的時才去創(chuàng)建實例杏死,不用時不創(chuàng)建實例泵肄。依據(jù)延遲加載可以把懶漢單例分為以下幾類。
(1) 同步延遲加載單例
public class Singleton {
/* 持有私有靜態(tài)實例淑翼,防止被引用腐巢,此處賦值為null,目的是實現(xiàn)延遲加載 */
private static Singleton instance = null;
/* 私有構(gòu)造方法窒舟,防止被實例化 */
private Singleton() {
}
/* 靜態(tài)工程方法系忙,創(chuàng)建實例 */
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
(2) 雙重檢測同步延遲加載單例
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 1
synchronized (Singleton.class) {
if (instance == null) {// 2
instance = new Singleton(); // 3
}
}
}
return instance;
}
}
雙重檢測同步延遲加載單例線程并非絕對安全;不能防止反序列化惠豺、反射產(chǎn)生新的實例银还。
線程不安全原因是Java的亂序執(zhí)行风宁、初始化對象需要時間。
對于語句3蛹疯,JVM在執(zhí)行時大致做了下述三件事:
a. 在內(nèi)存中分配一塊內(nèi)存戒财;
b. 調(diào)用構(gòu)造方法;
c. 將內(nèi)存的地址指向instance變量捺弦。(執(zhí)行這一步后饮寞,instance != null)
如果按照abc的順序執(zhí)行也不會有什么問題。但由于Java亂序執(zhí)行的機制列吼,有可能在真實情況下執(zhí)行順序為acb幽崩。
假設(shè)t1、t2是兩個線程寞钥。t1執(zhí)行到1時慌申,發(fā)現(xiàn)為null,于是執(zhí)行到語句3理郑,先執(zhí)行a,再執(zhí)行c,在還沒有執(zhí)行b時蹄溉,時間片給了t2。這時您炉,由于instance已經(jīng)分配了地址空間柒爵,instance不為null了。所以t2在執(zhí)行到語句1后直接return instance赚爵,獲得了這個還沒有被初始化的對象棉胀,然后在使用時就報錯了。因為有可能得到了實例的正確引用冀膝,但卻訪問到其成員變量的不正確值膏蚓。
(3) 內(nèi)部類實現(xiàn)延遲加載單例
public class Singleton {
private Singleton() {}
public static class Holder {
// 這里的私有沒有什么意義
/* private */static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
// 外圍類能直接訪問內(nèi)部類(不管是否是靜態(tài)的)的私有變量
return Holder.instance;
}
}
這種比上面1、2都好一些畸写,既實現(xiàn)了線程安全驮瞧,又避免了同步帶來的性能影響。
5.3.2 餓漢模式
(1) 非延遲加載單例枯芬,也稱為餓漢模式
public class Singleton {
private Singleton() {}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
餓漢模式在類創(chuàng)建的同時就已經(jīng)創(chuàng)建好一個靜態(tài)的對象供系統(tǒng)使用论笔,以后不再改變,所以天生是線程安全的千所。但是如果類創(chuàng)建時失敗狂魔,則永遠無法獲得該類的實例化。
5.4 優(yōu)點
(1)減少了類的頻繁創(chuàng)建淫痰,特別是減少大型對象的創(chuàng)建最楷,很大程度上節(jié)約了系統(tǒng)開銷。
(2)減少使用new操作符,降低了系統(tǒng)內(nèi)存的使用頻率籽孙,減輕GC壓力烈评。
(3)保證系統(tǒng)核心服務(wù)獨立控制整個系統(tǒng)操作流程,如果有多個實例的話犯建,系統(tǒng)將出現(xiàn)混亂讲冠。如同一個軍隊出現(xiàn)了多個司令員同時指揮,肯定會亂成一團适瓦,所以系統(tǒng)的核心控制模塊必須使用單例模式竿开,才能保證系統(tǒng)正常運行。