點(diǎn)贊的靚仔,你是人群中最閃耀的光芒
開(kāi)題
小學(xué)6年級(jí)的時(shí)候老師講,好好學(xué)習(xí)、現(xiàn)在暫時(shí)辛苦,等上初中就好了,至少我的初中生活并不輕松。 初三, 現(xiàn)在辛苦,等高中就好了,高中生涯更是苦逼中的戰(zhàn)斗機(jī)。而高三老師說(shuō),上大學(xué)就好了,上了大學(xué)發(fā)現(xiàn),是真的好,沒(méi)有學(xué)業(yè)、作業(yè)的壓力。可以自由飛翔,卻不料那是整個(gè)人生最后的狂歡,踏入社會(huì)從此就是一臺(tái)不高速運(yùn)轉(zhuǎn)就會(huì)停擺的機(jī)器。以后會(huì)將更多的業(yè)余時(shí)間拿來(lái)學(xué)習(xí)。
2年前在做Java講師的時(shí)候,經(jīng)常會(huì)將設(shè)計(jì)模式分享給學(xué)生,學(xué)習(xí)經(jīng)典的框架設(shè)計(jì)。很多框架的設(shè)計(jì)都用到了設(shè)計(jì)模式,這里將自己對(duì)設(shè)計(jì)模式的理解與各位分享。
從高內(nèi)聚、低耦合說(shuō)起
從入行到現(xiàn)在,便聽(tīng)眾人常掛在嘴邊的話語(yǔ),從初級(jí)程序員到資深程序員必學(xué)的最高內(nèi)功心法:高內(nèi)聚、低耦合。要寫(xiě)出高內(nèi)聚、低耦合的代碼,需要一定的功力。而有這樣一群人,將代碼編寫(xiě)的經(jīng)驗(yàn)總結(jié)為一套武功秘籍,這就是設(shè)計(jì)模式。
設(shè)計(jì)模式6大原則
設(shè)計(jì)模式也遵守一定的規(guī)范,并不是隨隨便便就做出一套設(shè)計(jì)模式。而這規(guī)范就是設(shè)計(jì)模式六大原則。
- 開(kāi)閉原則(Open Close Principle)
開(kāi)閉原則的意思是:對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
- 里氏代換原則(Liskov Substitution Principle)
任何基類(lèi)可以出現(xiàn)的地方,子類(lèi)一定可以出現(xiàn)。
- 依賴倒轉(zhuǎn)原則(Dependence Inversion Principle)
這個(gè)原則是開(kāi)閉原則的基礎(chǔ),具體內(nèi)容:針對(duì)接口編程,依賴于抽象而不依賴于具體。
- 接口隔離原則(Interface Segregation Principle)
這個(gè)原則的意思是:使用多個(gè)隔離的接口,比使用單個(gè)接口要好。它還有另外一個(gè)意思是:降低類(lèi)之間的耦合度。由此可見(jiàn),其實(shí)設(shè)計(jì)模式就是從大型軟件架構(gòu)出發(fā)、便于升級(jí)和維護(hù)的軟件設(shè)計(jì)思想,它強(qiáng)調(diào)降低依賴,降低耦合。
- 迪米特法則,又稱最少知道原則(Demeter Principle)
最少知道原則是指:一個(gè)實(shí)體應(yīng)當(dāng)盡量少地與其他實(shí)體之間發(fā)生相互作用,使得系統(tǒng)功能模塊相對(duì)獨(dú)立
- 合成復(fù)用原則(Composite Reuse Principle)
合成復(fù)用原則是指:盡量使用合成/聚合的方式,而不是使用繼承。
以上內(nèi)容基本是復(fù)制的理論知識(shí),并不是太深?yuàn)W的東西,理解即可。
工廠模式
在現(xiàn)實(shí)中,工廠是用來(lái)生產(chǎn)各種產(chǎn)品的,而在面向?qū)ο蟮木幊淌澜?,一切皆?duì)象,工廠模式就是用來(lái)創(chuàng)建我們需要的產(chǎn)品,即對(duì)象。
而工廠模式又分為:簡(jiǎn)單工廠、靜態(tài)方法工廠、抽象工廠三種。我們先來(lái)看一波三種工廠的UML圖,比較三種模式的區(qū)別。
由圖中可以看到,三種模式在產(chǎn)品類(lèi)的結(jié)構(gòu)不變的情況下,針對(duì)工廠類(lèi)有不同的實(shí)現(xiàn),具體如下。
簡(jiǎn)單工廠
簡(jiǎn)單工廠的工廠類(lèi)比較簡(jiǎn)單,提供一個(gè)接收字符串并返回產(chǎn)品的方法,在方法內(nèi)部根據(jù)傳入的字符串而返回對(duì)應(yīng)的對(duì)象。
工廠類(lèi)代碼SimpleFactory如下。
public class SimpleFactory implements Factoty{
@Override
public Sender getSender(String senderCode) {
switch (senderCode){
case "email":
return new EmailSender();
case "phone":
return new PhoneSender();
}
return null;
}
}
可能大家能看出區(qū)別,這里的SimpleFactory與UML圖中并不一致,二十實(shí)現(xiàn)了一個(gè)Factory工廠。其實(shí)這里是否實(shí)現(xiàn)是根據(jù)自己的代碼設(shè)計(jì)而來(lái),并不是必須是普通類(lèi)或者是接口。再來(lái)看下一測(cè)試代碼。
public class SimpleTest {
public static void main(String[] args) {
SimpleFactory factory = new SimpleFactory();
Sender email = factory.getSender("email");
email.send("隔壁老王叫你早點(diǎn)回家吃飯");
}
}
整個(gè)代碼的實(shí)現(xiàn)比較簡(jiǎn)單,不知道大家看測(cè)試代碼的時(shí)候有沒(méi)有熟悉的感覺(jué)的?
沒(méi)錯(cuò),這就是Spring的Factory的實(shí)現(xiàn),下面我們來(lái)感受一下Spring容器的調(diào)用。
public class SpringTest {
public static void main(String[] args) {
//獲取Spring容器對(duì)象
ApplicationContext app = new ClassPathXmlApplicationContext("");
//從容器獲取對(duì)象
SpringTest bean = app.getBean("",SpringTest.class);
//調(diào)用方法
bean.run();
}
public void run(){
System.out.println("run");
}
}
對(duì)比發(fā)下,真的是像兩個(gè)親兄弟。其實(shí),Spring的BeanFactory就是使用簡(jiǎn)單工廠模式來(lái)實(shí)現(xiàn)的。上源碼。
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String var1) throws BeansException;
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
// more code
}
ApplicationContext的getBean方法是繼承于BeanFactory頂級(jí)父類(lèi),結(jié)構(gòu)如下。
靜態(tài)方法工廠
靜態(tài)方法工廠的工廠類(lèi)不再提供一個(gè)創(chuàng)建產(chǎn)品的方法,而是為每個(gè)產(chǎn)品提供一個(gè)靜態(tài)方法。代碼如下。
/**
* 靜態(tài)方法工廠模式
*/
public class StaticFactory {
//Email生產(chǎn)方法
public Sender getEmailSender(){
return new EmailSender();
}
//Phone生產(chǎn)方法
public Sender getPhoneSender(){
return new PhoneSender();
}
}
對(duì)于靜態(tài)方法工廠模式的應(yīng)用場(chǎng)景,我查閱了資料并沒(méi)有找到對(duì)其應(yīng)用場(chǎng)景的具體說(shuō)明。
這一點(diǎn)我個(gè)人理解的是:靜態(tài)工廠的每一個(gè)不同的生產(chǎn)對(duì)象都需要提供一個(gè)靜態(tài)方法,而如果生產(chǎn)的對(duì)象非常多,那么這個(gè)工廠內(nèi)中將會(huì)有非常多的靜態(tài)方法,比如1000個(gè)類(lèi),需要1000個(gè)方法來(lái)創(chuàng)建,而10000個(gè)呢? 所以我判斷靜態(tài)方法工廠模式不太適合創(chuàng)建多種對(duì)象,而適合用來(lái)創(chuàng)建少量的對(duì)象,而這個(gè)創(chuàng)建對(duì)象的代碼可能在很多地方都會(huì)使用到,所以使用靜態(tài)方法來(lái)創(chuàng)建,這樣就不需要每次都來(lái)創(chuàng)建工廠對(duì)象了。
我的推斷來(lái)源于JDK源碼。
Calendar的靜態(tài)方法工廠
Calender對(duì)外提供了4個(gè)創(chuàng)建對(duì)象的getInstance方法, 分別對(duì)應(yīng)不同的實(shí)現(xiàn),源碼如下。
Executers的靜態(tài)方法工廠
線程池是工作中比較常用的,而Executers工具類(lèi)也提供了4種線程池的默認(rèn)實(shí)現(xiàn)。也是使用了靜態(tài)方法工廠模式來(lái)實(shí)現(xiàn)的。
public class ExecutersDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
ExecutorService executorService1 = Executors.newFixedThreadPool(5);
ExecutorService executorService2 = Executors.newCachedThreadPool();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
}
}
其實(shí),類(lèi)似的還要很多,比如包裝類(lèi)的valueOf方法、NumberFormat的newInstance方法等等,通過(guò)源碼研究,我們可以輕易的找到靜態(tài)工廠方法的套路:
- 可能使用的地方較多,而通過(guò)靜態(tài)方法以避免創(chuàng)建過(guò)多的工廠對(duì)象
- 不適用過(guò)多的生產(chǎn)對(duì)象方式,通常一類(lèi)的靜態(tài)工廠方法數(shù)量不會(huì)超過(guò)10個(gè)
- 其實(shí)就是簡(jiǎn)單的靜態(tài)方法創(chuàng)建對(duì)象
抽象工廠模式
抽象工廠模式我認(rèn)為是對(duì)靜態(tài)工廠模式的進(jìn)一步提升,同樣,其工廠的復(fù)雜程度也相應(yīng)的提升。先上測(cè)試代碼。
AbstractFactory
public abstract class AbstractFactory {
//抽象層提供創(chuàng)建對(duì)象的方法
public abstract Sender getSender();
}
EmailFactory
public class EmailFactory extends AbstractFactory{
@Override
public Sender getSender() {
return new EmailSender();
}
}
PhoneFactory
public class PhoneFactory extends AbstractFactory{
@Override
public Sender getSender() {
return new PhoneSender();
}
}
抽象工廠在靜態(tài)工廠的基礎(chǔ)上做了升級(jí),靜態(tài)工廠是一個(gè)對(duì)象對(duì)應(yīng)一個(gè)方法,而抽象工廠則直接使用一個(gè)工廠對(duì)應(yīng)一個(gè)對(duì)象。
這樣的抽象工廠模式的理解是我在最初學(xué)習(xí)工廠模式時(shí)候的理解。但當(dāng)時(shí)并沒(méi)有深入研究抽象工廠的應(yīng)用場(chǎng)景,以至于對(duì)抽象工廠的理解有一些偏差,這一次一起將這個(gè)坑給補(bǔ)上。
首先,抽象工廠模式的應(yīng)用場(chǎng)景并不是創(chuàng)建單一的產(chǎn)品對(duì)象。而是用來(lái)創(chuàng)建一個(gè)系列的產(chǎn)品,這個(gè)概念稱之為‘產(chǎn)品族’,看圖。
這是從業(yè)務(wù)場(chǎng)景剝離出來(lái)的結(jié)構(gòu),也不難理解。 比如一個(gè)貸款功能, 圖種可以看到有兩種角色,一種是產(chǎn)品工廠,一種是產(chǎn)品,這一點(diǎn)和我們前面講到的工廠模式是類(lèi)似的,但是區(qū)別在于,每個(gè)工廠不再是只生產(chǎn)一種產(chǎn)品,而是一系列的產(chǎn)品。如果覺(jué)得比較難理解我們可以再看一個(gè)例子:
蘋(píng)果公司生產(chǎn)什么產(chǎn)品呢? 平板、手機(jī)、表、電腦。
華為也會(huì)生產(chǎn)這些產(chǎn)品:平板、手機(jī)、表、電腦。
這樣的比較就比較清晰,蘋(píng)果和華為工廠都有自己的產(chǎn)品族,且產(chǎn)品族是類(lèi)似的。這種情況就可以使用抽象工廠模式。
UML類(lèi)圖如下。
網(wǎng)上看了很多相關(guān)的文章,總結(jié)抽象工廠模式有一下特點(diǎn):
1、產(chǎn)品被劃分為多個(gè)產(chǎn)品族,即上面的例子
2、系統(tǒng)一次僅消費(fèi)使用其中一個(gè)族的產(chǎn)品。即每次都會(huì)使用同一個(gè)族的產(chǎn)品, 比如你是蘋(píng)果粉,那么只能使用蘋(píng)果的產(chǎn)品,華為粉則只能使用華為的產(chǎn)品。
抽象工廠的優(yōu)缺點(diǎn):
1、抽象工廠模式的縱向擴(kuò)展(擴(kuò)展工廠)非常容易
2、抽象工廠模式橫向擴(kuò)展(擴(kuò)展產(chǎn)品)非常困難,因?yàn)樵诋a(chǎn)品族新增一個(gè)產(chǎn)品會(huì)導(dǎo)致整個(gè)工廠及產(chǎn)品代碼的修改。