繼上一篇 Android常用設(shè)計模式(一)里認(rèn)識了觀察者,適配器牛郑,代理等三種模式独令,這一篇將會講解以下三種模式:
- 工廠模式
- 單例模式
- 命令模式
1.工廠模式(Factory Pattern)
工廠模式分為簡單工廠模式吐辙,工廠方法模式以及抽象工廠模式
- 簡單工廠模式:一般情況下妒蔚,提供一個方法,方法的參數(shù)是一個標(biāo)志位残揉,根據(jù)標(biāo)志位來創(chuàng)建不同的對象胧后,這樣調(diào)用的時候只需要提供一個標(biāo)志位就可以創(chuàng)建一個實現(xiàn)了接口的類。
- 工廠方法模式:將簡單工廠模式的那個方法分開抱环,不再是在工廠方法中根據(jù)標(biāo)志位創(chuàng)建對象了壳快。而是定義一個工廠接口,然后想創(chuàng)建幾個不同類型的對象(即實現(xiàn)了同一接口的不同java類)镇草,就創(chuàng)建了幾個不同類型的工廠眶痰。也就是創(chuàng)建的對象和創(chuàng)建對象的工廠是一一對應(yīng)的。然后客戶端調(diào)用的時候直接去實例化一個具體的對象工廠梯啤,創(chuàng)建相對應(yīng)的對象竖伯。
- 抽象工廠模式:其實這個名起的有點不知所云沒有表達出這個模式的特點。其實這個模式就是工廠方法模式的稍微擴展一下而已因宇。工廠方法模式里面七婴,一般一個工廠接口只有一個方法,比如createMouse()羽嫡。然后用實現(xiàn)了這個接口的具體工廠類只能生產(chǎn)鼠標(biāo)本姥。而抽象工廠模式就是一個工廠接口有多個方法,比如createMouse() , createKeyboard() 杭棵。 這樣實現(xiàn)了這個工廠接口的具體工廠類就可以既生產(chǎn)鼠標(biāo)又生產(chǎn)鍵盤。
常見實例:比如android的bitmap中常用的BitmapFactory類氛赐,創(chuàng)建Bitmap對象魂爪,通常使用靜態(tài)工廠方法.
這里主要介紹簡單工廠與工廠方法的區(qū)別:
就以大話模式中小菜跟大鳥舉得雷鋒故事作為題材吧。
LeiFeng類:
//雷鋒
public interface LeiFeng {
void sweep(); //掃地
void wash(); //洗衣
void buyrice(); //做飯
}
Student類:
//學(xué)做雷鋒的大學(xué)生
public class Student implements LeiFeng {
public void buyrice() {
System.out.println("大學(xué)生做飯");
}
public void sweep() {
// TODO Auto-generated method stub
System.out.println("大學(xué)生掃地");
}
public void wash() {
// TODO Auto-generated method stub
System.out.println("大學(xué)生洗衣");
}
}
Valuator類志愿者:
//學(xué)做雷鋒的志愿者
public class Valuator implements LeiFeng {
public void buyrice() {
System.out.println("志愿者做飯");
}
public void sweep() {
// TODO Auto-generated method stub
System.out.println("志愿者掃地");
}
public void wash() {
// TODO Auto-generated method stub
System.out.println("志愿者洗衣");
}
}
然后簡單工廠是這么實現(xiàn)的:
//使用簡單工廠
public class SimpleFactory {
public static LeiFeng createLeiFeng(String type) {
if ("大學(xué)生".equals(type)) {
return new Student();
} else if ("志愿者".equals(type)) {
return new Valuator();
}
return null;
}
}
而工廠方法模式中艰管,則多了一個接口去創(chuàng)建不同類型的對象:
Factory類:
//工廠方法模式滓侍,工廠接口
public interface Factory {
LeiFeng createLeiFengFactory();
}
StudentFactory學(xué)生工廠:
//學(xué)生工廠
public class StudentFactory implements Factory {
public LeiFeng createLeiFengFactory() {
return new Student();
}
}
ValuatorFactory志愿者工廠:
//志愿者工廠
public class ValuatorFactory implements Factory {
public LeiFeng createLeiFengFactory() {
return new Valuator();
}
}
當(dāng)我們實現(xiàn)起來時:
public static void main(String[] args) {
//簡單工廠模式
LeiFeng f11 = SimpleFactory.createLeiFeng("大學(xué)生");
f11.buyrice();
LeiFeng f22 = SimpleFactory.createLeiFeng("大學(xué)生");
f22.wash();
//使用工廠方法模式
Factory fac = new StudentFactory();
LeiFeng f4 = fac.createLeiFengFactory();
f4.buyrice();
LeiFeng f5 = fac.createLeiFengFactory();
f5.wash();
}
這里就要說說為什么要使用工廠方法模式,因為簡單工廠使用起來明顯要方便簡約的多牲芋。從理論的角度來說撩笆,工廠方法模式更符合封閉-開放原則捺球。即對修改封閉對擴展開放。試想后期維護過程中要增加一個種類的對象夕冲,也就是增加對接口的一種實現(xiàn)氮兵,簡單工廠模式就要在switch...case中增加一個case項,無疑是修改了工廠方法歹鱼。如果是jar包模式的泣栈,就要重新發(fā)包了。但是工廠方法模式弥姻,完全不需要更改工廠接口南片,只是新增加一個實現(xiàn)的工廠類即可(如果是jar包模式的,就可以不用重新發(fā)jar包庭敦,讓用jar包的人自己去擴展一個實現(xiàn)了工廠接口的具體工廠類即可)疼进。完全符合封閉-擴展原則。
2.單例模式(Single Pattern)
釋義:
單例模式確保某一個類只有一個實例秧廉,而且自行實例化并向整個系統(tǒng)提供這個實例單例模式颠悬。單例模式只應(yīng)在有真正的“單一實例”的需求時才可使用。
故事理解:
俺有6個漂亮的老婆定血,她們的老公都是我赔癌,我就是我們家里的老公Sigleton,她們只要說道“老公”澜沟,都是指的同一個人灾票,那就是我
常見實例:
數(shù)據(jù)庫創(chuàng)建時使用單例模式,Servlet環(huán)境下共享同一個資源或者對象*
適用場景*:
對于定義的一個類茫虽,在整個應(yīng)用程序執(zhí)行期間只有唯一的一個實例對象刊苍。如Android中常見的Application對象。
單例模式可分為餓漢式濒析,懶漢式等:
(一)餓漢式:其特點是應(yīng)用中尚未需要用到此單一實例的時候即先實例化正什。
public class SingleTon {
// 靜態(tài)實例變量,直接初始化
private static SingleTon instance = new SingleTon();
// 私有化構(gòu)造函數(shù)
private SingleTon() {
}
// 靜態(tài)public方法号杏,向整個應(yīng)用提供單例獲取方式
public static SingleTon getInstance() {
return instance;
}
}
(二)懶漢式:其特點是延遲加載婴氮,即當(dāng)需要用到此單一實例的時候,才去初始化此單一實例盾致。
public class SingletonA {
/**
* 單例對象實例
*/
private static SingletonA instance = null;
public static SingletonA getInstance() {
if (instance == null) { //line 12
instance = new SingletonA(); //line 13
}
return instance;
}
}
在這里要說下懶漢式主经,因為它具有一定的缺陷,我們可以假設(shè)這樣的場景:兩個線程并發(fā)調(diào)用Singleton.getInstance()庭惜,假設(shè)線程一先判斷完instance是否為null罩驻,既代碼中的line 12進入到line 13的位置。剛剛判斷完畢后护赊,JVM將CPU資源切換給線程二惠遏,由于線程一還沒執(zhí)行l(wèi)ine 13砾跃,所以instance仍然是空的,因此線程二執(zhí)行了new Signleton()操作节吮。片刻之后抽高,線程一被重新喚醒,它執(zhí)行的仍然是new Signleton()操作课锌。
所以對它進行了改良:
public class SingletonB {
/**
* 單例對象實例
*/
private static SingletonB instance = null;
public synchronized static SingletonB getInstance() {
if (instance == null) { //line 12
instance = new SingletonB(); //line 13
}
return instance;
}
}
往方法上加了個同步鎖厨内,這樣就可以保證不會出線程問題了,但是這里有個很大(至少耗時比例上很大)的性能問題渺贤。除了第一次調(diào)用時是執(zhí)行了SingletonB的構(gòu)造函數(shù)之外雏胃,以后的每一次調(diào)用都是直接返回instance對象。返回對象這個操作耗時是很小的志鞍,絕大部分的耗時都用在synchronized修飾符的同步準(zhǔn)備上瞭亮,因此從性能上說很不劃算。
所以又進行了改進:
public class SingletonC {
/** * 單例對象實例 */
private static SingletonKerriganD instance = null;
public static SingletonC getInstance() {
if (instance == null) {
synchronized(SingletonC.class) {
if (instance == null) {
instance = new SingletonC();
}
}
}
return instance;
}
}
目前我用的版本也就是這種固棚。然而统翩,網(wǎng)上有人又對這種單例模式進行了改進,因為還是存在缺陷此洲,具體可以去網(wǎng)上拓展下厂汗。再說說餓漢式的寫法,這種寫法不會出現(xiàn)并發(fā)問題呜师,在ClassLoader加載類后實例就會第一時間被創(chuàng)建娶桦。但餓漢式的創(chuàng)建方式在一些場景中將無法使用:譬如實例的創(chuàng)建是依賴參數(shù)或者配置文件的,在getInstance()之前必須調(diào)用某個方法設(shè)置參數(shù)給它汁汗,那樣這種單例寫法就無法使用了衷畦。
3.命令模式(Command Pattern)
釋義:
把一個請求或者操作封裝到一個對象中。命令模式把發(fā)出命令的責(zé)任和執(zhí)行命令的責(zé)任分割開知牌,委派給不同的對象祈争。命令模式允許請求的一方和發(fā)送的一方獨立開來,使得請求的一方不必知道接收請求的一方的接口角寸,更不必知道請求是怎么被接收菩混,以及操作是否執(zhí)行,何時被執(zhí)行以及是怎么被執(zhí)行的袭厂。
故事理解:
俺有一個MM家里管得特別嚴(yán)墨吓,沒法見面,只好借助于她弟弟在我們倆之間傳送信息纹磺,她對我有什么指示,就寫一張紙條讓她弟弟帶給我亮曹。這不橄杨,她弟弟又傳送過來一個COMMAND秘症,為了感謝他,我請他吃了碗雜醬面式矫,哪知道他說:“我同時給我姐姐三個男朋友送COMMAND乡摹,就數(shù)你最小氣,才請我吃面采转。
常見實例:
常用的Runnable(在java.lang包下)聪廉,其實就是用了命令模式,具體的體現(xiàn)過程故慈,可見該博客Runnable下的命令設(shè)計模式**
適用場景**:
- 命令的發(fā)送者和命令執(zhí)行者有不同的生命周期板熊。命令發(fā)送了并不是立即執(zhí)行。
- 命令需要進行各種管理邏輯察绷。
- 需要支持撤消\重做操作(這種狀況的代碼大家可以上網(wǎng)搜索下干签,有很多,這里不進行詳細(xì)解讀)拆撼。
其實經(jīng)典的命令模式包括4個角色:
Command:定義命令的統(tǒng)一接口
ConcreteCommand:Command接口的實現(xiàn)者容劳,用來執(zhí)行具體的命令,某些情況下可以直接用來充當(dāng)Receiver闸度。
Receiver:命令的實際執(zhí)行者
Invoker:命令的請求者竭贩,是命令模式中最重要的角色句惯。這個角色用來對各個命令進行控制雨膨。
接下來,就以小菜大鳥去燒烤店购啄,給服務(wù)員報菜睁宰,然后服務(wù)員通知廚師為例子肪获。
Command類 :
/*
* 抽象命令
*/
abstract class Command {
protected Barbecuer barbecuer;
public Command(Barbecuer barbecuer) {
this.barbecuer = barbecuer;
}
// 執(zhí)行命令
public abstract void excuteCommand();
}
(Receiver類)Barbecuer:
/*
* 烤肉串者
*/
class Barbecuer {
// 烤羊肉串
public void bakeMutton() {
System.out.println("烤羊肉串!");
}
// 烤雞翅
public void bakeChickenWing() {
System.out.println("烤雞翅!");
}
}
(ConcreteCommand類)BakeMuttonCommand、BakeChickenWingCommand:
/*
* 烤羊肉串命令
*/
class BakeMuttonCommand extends Command {
public BakeMuttonCommand(Barbecuer barbecuer) {
super(barbecuer);
}
@Override public void excuteCommand() {
barbecuer.bakeMutton();
}
@Override public String toString() {
return "命令模式柒傻,烤羊肉串命令孝赫!";
}
}
/*
* 烤雞翅命令
*/
class BakeChickenWingCommand extends Command {
public BakeChickenWingCommand(Barbecuer barbecuer) {
super(barbecuer);
}
@Override public void excuteCommand() {
barbecuer.bakeChickenWing();
}
@Override public String toString() {
return "命令模式,烤雞翅命令红符! ";
}
}
**(Invoker類)Waiter **:
/*
* 服務(wù)員
*/
class Waiter {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private List < Command > orders = new ArrayList < Command > ();
// 設(shè)定訂單
public void setOrder(Command command) {
orders.add(command);
System.out.println("增加訂單:" + command.toString() + " \t時間:" + simpleDateFormat.format(new Date()));
}
// 取消訂單
public void cancelOrder(Command command) {
orders.remove(command);
System.out.println("取消訂單:" + command.toString() + " \t時間:" + simpleDateFormat.format(new Date()));
}
// 通知全部執(zhí)行
public void notifyA() {
for (Command command: orders) {
command.excuteCommand();
}
}
}
Client類實現(xiàn)(如下):
public class CommandMode {
public static void main(String[] args) {
// 開店前準(zhǔn)備
Barbecuer boy = new Barbecuer();
Command bakeMuttonCommand1 = new BakeMuttonCommand(boy);
Command bakeMuttonCommand2 = new BakeMuttonCommand(boy);
Command bakeChickenWingCommand = new BakeChickenWingCommand(boy);
Waiter girl = new Waiter();
// 開門營業(yè)
girl.setOrder(bakeMuttonCommand1);
girl.setOrder(bakeMuttonCommand2);
girl.setOrder(bakeChickenWingCommand);
// 點菜完畢青柄,通知廚房
girl.notifyA();
}
}
總結(jié)來說,命令模式是將功能提升到對象來操作预侯,以便對多個功能進行一系列的處理以及封裝致开。
這里要建議下命令模式的使用,當(dāng)我們不清楚是否需要使用時萎馅,一般不用著急去實現(xiàn)它双戳。事實上,在需要的時候通過重構(gòu)實現(xiàn)這個模式并不困難糜芳,只有在真正需要如撤銷/恢復(fù)操作等功能時飒货,把原來的代碼重構(gòu)為命令模式才有意義魄衅。
最后,設(shè)計模式的運用塘辅,有助于代碼的維護與拓展晃虫。任何模式的出現(xiàn),都是為了解決一些特定的場景的耦合問題扣墩,以達到對修改封閉哲银,對擴展開放的效果。
六種模式呻惕,學(xué)習(xí)它們荆责,提高自己!除了這六種蟆融,還有幾種比較常用的草巡,接下來會繼續(xù)思考,繼續(xù)熟悉型酥!
如果覺得此文不錯山憨,麻煩幫我點下“喜歡”。么么噠弥喉!