摘要:的設(shè)計(jì)模式世界頂級(jí)足球射門(mén)錦集。重構(gòu)敏捷軟件開(kāi)發(fā)設(shè)計(jì)模式解析一場(chǎng)場(chǎng)最精彩的足球比賽。但不屬于種設(shè)計(jì)模式之一。代理設(shè)計(jì)模式代理模式,為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。
前言
編程是一門(mén)技術(shù),更是一門(mén)藝術(shù)。
如果想成為一名更優(yōu)秀的軟件設(shè)計(jì)師,了解優(yōu)秀軟件設(shè)計(jì)的演變過(guò)程比學(xué)習(xí)優(yōu)秀設(shè)計(jì)本身更有價(jià)值,因?yàn)樵O(shè)計(jì)的演變過(guò)程中蘊(yùn)藏著大智慧。
學(xué)習(xí)設(shè)計(jì)模式,重要的不是你將來(lái)會(huì)不會(huì)用得到這些模式,而是通過(guò)這些模式讓你找到“封裝變化”,“對(duì)象間松散耦合”,“針對(duì)接口編程”的感覺(jué),從而設(shè)計(jì)出易維護(hù),易擴(kuò)展,易復(fù)用,靈活性好的程序。
成為詩(shī)人后可能不需要刻意地按照某種模式去創(chuàng)作,但成為詩(shī)人前他們一定是認(rèn)真地研究過(guò)成百上千的唐詩(shī)宋詞,古今名句。
GOF的《設(shè)計(jì)模式》:世界頂級(jí)足球射門(mén)錦集。
《重構(gòu)》《敏捷軟件開(kāi)發(fā)》《設(shè)計(jì)模式解析》:一場(chǎng)場(chǎng)最精彩的足球比賽。
球迷(軟件使用者)——>足球運(yùn)動(dòng)員(軟件設(shè)計(jì)編程者)——>球星(軟件架構(gòu)師)
類(lèi)圖:
類(lèi)圖分三層。
第一層顯示類(lèi)的名稱,如果是抽象類(lèi),用斜體表示。
第二層是類(lèi)的特性,通常是字段和屬性。
第三層是類(lèi)的操作,通常是方法和行為。
注意:"+"表示public,"-"表示private,"#"表示protected。
接口圖:
與類(lèi)圖的區(qū)別主要是頂端有<
第一行:接口名稱。
第二行:接口方法。
接口還有另外一種表示方法,俗稱棒棒糖表示法。
類(lèi)與類(lèi),類(lèi)與接口的關(guān)系:
繼承關(guān)系:空心三角+實(shí)線來(lái)表示。
實(shí)現(xiàn)接口:空心三角+虛線表示。
關(guān)聯(lián)關(guān)系:實(shí)線箭頭表示。
聚合關(guān)系:用空心菱形+實(shí)線箭頭表示。
大雁和雁群。聚合表示一種弱的"擁有"關(guān)系,體現(xiàn)的是A對(duì)象可以包含B對(duì)象,但B對(duì)象不是A對(duì)象的一部分。
合成(組合)關(guān)系:實(shí)心菱形+實(shí)線箭頭表示。
鳥(niǎo)和翅膀。合成(組合)關(guān)系是一種強(qiáng)的"擁有"關(guān)系,體現(xiàn)了嚴(yán)格的部分和整體的關(guān)系,部分和整體的生命周期一樣。
依賴關(guān)系:虛線箭頭表示。
定義:簡(jiǎn)單工廠模式是屬于創(chuàng)建型模式,又叫做靜態(tài)工廠方法(Static Factory Method)模式,是由一個(gè)工廠對(duì)象決定創(chuàng)建出哪一種產(chǎn)品類(lèi)的實(shí)例。但不屬于23種GOF設(shè)計(jì)模式之一。
實(shí)現(xiàn)方法和角色:
(1)實(shí)現(xiàn)方式:簡(jiǎn)單工廠模式的實(shí)質(zhì)是由一個(gè)工廠類(lèi)根據(jù)傳入的參數(shù),動(dòng)態(tài)決定應(yīng)該創(chuàng)建哪一個(gè)產(chǎn)品類(lèi)(這些產(chǎn)品類(lèi)繼承自一個(gè)父類(lèi)或接口)的實(shí)例。
(2)角色:
工廠(Creator)角色
抽象產(chǎn)品(Product)角色
具體產(chǎn)品(Concrete Product)角色
優(yōu)缺點(diǎn):
(1)優(yōu)點(diǎn):在于工廠類(lèi)中包含了必要的邏輯,根據(jù)客戶需要的條件動(dòng)態(tài)實(shí)例化相關(guān)的類(lèi),對(duì)客戶端來(lái)說(shuō),去掉了與具體產(chǎn)品的依賴。
(2)缺點(diǎn):工廠類(lèi)集中了所有實(shí)例的創(chuàng)建邏輯,當(dāng)增加新的產(chǎn)品時(shí),會(huì)違反開(kāi)放-封閉原則。
簡(jiǎn)單工廠模式結(jié)構(gòu)圖:
//抽象產(chǎn)品 public abstract class Computer { public abstract void start(); } //具體產(chǎn)品 public class LenovoComputer extends Computer { public void start() { System.out.println("聯(lián)想電腦啟動(dòng)"); } } public class HpComputer extends Computer { public void start() { System.out.println("惠普電腦啟動(dòng)"); } } public class AsusComputer extends Computer { public void start() { System.out.println("華碩電腦啟動(dòng)"); } } //工廠類(lèi) public class ComputerFactory { public static Computer createComputer(String type) { Computer computer = null; if("lenovo".equals(type)){ computer = LenovoComputer(); } else if("hp".equals(type)){ computer = HpComputer(); } else if("asus".equals(type)){ computer = AsusComputer(); } return computer; } } //客戶端調(diào)用 public class Client { public static void main(String[] args) { ComputerFactory.createComputer("lenovo").start(); } }
面試題:請(qǐng)用C++,Java,C#,或VB.NET任意一種面向?qū)ο笳Z(yǔ)言實(shí)現(xiàn)一個(gè)計(jì)算器控制臺(tái)程序,要求輸入兩個(gè)數(shù)和運(yùn)算符號(hào),得到結(jié)果。
活字印刷
易維護(hù):要改,只需要改要改之字。
可復(fù)用:每個(gè)字在印刷中可重復(fù)使用。
易擴(kuò)展:要加字,只需另刻字加入。
靈活性好:字可任意排序。
面向?qū)ο蟮姆治鲈O(shè)計(jì)編程思想:考慮通過(guò)封裝,繼承,多態(tài)把程序的耦合度降低,用設(shè)計(jì)模式使得程序更加靈活,容易修改,并且易于復(fù)用。
初級(jí)程序員的工作就是:Ctrl+C和Ctrl+V。
編程有一個(gè)原則:用盡可能的辦法避免重復(fù)。代碼重復(fù)到一定程度,維護(hù)就是一場(chǎng)災(zāi)難。
復(fù)制粘貼是最容易的編程,也是最沒(méi)有價(jià)值的編程。
封裝:
業(yè)務(wù)封裝,將界面邏輯和業(yè)務(wù)邏輯分開(kāi)。降低耦合度,達(dá)到易維護(hù)。
繼承:
將加減乘除等運(yùn)算分離。修改或增加新的運(yùn)算方法,不影響原有功能,降低風(fēng)險(xiǎn)。
多態(tài)+簡(jiǎn)單工廠模式:
實(shí)例化哪個(gè)運(yùn)算對(duì)象?應(yīng)該用一個(gè)多帶帶的類(lèi)來(lái)做這個(gè)創(chuàng)建實(shí)例的過(guò)程,這就是工廠。
public abstract class Operate { public double numberA; public double numberB; public abstract double getResult(); //TODO get set 方法 } public Class OperateAdd extends Operate { public double getResult(){ return numberA + numberB; } } public Class OperateSub extends Operate { public double getResult(){ return numberA - numberB; } } public Class OperateMul extends Operate { public double getResult(){ return numberA * numberB; } } public Class OperateDiv extends Operate { public double getResult() { return numberA / numberB; } } public Class OperateFactory { public static Operate createOperate(String oper){ Operate operate = null; if("+".equal(oper)){ operate = new OperateAdd(); } else if("-".equal(oper)){ operate = new OperateSub(); } else if("*".equal(oper)){ operate = new OperateMul(); } else if("/".equal(oper)){ operate = new OperateDiv(); } return operate; } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("請(qǐng)輸入一個(gè)數(shù)字A:"); double numberA = scanner.nextDouble(); System.out.println("請(qǐng)輸入一個(gè)運(yùn)算符(+,-,*,/):"); String oper = scanner.next(); System.out.println("請(qǐng)輸入一個(gè)數(shù)字B:"); double numberB = scanner.nextDouble(); Operate operate = OperateFactory.createOperate(oper); operate.setNumberA(numberA); operate.setNumberB(numberB); System.out.println(operate.getResult()); }
使用場(chǎng)景:
工廠類(lèi)負(fù)責(zé)創(chuàng)建的對(duì)象比較少。
客戶只知道傳入工廠類(lèi)的參數(shù),對(duì)于如何創(chuàng)建對(duì)象(邏輯)不關(guān)心。
優(yōu)點(diǎn):
使用戶根據(jù)參數(shù)獲得相應(yīng)的類(lèi)實(shí)例,避免了直接實(shí)例化,降低了耦合性。
缺點(diǎn):
增加新類(lèi)型,需要修改工廠,違背了開(kāi)放封閉原則。
簡(jiǎn)單工廠需要知道所有要生成的類(lèi)型,當(dāng)子類(lèi)過(guò)多或者子類(lèi)層次過(guò)多時(shí)不適合使用。
不能只滿足于寫(xiě)完代碼運(yùn)行結(jié)果正確就完事,時(shí)常考慮如何讓代碼更加簡(jiǎn)練,更加容易維護(hù),容易擴(kuò)展和復(fù)用,只有這樣才可以真正得到提高。
工廠方法模式定義:定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類(lèi)決定實(shí)例化哪個(gè)類(lèi)。工廠方法使一個(gè)類(lèi)的實(shí)例化延遲到其子類(lèi)。
依賴倒置原則
工廠方法模式:克服了簡(jiǎn)單工廠違背"開(kāi)發(fā)-封閉原則"的缺點(diǎn),又保持了封裝對(duì)象創(chuàng)建過(guò)程的優(yōu)點(diǎn)。
工廠方法模式是簡(jiǎn)單工廠模式的進(jìn)一步抽象和推廣。由于使用了多態(tài)性,工廠方法模式保持了簡(jiǎn)單工廠模式的優(yōu)點(diǎn),而且克服了它的缺點(diǎn)。缺點(diǎn):每加一個(gè)產(chǎn)品,就需要加一個(gè)產(chǎn)品工廠類(lèi),增加了額外的開(kāi)發(fā)量。
工廠方法模式(Factory Method)結(jié)構(gòu)圖:
常規(guī)實(shí)現(xiàn):
通過(guò)反射實(shí)現(xiàn):
//抽象產(chǎn)品 public abstract class Computer { public abstract void start(); } //具體產(chǎn)品 public LenovoComputer extends Computer { public void start() { System.out.println("聯(lián)想電腦啟動(dòng)"); } } public HpComputer extends Computer { public void start() { System.out.println("惠普電腦啟動(dòng)"); } } public AsusComputer extends Computer { public void start() { System.out.println("華碩電腦啟動(dòng)"); } } //抽象工廠 public abstract class ComputerFactory { public abstractT createComputer(Class clz); } //具體工廠 public class ConcreteFactory extends ComputerFactory { public T createComputer(Class clz) { Computer computer = null; String className =clz.getName(); try{ computer = (Computer)Class.forName(className).netInstance(); }catch(Exception e) { e.printStackTrace(); } return (T)computer; } } //客戶端調(diào)用 public class Client { public static void main(String[] args) { ComputerFactory computerFactory = new ConcreteFactory(); Computer computer = null; computer = computerFactory.createComputer(LenovoComputer.class); computer.start(); computer = computerFactory.createComputer(HpComputer.class); computer.start(); computer = computerFactory.createComputer(AsusComputer.class); computer.start(); } }
這樣整個(gè)工廠和產(chǎn)品體系其實(shí)都沒(méi)有修改的變化,而只是擴(kuò)展的變化,這就完全符合了開(kāi)放-封閉原則。
工廠方法模式實(shí)現(xiàn)時(shí),客戶端需要決定實(shí)例化哪一個(gè)工廠來(lái)實(shí)現(xiàn)運(yùn)算類(lèi),工廠方法把簡(jiǎn)單工廠的內(nèi)部邏輯判斷轉(zhuǎn)移到了客戶端代碼來(lái)進(jìn)行。
定義:提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無(wú)需指定他們具體的類(lèi)。
當(dāng)需要?jiǎng)?chuàng)建的產(chǎn)品有多個(gè)產(chǎn)品線(產(chǎn)品族)時(shí),使用抽象工廠模式是比較好的選擇。
那什么是多個(gè)產(chǎn)品線?聯(lián)想和惠普的電腦,電腦也有多個(gè)產(chǎn)品線:臺(tái)式機(jī)、筆記本和平板。聯(lián)想和惠普都在生產(chǎn)這些不同產(chǎn)品線上的電腦,使用工廠方法模式已經(jīng)滿足不了需求,可用抽象工廠模式來(lái)解決。
抽象工廠(Abstract Factory)模式結(jié)構(gòu)圖:
實(shí)現(xiàn):
//抽象產(chǎn)品 public abstract class DesktopComputer { public abstract void start(); } public abstract class NotebookComputer { public abstract void start(); } //具體產(chǎn)品 public LenovoDesktopComputer extends DesktopComputer { public void start() { System.out.println("聯(lián)想臺(tái)式機(jī)啟動(dòng)"); } } public HpDesktopComputer extends DesktopComputer { public void start(){ System.out.println("惠普臺(tái)式機(jī)啟動(dòng)"); } } public LenovoNotebookComputer extends NotebookComputer { public void start() { System.out.println("聯(lián)想筆記本啟動(dòng)"); } } public HpNotebookComputer extends NotebookComputer { public void start() { System.out.println("惠普筆記本啟動(dòng)"); } } //抽象工廠 public abstract class ComputerFactory { public abstract DesktopComputer createDesktopComputer(); public abstract NotebookComputer createNotebookComputer(); } //具體工廠 public class LenovoFactory extends ComputerFactory { public DesktopComputer createDesktopComputer() { return new LenovoDesktopComputer(); } public NotebookComputer createNotebookComputer() { return new LenovoNotebookComputer(); } } public class HpFactory extends ComputerFactory { public DesktopComputer createDesktopComputer() { return new HpDesktopComputer(); } public NotebookComputer createNotebookComputer() { return new HpNoteBookComputer(); } } //客戶端調(diào)用 public class Client { public static void main(String[] args) { ComputerFactory lenovoFactory = new LenovoFactory(); lenovoFactory.createDesktopComputer().start(); lenovoFactory.createNotebookComputer().start(); ComputerFactory hpFactory = new HpFactory(); hpFactory.createDesktopComputer().start(); hpFactory.createNotebookComputer().start(); } }
抽象工廠模式的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
具體類(lèi)的創(chuàng)建實(shí)例過(guò)程與客戶端分離,客戶端通過(guò)工廠的抽象接口操縱實(shí)例,客戶端并不知道具體的實(shí)現(xiàn)是誰(shuí)。
缺點(diǎn):
如果增加新的產(chǎn)品族則也需要修改抽象工廠和所有的具體工廠。
策略設(shè)計(jì)模式(Strategy):它定義了算法家族,分別封裝起來(lái),讓它們之間可以互相替換,此模式讓算法的變化,不會(huì)影響到使用算法的客戶。
面試題:請(qǐng)用任意面向?qū)ο笳Z(yǔ)言,設(shè)計(jì)實(shí)現(xiàn)一個(gè)商場(chǎng)收銀功能,需包含:打折(9折,8折,五折),返現(xiàn)(滿300返100,滿100返50),及正常價(jià)格收費(fèi)。
面向?qū)ο缶幊?,并不是?lèi)越多越好,類(lèi)的劃分是為了封裝,但分類(lèi)的基礎(chǔ)是抽象,具有相同屬性和功能的對(duì)象的抽象集合才是類(lèi)。
商場(chǎng)促銷(xiāo),無(wú)論是打折還是返現(xiàn),其實(shí)都是一些算法,但算法本身只是一種策略,最重要的是這些算法隨時(shí)都可能互相替換,這就是變化點(diǎn),封裝變化點(diǎn)是我們面向?qū)ο蟮囊环N很重要的思維方式。
現(xiàn)金收費(fèi)抽象類(lèi)。
正常收費(fèi)子類(lèi)。
打折收費(fèi)子類(lèi)。
返現(xiàn)收費(fèi)子類(lèi)。
//現(xiàn)金收費(fèi)抽象類(lèi) public abstract class CashSuper { public abstarct double acceptCash(double money); } //正常收費(fèi)子類(lèi) public class CashNormal extends CashSuper { public double acceptCash(double money) { return money; } } //打折收費(fèi)子類(lèi) public class CashRebate extends CashSuper { private double rebate; public CashRebate(double rebate){ this.rebate = rebate; } public double acceptyCash(double money) { return money * rebate; } } //返現(xiàn)收費(fèi)子類(lèi) public class CashReturn extends CashSuper { private double moneyCondition; private double moneyReturn; public CashReturn(double moneyCondition,double moneyReturn) { this.moneyCondition = moneyCondition; this.moneyReturn = moneyReturn; } public double acceptCash(double money) { double resule = money; if(money >= moneyCondition){ result = money - Math.floor(money / moneyCondition) * moneyReturn ; return result; } } } //上下文,維護(hù)一個(gè)對(duì)Strategy對(duì)象的引用 public class CashContext { private CashSuper cashSuper; public CashContext (CashSuper cashSuper) { this.cashSuper = cashSuper; } public double getResult(double money){ return cashSuper.acceptCash(money); } } 策略模式和簡(jiǎn)單工廠模式結(jié)合,將客戶端條件判斷轉(zhuǎn)移到CashContext中。 客戶端調(diào)用略
總結(jié):
當(dāng)不同的行為堆砌在一個(gè)類(lèi)中,就很難避免使用條件語(yǔ)句來(lái)選擇合適的行為。將這些行為封裝在一個(gè)個(gè)獨(dú)立的Strategy類(lèi)中,可以在使用這些行為的類(lèi)中消除條件語(yǔ)句。
策略模式封裝了變化。
策略模式就是用來(lái)封裝算法的,只要在分析的過(guò)程中聽(tīng)到需要在不同的時(shí)間應(yīng)用不同的業(yè)務(wù)規(guī)則,就可以考慮是使用策略模式。
消除switch或條件判斷:反射技術(shù)。
代理模式(Proxy),為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。
為什么要有代理模式?
開(kāi)閉原則,對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
為什么有靜態(tài)代理和動(dòng)態(tài)代理?
最大的缺點(diǎn):接口變化了,除了實(shí)現(xiàn)類(lèi)需要改變,代理也需要修改。實(shí)際上我們是不想讓代理也需要修改的。所以出現(xiàn)了動(dòng)態(tài)代理。
jdk 動(dòng)態(tài)代理的缺點(diǎn)?
必須要實(shí)現(xiàn)接口。
jdk動(dòng)態(tài)代理為什么必須要實(shí)現(xiàn)接口?
因?yàn)樗J(rèn)繼承的proxy類(lèi),java是單繼承,所以必須要實(shí)現(xiàn)接口。
spring aop 最核心的思想:動(dòng)態(tài)代理。
什么樣的場(chǎng)景下用cglib?
看代理的對(duì)象有沒(méi)有接口,沒(méi)有接口的用cglib。
代理模式使用場(chǎng)景
無(wú)法或者不想直接訪問(wèn)某個(gè)對(duì)象時(shí)可以通過(guò)一個(gè)代理對(duì)象來(lái)間接的訪問(wèn)。
//靜態(tài)代理 //接口 public interface IuserDao { void save(); } //實(shí)現(xiàn)類(lèi) public class UserDaoImpl implements IuserDao { public void save(){ System.out.println("保存用戶"); } } //代理類(lèi) public class UserDaoProxy implements IUserDao { private IUserDao userDao; public UserDaoProxy(IUserDao userDao){ this.userDao = userDao; } public void save(){ System.out.println("操作前"); userDao.svae(); System.out.println("操作后"); } } //測(cè)試 public static void main(String[] args) { //靜態(tài)代理 IUserDao userDao = new UserDaoProxy(new UserDaoImpl()); userDao.save(); //動(dòng)態(tài)代理 IUserDao userDao = (IUserDao)Proxy.newProxyInstance(IUserDao.class.getClassLoader,new Class[]{IuserDao.class},new InvocationHandler(){ public Object invoke(Object proxy,Method method,Object[] args){ System.out.println("操作前"); Object object = method.invoke(new UserDaoImpl(),args); System.out.println("操作后"); return object ; } }); }單例設(shè)計(jì)模式
定義:保證一個(gè)類(lèi)僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。
餓漢式(線程安全)
public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return instance; } }
這種方式在類(lèi)加載時(shí)就完成了初始化,所以類(lèi)加載較慢,但獲取對(duì)象的速度快。
基于類(lèi)加載機(jī)制避免了多線程的同步問(wèn)題。但是也不能確定有其他方式導(dǎo)致類(lèi)裝載。
懶漢式(線程不安全)
public class Singleton(){ private static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ if(instance == null) { instance = new Singleton(); } return instance; } }
初始化時(shí)節(jié)約資源,第一次加載是需要實(shí)例化反映稍慢一些,多線程不能正常工作。
懶漢式(線程安全)
public class Singleton { private static Singleton instance = null; private Singleton(){} public static synchronized Singleton getInstance(){ if(instance == null) { instance = new Singleton(); } return instance; } }
每次調(diào)用getInstance方法時(shí)都需要同步,造成不必要的同步開(kāi)銷(xiāo),大部分時(shí)候是用不到同步的,所以不建議使用這種模式。
雙重檢查枷鎖(DCL)
public class Singleton { private volatile static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ if(instance == null) { synchroinzed(Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; } }
這種寫(xiě)法在getSingleton方法中對(duì)singleton進(jìn)行了兩次判空,第一次是為了不必要的同步,第二次是在singleton等于null的情況下才創(chuàng)建實(shí)例。
DCL雖然在一定程度解決了資源的消耗和多余的同步,線程安全等問(wèn)題,但是他還是在某些情況會(huì)出現(xiàn)失效的問(wèn)題,也就是DCL失效,在《java并發(fā)編程實(shí)踐》一書(shū)建議用靜態(tài)內(nèi)部類(lèi)單例模式來(lái)替代DCL。
靜態(tài)內(nèi)部類(lèi)
public class Singleton { private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.instance; } private static class SingletonHolder { private static final Singleton instance = new Singleton(); } }
第一次加載Singleton類(lèi)時(shí)并不會(huì)初始化instance,只有第一次調(diào)用getInstance方法時(shí),虛擬機(jī)加載SingletonHolder并初始化instance,這樣不僅能確保線程安全也能保證Singleton類(lèi)的唯一性,所以推薦使用靜態(tài)內(nèi)部類(lèi)單例模式。
枚舉
public enum Singleton { INSTANCE; }
總結(jié):
枚舉式是最簡(jiǎn)單最優(yōu)秀的單例寫(xiě)法,可以防止反射工具(《如何防止單例模式被Java反射攻擊》)和序列化破壞。《Effective Java》的作者Joshua Bloch推薦使用這種寫(xiě)法,只是用的人較少,沒(méi)有普遍性,建議編程時(shí)采用靜態(tài)內(nèi)部類(lèi)(不能防止反射和序列化破壞)。
定義:定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類(lèi)中,模板方法使得子類(lèi)可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
當(dāng)我們要完成一系列步驟,但其個(gè)別步驟在更詳細(xì)的層次上的實(shí)現(xiàn)可能不同時(shí),我們通??紤]使用模板方法模式來(lái)處理。
模板方法模式(TemplateMethod)結(jié)構(gòu)圖:
定義:用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi),并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。
原型模式其實(shí)就是從一個(gè)對(duì)象再創(chuàng)建另外一個(gè)可定制的對(duì)象,而且不需知道任何創(chuàng)建的細(xì)節(jié)。
一般在初始化信息不發(fā)生變化的情況下,克隆是最好的辦法,這既隱藏了創(chuàng)建對(duì)象的細(xì)節(jié),又對(duì)性能是大大的提高。
原型模式(Prototype)結(jié)構(gòu)圖:
Client:客戶端角色。
Prototype:抽象原型角色,抽象類(lèi)或者接口,用來(lái)聲明clone方法。
ConcretePrototype:具體的原型類(lèi),是客戶端角色使用的對(duì)象,即被復(fù)制的對(duì)象。
注意:Prototype通常是不用自己定義的,因?yàn)榭截愡@個(gè)操作十分常用,Java中就提供了Cloneable接口來(lái)支持拷貝操作,它就是原型模式中的Prototype。當(dāng)然,原型模式也未必非得去實(shí)現(xiàn)Cloneable接口,也有其他的實(shí)現(xiàn)方式。
Cloneable接口是一個(gè)標(biāo)識(shí)接口,表示這個(gè)對(duì)象是可拷貝的,只要重寫(xiě)clone方法就可以實(shí)現(xiàn)拷貝。clone方法不是在Cloneable接口中定義的(Cloneable接口中沒(méi)有定義任何方法),而是在Object中定義的。
//淺復(fù)制 public class BusinessCard implements Cloneable { private String name; private String company; //TODO get set方法 public BusinessCard(){ System.out.println("調(diào)用構(gòu)造方法"); } public Object clone() throws CloneNotSupportException{ return super.clone(); } public void show() { System.out.println("name:"+name+",compnay:"+company); } } //客戶端 public class Clinet { public static void main(String[] args) { BusinessCard businessCard = new BusinessCard(); businessCard.setName("張三"); businessCard.setCompany("百度"); BusinessCard cloneCard1 = businessCard.clone(); cloneCard1.setName("李四"); cloneCard1.setCompany("阿里巴巴"); BusinessCard cloneCard2 = businessCard.clone(); cloneCard2.setName("王五"); cloneCard2.setCompany("騰訊"); businessCard.show(); cloneCard1.show(); cloneCard2.show(); } } //對(duì)象類(lèi)型 public class Company { private String name; private String address; //TODO get set 方法 } public class BusinessCard implements Cloneable { private String name; private Company company = new Company(); //TODO get set 方法 public BusinessCard(){ System.out.println("初始化構(gòu)造方法"); } public void setCompany(String name,String address){ this.company.setName(name); this.company.setAddress(address); } protected Object clone() throws CloneNotSupportedException { return super.clone(); } public void show(){ System.out.println("name:"+name+",compnayName:"+company.getName()+",compnayAddress:"+company.getAddress()); } } //客戶端 public class Clinet { public static void main(String[] args) { BusinessCard businessCard = new BusinessCard(); businessCard.setName("張三"); businessCard.setCompany("百度", "西二旗"); BusinessCard cloneCard1 = businessCard.clone(); cloneCard1.setName("李四"); cloneCard1.setCompany("聯(lián)想", "中關(guān)村"); BusinessCard cloneCard2 = businessCard.clone(); cloneCard2.setName("王五"); cloneCard2.setCompany("騰訊", "望京"); businessCard.show(); cloneCard1.show(); cloneCard2.show(); } } //輸出結(jié)果company:全為 騰訊 望京
String是一種擁有值類(lèi)型特定的特殊引用類(lèi)型。
這是因?yàn)镺bject類(lèi)提供的clone方法,不會(huì)拷貝對(duì)象中的內(nèi)部數(shù)組和引用對(duì)象,導(dǎo)致它們?nèi)耘f指向原來(lái)對(duì)象的內(nèi)部元素地址,這種拷貝叫做淺拷貝。
實(shí)現(xiàn)深拷貝:
//修改Company實(shí)現(xiàn)Cloneable接口,實(shí)現(xiàn)clone方法 public class Company implements Cloneable { private String name; private String address; //TODO get set 方法 protected Object clone() throws CloneNotSupportedException { return super.clone(); } } //修改BusinessCard的clone方法: protected Object clone() throws CloneNotSupportedException { BusinessCard businessCard = null; businessCard = (BusinessCard)super.clone(); businessCard.company = (Company)company.clone(); return businessCard; }
原型設(shè)計(jì)模式的使用場(chǎng)景:
如果類(lèi)的初始化需要耗費(fèi)較多的資源,那么可以通過(guò)原型拷貝避免這些消耗。
通過(guò)new產(chǎn)生一個(gè)對(duì)象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問(wèn)權(quán)限,則可以使用原型模式。
一個(gè)對(duì)象需要提供給其他對(duì)象訪問(wèn),而且各個(gè)調(diào)用者可能都需要修改其值時(shí),可以拷貝多個(gè)對(duì)象供調(diào)用者使用,即保護(hù)性拷貝。
優(yōu)點(diǎn):
原型模式是在內(nèi)存中二進(jìn)制流的拷貝,要比new一個(gè)對(duì)象的性能要好,特別是需要產(chǎn)生大量對(duì)象時(shí)
缺點(diǎn):
直接在內(nèi)存中拷貝,構(gòu)造函數(shù)是不會(huì)執(zhí)行的,這樣就減少了約束,這既是優(yōu)點(diǎn)也是缺點(diǎn),需要在實(shí)際應(yīng)用中去考量。
定義:觀察者模式又叫發(fā)布-訂閱模式(Publish/Subscribe),定義了一種一對(duì)多的依賴關(guān)系,讓多個(gè)觀察者同時(shí)監(jiān)聽(tīng)某一個(gè)主題對(duì)象。這個(gè)主題對(duì)象在狀態(tài)發(fā)生變化時(shí),會(huì)通知所有觀察者對(duì)象,使它們能夠自動(dòng)更新自己。
觀察者模式有兩種方模型,分別是推模型和拉模型
觀察者模式(Oberver)結(jié)構(gòu)圖:
實(shí)現(xiàn):
抽象被觀察者角色:定義了動(dòng)態(tài)增加、刪除以及通知觀察者對(duì)象的方法,職責(zé)就是管理和通知觀察者。持有觀察者對(duì)象的集合。
具體被觀察者角色:一般繼承抽象被觀察者,實(shí)現(xiàn)自己本身的業(yè)務(wù)邏輯,當(dāng)狀態(tài)發(fā)生改變時(shí)發(fā)起通知。
抽象觀察者角色:提供一個(gè)接口,定義了觀察者收到通知時(shí)更新自己的方法。
具體觀察者角色:實(shí)現(xiàn)抽象觀察者接口,處理不同具體觀察者的不同業(yè)務(wù)邏輯。
//抽象被觀察者 public abstract class Subject { private ListobserverList = new ArrayList<>(); public void attach(Observer observer){ observerList.add(observer); System.out.println("增加了觀察者:"+observer.getName); } public void dettach(Observer observer){ observerList.remove(observer); System.out.println("刪除了觀察者:"+observer.getName); } public void notifyObserver(){ for( Observer observer: observerList) { observer.update("灰太狼要搞事情了"); } } } //具體被觀察者 public class Wolf extends Subject { public void invade() { System.out.println("灰太狼:我要搞事情了"); notifyObserver(); } } //抽象觀察者 public interface Observer { String getName(); void update(String msg); } //具體觀察者 public PleasantSheep implents Observer { public String getName() { return "喜洋洋"; } public void update(String msg) { System.out.println("喜洋洋收到通知:"+msg); } } public LazySheep implents Observer { public String getName() { return "懶洋洋"; } public void update(String msg) { System.out.println("懶洋洋收到通知:"+msg); } } //客戶端 public class Client { public static void main(String[] args) { //灰太狼--被觀察者 Wolf wolf = new Wolf(); //喜洋洋--觀察者 Observer pleasantSheep= new PleasantSheep(); //登記觀察者 wolf.attach(pleasantSheep); // 懶羊羊--觀察者 Observer lazySheep = new LazySheep(); // 登記觀察者 wolf.attach(lazySheep); //灰太狼入侵 wolf.invade(); } }
JDK(被觀察者【Observable】和觀察者【Observer】)
在JAVA語(yǔ)言的 java.util 庫(kù)里面,提供了一個(gè)Observable類(lèi)以及一個(gè)Observer接口,構(gòu)成JAVA語(yǔ)言對(duì)觀察者模式的支持。
基于JDK實(shí)現(xiàn):
//被觀察者 public class Wolf extends Observable { private String message; //TODO public Wolf(){ System.out.println("灰太狼:我要搞事情了!"); } public void invade() { message="大灰狼來(lái)了!"; super.setChanged(); super.notifyObservers(); } } //觀察者 public class PleasantSheep implements Observer { public void update(Observable o,Object arg) { System.out.println("喜洋洋接到通知:"+((Wolf)o).getMessage()); } } public class LazySheep implements Observer { public void update(Observable o,Object arg) { System.out.println("懶洋洋接到通知:"+((Wolf)o).getMessage()); } } public class Client { public static void main(String[] args) { //被觀察者--灰太狼 Wolf wolf = new Wolf(); //觀察者--喜洋洋 PleasantSheep pleasantSheep = new PleasantSheep(); //登記觀察者 wolf.addObserver(pleasantSheep); //觀察者--懶洋洋 LazySheep lazySheep = new LazySheep(); //登記觀察者 wolf.addObserver(lazySheep); //灰太狼入侵 wolf.invade(); } }
優(yōu)點(diǎn):
(1)觀察者和被觀察者之間抽象耦合。觀察者模式容易擴(kuò)展,被觀察者只持有觀察者集合,并不需要知道具體觀察者內(nèi)部的實(shí)現(xiàn)。
(2)對(duì)象之間的保持高度的協(xié)作。當(dāng)被觀察者發(fā)生變化時(shí),所有被觀察者都會(huì)通知到,然后做出相應(yīng)的動(dòng)作。
缺點(diǎn):
(1)如果觀察者太多,被觀察者通知觀察者消耗的時(shí)間很多,影響系統(tǒng)的性能。
(2)當(dāng)觀察者集合中的某一觀察者錯(cuò)誤時(shí)就會(huì)導(dǎo)致系統(tǒng)卡殼,因此一般會(huì)采用異步方式。
(3)抽象通知者還是依賴了抽象觀察者,當(dāng)沒(méi)有觀察者的時(shí)候,沒(méi)辦法更新。
(4)要求觀察者的所有動(dòng)作必須一樣 ,如果不一樣的話,不能實(shí)現(xiàn)。
事件委托
一次通知,執(zhí)行了不同類(lèi)的不同方法。
PS:Java中是沒(méi)有像c#delegate關(guān)鍵字的,所以我是通過(guò)用Java中的反射來(lái)實(shí)現(xiàn)。
定義:將一個(gè)類(lèi)的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類(lèi)可以一起工作。
適配器模式主要解決什么問(wèn)題?
需要的東西就在前面,但卻不能使用,而短時(shí)間內(nèi)又無(wú)法改造它,于是我們就想辦法試配它。
適配器模式主要應(yīng)用于希望復(fù)用一些現(xiàn)存的類(lèi),但是接口又與復(fù)用環(huán)境要求不一致的情況。
GOF設(shè)計(jì)模式中對(duì)適配器模式將了兩種類(lèi)型,類(lèi)適配器和對(duì)象適配器。
建議盡量使用對(duì)象的適配器模式,少用繼承。
何時(shí)使用適配器模式?
軟件開(kāi)發(fā)后期或維護(hù)期。
第三方開(kāi)發(fā)組件。
適配器模式屬于補(bǔ)償模式,專(zhuān)門(mén)用來(lái)在系統(tǒng)后期擴(kuò)展、修改時(shí)使用,但要注意不要過(guò)度使用適配器模式。
適配器模式(Adapter)結(jié)構(gòu)圖:
實(shí)現(xiàn):
//球員 public abstract class Player { protected Sring name; public Player(String name){ this.name = name; } public abstract void attack(); public abstract void defense(); } //前鋒 public Forward extends Player { public void attack() { Systme.out.println("前鋒"+name+"進(jìn)攻"); } public void defense() { Systme.out.println("前鋒"+name+"防守"); } } //中鋒 public Center extends Player { public void attack() { Systme.out.println("中鋒"+name+"進(jìn)攻"); } public void defense() { Systme.out.println("中鋒"+name+"防守"); } } ... //外籍中鋒 public class ForeignCenter { private String name; public void 進(jìn)攻(){ Systme.out.println("外籍中鋒"+name+"進(jìn)攻"); } public void 防守(){ Systme.out.println("外籍中鋒"+name+"防守"); } } //翻譯 public class Translator extends Player { private ForeignCenter foreignCenter = new ForeignCenter(); public void attack() { foreignCenter.進(jìn)攻(); } public void defense() { foreignCenter.防守(); } }裝飾模式
定義:動(dòng)態(tài)地給一個(gè)對(duì)象增加一些額外的職責(zé),就增加功能來(lái)說(shuō),裝飾模式比生成子類(lèi)更為靈活。
外觀模式 建造者模式 狀態(tài)模式 享元模式 中介者模式文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68425.html
摘要:哪吒社區(qū)技能樹(shù)打卡打卡貼函數(shù)式接口簡(jiǎn)介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號(hào)作者架構(gòu)師奮斗者掃描主頁(yè)左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點(diǎn)贊收藏留言前情提要無(wú)意間聽(tīng)到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨(dú)立帶隊(duì)的人太少,簡(jiǎn)而言之,不缺干 ? 哪吒社區(qū)Java技能樹(shù)打卡?【打卡貼 day2...
摘要:編程思想第版這本書(shū)要常讀,初學(xué)者可以快速概覽,中等程序員可以深入看看,老鳥(niǎo)還可以用之回顧的體系。以下視頻整理自慕課網(wǎng)工程師路徑相關(guān)免費(fèi)課程。 我自己總結(jié)的Java學(xué)習(xí)的系統(tǒng)知識(shí)點(diǎn)以及面試問(wèn)題,目前已經(jīng)開(kāi)源,會(huì)一直完善下去,歡迎建議和指導(dǎo)歡迎Star: https://github.com/Snailclimb/Java-Guide 筆者建議初學(xué)者學(xué)習(xí)Java的方式:看書(shū)+視頻+實(shí)踐(初...
摘要:大多數(shù)待遇豐厚的開(kāi)發(fā)職位都要求開(kāi)發(fā)者精通多線程技術(shù)并且有豐富的程序開(kāi)發(fā)調(diào)試優(yōu)化經(jīng)驗(yàn),所以線程相關(guān)的問(wèn)題在面試中經(jīng)常會(huì)被提到。將對(duì)象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對(duì)象稱之為反序列化。 JVM 內(nèi)存溢出實(shí)例 - 實(shí)戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細(xì)介紹 Java 注解的使用,有利于學(xué)習(xí)編譯時(shí)注解 Java 程序員快速上手 Kot...
摘要:一直想做一個(gè)總結(jié)吧,拖延癥,一直拖到了現(xiàn)在。設(shè)計(jì)模式在去年,月的時(shí)候,學(xué)習(xí)了大部分的設(shè)計(jì)模式。數(shù)據(jù)結(jié)構(gòu)和算法不是科班出身,所以找了一本算法書(shū),重頭到尾,認(rèn)真學(xué)習(xí)了一遍。學(xué)完感受就是,會(huì)寫(xiě)數(shù)據(jù)結(jié)構(gòu)和算法還是會(huì)寫(xiě),不會(huì)寫(xiě)的還是不會(huì)寫(xiě)。 工作了一年多了,這一年里,過(guò)的還是比較充實(shí)。一直想做一個(gè)總結(jié)吧,拖延癥,一直拖到了現(xiàn)在。 1 設(shè)計(jì)模式 在去年3,4月的時(shí)候,學(xué)習(xí)了大部分的設(shè)計(jì)模式。設(shè)計(jì)模...
閱讀 772·2021-09-28 09:35
閱讀 2601·2019-08-29 11:25
閱讀 2165·2019-08-23 18:36
閱讀 1864·2019-08-23 16:31
閱讀 2078·2019-08-23 14:50
閱讀 3131·2019-08-23 13:55
閱讀 3299·2019-08-23 12:49
閱讀 2091·2019-08-23 11:46