Java學習——模板設計模式——抽象類的實際應用
設計模式的精髓:解耦。而模板設計模式是通過第三方進行解耦
什麼是內聚、解耦大家可以看一下博主 小異常 的博文:https://blog.csdn.net/sun8112133/article/details/81040275
模板設計模式:(基於抽象類)在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類再不改變演算法的前提下,重新定義演算法中的某些步驟。
通俗點的理解就是 :完成一件事情,有固定的數個步驟,但是每個步驟根據物件的不同,而實現細節不同;就可以在父類中定義一個完成該事情的總方法,按照完成事件需要的步驟去呼叫其每個步驟的實現方法。每個步驟的具體實現,由子類完成。
一個經典的模板設計模式的例子
星巴克咖啡沖泡法
1. 將水煮沸
2. 用沸水沖泡咖啡
3. 將咖啡倒進杯子
4. 加糖和牛奶
星巴克茶沖泡法
1. 將水煮沸
2. 用沸水浸泡茶葉
3. 把茶倒進杯子
4. 加檸檬
用程式碼簡單實現就是
class Coffee{ void prepareRecipe(){ boilWater(); brewCoffeeGrings(); pourInCap(); addSugarAndMilk(); } public void boilWater(){ System.out.println("將水煮沸"); } public void brewCoffeeGrings(){ System.out.println("沖泡咖啡"); } public void pourInCap(){ System.out.println("將咖啡倒進杯子"); } public void addSugarAndMilk(){ System.out.println("加入糖和牛奶"); } } class Tea { void prepareRecipe() { boilWater(); steepTeaBag(); pourInCup(); addLemon(); } public void boilWater() { System.out.println("將水煮沸"); } public void steepTeaBag() { System.out.println("沖泡茶"); } public void pourInCup() { System.out.println("將茶倒進杯子"); } public void addLemon() { System.out.println("加檸檬"); } } class Test{ public static void main(String[] args) { Coffee coffee = new Coffee(); coffee.prepareRecipe(); Tea tea = new Tea(); tea.prepareRecipe(); } }
讓我們先從沖泡法入手。觀察咖啡和茶的沖泡法我們會發現,兩種沖泡法都採用了相同的演算法:
1. 將水煮沸
2. 用熱水泡飲料
3. 把飲料倒進杯子
4. 在飲料內加入適當的調料
實際上,浸泡(steep)和沖泡(brew)差異並不大。因此我們給它一個新的方法名稱brew(),這樣我們無論沖泡的是何種飲
料都可以使用這個方法。同樣的,加糖、牛奶還是檸檬也很相似,都是在飲料中加入其它調料,因此我們也給它一
個通用名稱addCondiments()。我們可以將這些重複的操作寫在一個抽象類中,不同的地方寫抽象方法,讓實現的操作延遲到子類去具體完成。
/** * 咖啡因飲料是一個抽象類 */ abstract class CaffeineBeverage{ /** * 宣告為final的原因是我們不希望子類覆蓋這個方法! */ final void prepareRecipe(){ boilWater(); brew(); pourInCap(); addCondiments(); } /** * 咖啡和茶處理這些方法不同,因此這兩個方法必須被宣告為抽象,留給子類實現 */ abstract void brew(); abstract void addCondiments(); public void boilWater(){ System.out.println("將水煮沸"); } public void pourInCap(){ System.out.println("將飲料倒進杯子"); } } class Coffee extends CaffeineBeverage{ public void brew(){ System.out.println("沖泡咖啡"); } public void addCondiments(){ System.out.println("加入糖和牛奶"); } } class Tea extends CaffeineBeverage{ public void brew() { System.out.println("沖泡茶"); } public void addCondiments() { System.out.println("加檸檬"); } } class Test{ public static void main(String[] args) { CaffeineBeverage coffee = new Coffee(); coffee.prepareRecipe(); CaffeineBeverage tea = new Tea(); tea.prepareRecipe(); } }
此時的類圖:
我們剛剛實現的就是模板設計模式,它包含了實際的"模板方法"
模板方法定義了一個演算法的步驟,並允許子類為一個或者多個步驟提供具體實現。
不好的茶或咖啡實現 模板方法提供的咖啡因飲料
Coffee或Tea主導一切,控制演算法 由超類主導一切,它擁有演算法,並且保護這個演算法
Coffee與Tea之間存在重複程式碼 有超類的存在,因此可以將程式碼複用最大化
對於演算法所做的程式碼改變,需要開啟各個子類修改很多地方 演算法只存在一個地方,容易修改
彈性差,新種類的飲料加入需要做很多工作 彈性高,新飲料的加入只需要實現自己的沖泡和加料方法即可
演算法的知識和它的實現分散在許多類中 超類專注於演算法本身,而由子類提供完整的實現。
一個完整的模板模式超類的定義:
/**
* 基類宣告為抽象類的原因是
* 其子類必須實現其操作
*/
abstract class AbstractClass {
/**
* 模板方法,被宣告為final以免子類改變這個演算法的順序
*/
final void templateMethod() {
}
/**
* 具體操作延遲到子類中實現
*/
abstract void primitiveOperation1();
abstract void primitiveOperation2();
/**
* 具體操作且共用的方法定義在超類中,可以被模板方法或子類直接使用
*/
final void concreteOperation() {
// 實現
}
/**
* 鉤子方法是一類"預設不做事的方法"
* 子類可以視情況決定要不要覆蓋它們。
*/
void hook() {
// 鉤子方法
}
}
什麼是鉤子方法:鉤子方法在我理解就是當我們需要用法從其他地方才能獲取的值時,我們需要一個鉤子把它鉤過來,在下面的程式碼裡就是我們從主類鉤過來了使用者的輸入,再在其他類中根據這個輸入做具體的操作,這樣使我們的程式更加靈活。
import java.util.Scanner;
abstract class CaffeineBeverage{
final void prepareRecipe(){
boilWater();
brew();
pourInCap();
if(customerWantsCondiments()){
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
public void boilWater(){
System.out.println("將水煮沸");
}
public void pourInCap(){
System.out.println("將飲料倒進杯子");
}
/**
* 鉤子方法
* 超類中通常是預設實現
* 子類可以選擇性的覆寫此方法
* @return
*/
public boolean customerWantsCondiments(){
return true;
}
}
class Coffee extends CaffeineBeverage{
public void brew(){
System.out.println("沖泡咖啡");
}
public void addCondiments(){
System.out.println("加入糖和牛奶");
}
/**
* 子類覆寫了鉤子函式,實現自定義功能
* @return
*/
public boolean customerWantsCondiments(){
String str = getUserInput();
if(str.equals("y"))
return true;
else
return false;
}
private String getUserInput() {
String answer = null;
System.out.println("您想要在咖啡中加入牛奶或糖嗎 (y/n)?");
Scanner scanner = new Scanner(System.in);
answer = scanner.nextLine();
return answer;
}
}
class Test{
public static void main(String[] args) {
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
}
}