汕頭澄海招聘網(wǎng)搜索引擎優(yōu)化期末考試答案
設(shè)計模式
前言
有一些重要的設(shè)計原則在開篇和大家分享下,這些原則將貫通全文:
-
面向接口編程,而不是面向?qū)崿F(xiàn)。這個很重要,也是優(yōu)雅的、可擴展的代碼的第一步,這就不需要多說了吧。
-
職責(zé)單一原則。每個類都應(yīng)該只有一個單一的功能,并且該功能應(yīng)該由這個類完全封裝起來。
-
對修改關(guān)閉,對擴展開放。對修改關(guān)閉是說,我們辛辛苦苦加班寫出來的代碼,該實現(xiàn)的功能和該修復(fù)的 bug 都完成了,別人可不能說改就改;對擴展開放就比較好理解了,也就是說在我們寫好的代碼基礎(chǔ)上,很容易實現(xiàn)擴展。
創(chuàng)建型模式比較簡單,但是會比較沒有意思,結(jié)構(gòu)型和行為型比較有意思
每個代理模式的代碼都必須自己手動完成一遍。
創(chuàng)建型模式
創(chuàng)建型模式的作用就是創(chuàng)建對象,說到創(chuàng)建一個對象,最熟悉的就是 new 一個對象,然后 set 相關(guān)屬性。但是,在很多場景下,我們需要給客戶端提供更加友好的創(chuàng)建對象的方式,尤其是那種我們定義了類,但是需要提供給其他開發(fā)者用的時候。
工廠模式分為簡單工廠模式,工廠模式,抽象工廠模式
在工廠模式中,我們在創(chuàng)建對象時不會對客戶端暴露創(chuàng)建邏輯,并且是通過使用一個共同的接口來指向新創(chuàng)建的對象。本質(zhì)就是使用工廠方法代替new操作。
簡單工廠模式
public class FoodFactory {public static Food makeFood(String name) {if (name.equals("蘭州拉面")) {Food noodle = new LanZhouNoodle();System.out.println("蘭州拉面"+noodle+"出鍋啦");return noodle;} else if (name.equals("黃燜雞")) {Food chicken = new HuangMenChicken();System.out.println("黃燜雞"+ chicken +"出鍋啦");return chicken;} else {System.out.println("不知道你做的什么哦~");return null;}}
}
其中,LanZhouNoodle 和 HuangMenChicken 都繼承自 Food。
public class Cook {public static void main(String[] args) {Food food = FoodFactory.makeFood("黃燜雞");FoodFactory.makeFood("jaja");}
}
簡單地說,簡單工廠模式通常就是這樣,一個工廠類 XxxFactory,里面有一個靜態(tài)方法,根據(jù)我們不同的參數(shù),返回不同的派生自同一個父類(或?qū)崿F(xiàn)同一接口)的實例對象。
我們強調(diào)職責(zé)單一原則,一個類只提供一種功能,FoodFactory 的功能就是只要負(fù)責(zé)生產(chǎn)各種 Food。
在此例中可以看出,Cook 類在使用 FoodFactory 時就不需要 new 任何一個對象,這就是簡單工廠模式的好處,封裝了 new 的部分,做到的代碼易用性。
工廠模式
簡單工廠模式很簡單,如果它能滿足我們的需要,我覺得就不要折騰了。之所以需要引入工廠模式,是因為我們往往需要使用兩個或兩個以上的工廠。
public interface FoodFactory {Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {@Overridepublic Food makeFood(String name) {if (name.equals("A")) {return new ChineseFoodA();} else if (name.equals("B")) {return new ChineseFoodB();} else {return null;}}
}
public class AmericanFoodFactory implements FoodFactory {@Overridepublic Food makeFood(String name) {if (name.equals("A")) {return new AmericanFoodA();} else if (name.equals("B")) {return new AmericanFoodB();} else {return null;}}
}
其中,ChineseFoodA、ChineseFoodB、AmericanFoodA、AmericanFoodB 都派生自 Food。
客戶端調(diào)用:
public class APP {public static void main(String[] args) {// 先選擇一個具體的工廠FoodFactory factory = new ChineseFoodFactory();// 由第一步的工廠產(chǎn)生具體的對象,不同的工廠造出不一樣的對象Food food = factory.makeFood("A");}
}
雖然都是調(diào)用 makeFood(“A”) 制作 A 類食物,但是,不同的工廠生產(chǎn)出來的完全不一樣。
第一步,我們需要選取合適的工廠,然后第二步基本上和簡單工廠一樣。
核心在于,我們需要在第一步選好我們需要的工廠。比如,我們有 LogFactory 接口,實現(xiàn)類有 FileLogFactory 和 KafkaLogFactory,分別對應(yīng)將日志寫入文件和寫入 Kafka 中,顯然,我們客戶端第一步就需要決定到底要實例化 FileLogFactory 還是 KafkaLogFactory,這將決定之后的所有的操作。
抽象工廠模式
當(dāng)涉及到產(chǎn)品族的時候,就需要引入抽象工廠模式了。 一個經(jīng)典的例子是造一臺電腦 。
當(dāng)涉及到這種產(chǎn)品族的問題的時候,就需要抽象工廠模式來支持了。我們不再定義 CPU 工廠、主板工廠、硬盤工廠、顯示屏工廠等等,我們直接定義電腦工廠,每個電腦工廠負(fù)責(zé)生產(chǎn)所有的設(shè)備,這樣能保證肯定不存在兼容問題。
當(dāng)然,抽象工廠的問題也是顯而易見的,比如我們要加個顯示器,就需要修改所有的工廠,給所有的工廠都加上制造顯示器的方法。這有點違反了對修改關(guān)閉,對擴展開放這個設(shè)計原則。
本節(jié)要介紹的抽象工廠模式將考慮多等級產(chǎn)品的生產(chǎn),將同一個具體工廠所生產(chǎn)的位于不同等級的一組產(chǎn)品稱為一個產(chǎn)品族,圖 1 所示的是海爾工廠和 TCL 工廠所生產(chǎn)的電視機與空調(diào)對應(yīng)的關(guān)系圖。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tXbTibvX-1691644382209)(images/3-1Q1141559151S.gif)]
抽象工廠(AbstractFactory)模式的定義:是一種為訪問類提供一個創(chuàng)建一組相關(guān)或相互依賴對象的接口,且訪問類無須指定所要產(chǎn)品的具體類就能得到同族的不同等級的產(chǎn)品的模式結(jié)構(gòu)。
抽象工廠模式是工廠方法模式的升級版本,工廠方法模式只生產(chǎn)一個等級的產(chǎn)品,而抽象工廠模式可生產(chǎn)多個等級的產(chǎn)品。
使用抽象工廠模式一般要滿足以下條件。
- 系統(tǒng)中有多個產(chǎn)品族,每個具體工廠創(chuàng)建同一族但屬于不同等級結(jié)構(gòu)的產(chǎn)品。
- 系統(tǒng)一次只可能消費其中某一族產(chǎn)品,即同族的產(chǎn)品一起使用。
抽象工廠模式除了具有工廠方法模式的優(yōu)點外,其他主要優(yōu)點如下。
- 可以在類的內(nèi)部對產(chǎn)品族中相關(guān)聯(lián)的多等級產(chǎn)品共同管理,而不必專門引入多個新的類來進行管理。
- 當(dāng)增加一個新的產(chǎn)品族時不需要修改原代碼,滿足開閉原則。
其缺點是:當(dāng)產(chǎn)品族中需要增加一個新的產(chǎn)品時,所有的工廠類都需要進行修改。
單例模式
簡單點說,就是一個應(yīng)用程序中,某個類的實例對象只有一個,你沒有辦法去new,因為構(gòu)造器是被private修飾的,一般通過getInstance()的方法來獲取它們的實例。
getInstance()的返回值是一個對象的引用,并不是一個新的實例,所以不要錯誤的理解成多個對象。
特點
- 類構(gòu)造器私有
- 持有自己類型的屬性
- 對外提供獲取實例的靜態(tài)方法
餓漢式寫法
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; }
}
弊端:因為類加載的時候就會創(chuàng)建對象,所以有的時候還不需要使用對象,就會創(chuàng)建對象,造成內(nèi)存的浪費;
飽漢模式最容易出錯:
public class Singleton {// 首先,也是先堵死 new Singleton() 這條路private Singleton() {}// 和餓漢模式相比,這邊不需要先實例化出來,注意這里的 volatile,它是必須的private static volatile Singleton instance = null;public static Singleton getInstance() {if (instance == null) {// 加鎖synchronized (Singleton.class) {// 這一次判斷也是必須的,不然會有并發(fā)問題if (instance == null) {instance = new Singleton();}}}return instance;}
}
雙重檢查,指的是兩次檢查 instance 是否為 null。
volatile 在這里是需要的,希望能引起讀者的關(guān)注。
很多人不知道怎么寫,直接就在 getInstance() 方法簽名上加上 synchronized,這就不多說了,性能太差。
嵌套類最經(jīng)典,以后大家就用它吧:
public class Singleton {private Singleton() {}// 主要是使用了 嵌套類可以訪問外部類的靜態(tài)屬性和靜態(tài)方法 的特性private static class Holder {private static Singleton instance = new Singleton();}public static Singleton getInstance() {return Holder.instance;}
}
注意,很多人都會把這個嵌套類說成是靜態(tài)內(nèi)部類,嚴(yán)格地說,內(nèi)部類和嵌套類是不一樣的,它們能訪問的外部類權(quán)限也是不一樣的。
最后,我們說一下枚舉,枚舉很特殊,它在類加載的時候會初始化里面的所有的實例,而且 JVM 保證了它們不會再被實例化,所以它天生就是單例的。
TODO:
建造者模式
原型模式
結(jié)構(gòu)型模式
前面創(chuàng)建型模式介紹了創(chuàng)建對象的一些設(shè)計模式,這節(jié)介紹的結(jié)構(gòu)型模式旨在通過改變代碼結(jié)構(gòu)來達(dá)到解耦的目的,使得我們的代碼容易維護和擴展。
代理模式
第一個要介紹的代理模式是最常使用的模式之一了,用一個代理來隱藏具體實現(xiàn)類的實現(xiàn)細(xì)節(jié),通常還用于在真實的實現(xiàn)的前后添加一部分邏輯。
既然說是代理,那就要對客戶端隱藏真實實現(xiàn),由代理來負(fù)責(zé)客戶端的所有請求。當(dāng)然,代理只是個代理,它不會完成實際的業(yè)務(wù)邏輯,而是一層皮而已,但是對于客戶端來說,它必須表現(xiàn)得就是客戶端需要的真實實現(xiàn)。
理解代理這個詞,這個模式其實就簡單了。 下面上代碼理解。 代理接口:
//要有一個代理接口讓實現(xiàn)類和代理實現(xiàn)類來實現(xiàn)。
public interface FoodService {Food makeChicken();
}
被代理的實現(xiàn)類:
public class FoodServiceImpl implements FoodService {@Overridepublic Food makeChicken() {Food f = new Chicken();f.setChicken("1kg");f.setSpicy("1g");f.setSalt("3g");System.out.println("雞肉加好佐料了");return f;}
}
被代理實現(xiàn)類就只需要做自己該做的事情就好了,不需要管別的。
代理實現(xiàn)類:
public class FoodServiceProxy implements FoodService {// 內(nèi)部一定要有一個真實的實現(xiàn)類,當(dāng)然也可以通過構(gòu)造方法注入private FoodService foodService = new FoodServiceImpl();@Overridepublic Food makeChicken() {System.out.println("開始制作雞肉");// 如果我們定義這句為核心代碼的話,那么,核心代碼是真實實現(xiàn)類做的,// 代理只是在核心代碼前后做些“無足輕重”的事情Food food = foodService.makeChicken();System.out.println("雞肉制作完成啦,加點胡椒粉");food.addCondiment("pepper");System.out.println("上鍋咯");return food;}
}
客戶端調(diào)用,注意,我們要用代理來實例化接口:
// 這里用代理類來實例化
FoodService foodService = new FoodServiceProxy();
foodService.makeChicken();
所謂代理模式,就是對被代理方法包裝或者叫增強, 在面向切面編程(AOP)中,其實就是動態(tài)代理的過程。比如 Spring 中,我們自己不定義代理類,但是 Spring 會幫我們動態(tài)來定義代理,然后把我們定義在 @Before、@After、@Around 中的代碼邏輯動態(tài)添加到代理中。
待續(xù)。。。
行為型模式
模板模式
在含有繼承結(jié)構(gòu)的代碼中,模板方法模式是非常常用的。
父類定義了骨架(調(diào)用哪些方法及順序),某些特定方法由子類實現(xiàn)
模板方法只負(fù)責(zé)定義第一步應(yīng)該要做什么,第二步應(yīng)該做什么,第三步應(yīng)該做什么,至于怎么做,由子類來實現(xiàn)。
好處:代碼復(fù)用,減少重復(fù)代碼。除了子類要實現(xiàn)的特定方法,其他方法及方法調(diào)用順序都在父類中預(yù)先寫好
缺點: 每一個不同的實現(xiàn)都需要一個子類來實現(xiàn),導(dǎo)致類個數(shù)增加,使系統(tǒng)更加龐大
模板模式的關(guān)鍵點:
1、使用抽象類定義模板類,并在其中定義所有的基本方法、模板方法,鉤子方法,不限數(shù)量,以實現(xiàn)功能邏輯為主。其中基本方法使用final修飾,其中要調(diào)用基本方法和鉤子方法,基本方法和鉤子方法可以使用protected修飾,表明可被子類修改。
2、定義實現(xiàn)抽象類的子類,重寫其中的模板方法,甚至鉤子方法,完善具體的邏輯。
使用場景:
1、在多個子類中擁有相同的方法,而且邏輯相同時,可以將這些方法抽出來放到一個模板抽象類中。
2、程序主框架相同,細(xì)節(jié)不同的情況下,也可以使用模板方法。
架構(gòu)方法介紹
模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。其主要分為兩大類:模版方法和基本方法,而基本方法又分為:抽象方法(Abstract Method),具體方法(Concrete Method),鉤子方法(Hook Method)。
四種方法的基本定義(前提:在抽象類中定義):
(1)抽象方法:由抽象類聲明,由具體子類實現(xiàn),并以abstract關(guān)鍵字進行標(biāo)識。
(2)具體方法:由抽象類聲明并且實現(xiàn),子類并不實現(xiàn)或者做覆蓋操作。其實質(zhì)就是普遍適用的方法,不需要子類來實現(xiàn)。
(3)鉤子方法:由抽象類聲明并且實現(xiàn),子類也可以選擇加以擴展。通常抽象類會給出一個空的鉤子方法,也就是沒有實現(xiàn)的擴展。它和具體方法在代碼上沒有區(qū)別,不過是一種意識的區(qū)別;而它和抽象方法有時候也是沒有區(qū)別的,就是在子類都需要將其實現(xiàn)的時候。而不同的是抽象方法必須實現(xiàn),而鉤子方法可以不實現(xiàn)。也就是說鉤子方法為你在實現(xiàn)某一個抽象類的時候提供了可選項,相當(dāng)于預(yù)先提供了一個默認(rèn)配置。
(4)模板方法:定義了一個方法,其中定義了整個邏輯的基本骨架。
public abstract class AbstractTemplate {// 這就是模板方法public void templateMethod() {init();apply(); // 這個是重點end(); // 可以作為鉤子方法}//這是具體方法protected void init() {System.out.println("init 抽象層已經(jīng)實現(xiàn),子類也可以選擇覆寫");}// 這是抽象方法,留給子類實現(xiàn)protected abstract void apply();//這是鉤子方法,可定義一個默認(rèn)操作,或者為空protected void end() {}
}