生活案例
咖啡廳 咖啡定制案例
在咖啡廳中,有多種不同類型的咖啡,客戶在預定了咖啡之后,還可以選擇添加不同的調(diào)料來調(diào)整咖啡的口味,當客戶點了咖啡添加了不同的調(diào)料,咖啡的價格需要做出相應的改變。
要求
:程序?qū)崿F(xiàn)具有良好的拓展性、改動方便、維護方便
【方案一】
寫一個抽象類Drink,然后將所有咖啡和調(diào)料組合形成多個類來繼承抽象類,缺點
:當增加一個單品咖啡,或者調(diào)味,類的數(shù)量就會大增,產(chǎn)生類爆炸問題
【方案二】
分析:
- 可以控制類的數(shù)量,不至于造成很多的類
- 增加或者刪除調(diào)料種類時,代碼的維護量很大
- 如果同樣一種調(diào)料可以點多份時,可以將 hasMilk 返回一個對應int類型的數(shù)據(jù)來表示調(diào)料的份數(shù)
裝飾者模式介紹
介紹
- 動態(tài)的將新功能附加到對象上。在對象功能擴展方面,它比繼承更好,裝飾者模式也體現(xiàn)了開閉原則(ocp)
- 假如有一塊蛋糕,如果加上奶油,就變成了奶油蛋糕;再加上草莓,就變成了草莓奶油蛋糕。整個過程就是在不斷裝飾蛋糕的過程。根據(jù)裝飾者模式編寫的程序的對象與蛋糕十分相似。首先有一個相當于蛋糕的對象,然后像不斷地裝飾蛋糕一樣地不斷地對其增加功能,它就變成了使用目的更加明確的對象。
出場角色
- Component(主體,被裝飾對象):增加功能時的核心角色,定義了接口(API)
- ConcreteComponent(具體主體):實現(xiàn)了Component角色所定義的接口
- Decorator(裝飾者):該角色具有與Component角色相同的接口(API),在它內(nèi)部保存了Component角色
- ConcreteDecorator( 具體的裝飾者)
案例實現(xiàn)
案例一(咖啡廳問題)
類圖
代碼實現(xiàn)
【被裝飾主體】
package com.atguigu.decorator;
public abstract class Drink {
/**
* 描述
*/
public String des;
/**
* 價格
*/
private float price = 0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
/**
* 計算費用的抽象方法,需要子類來實現(xiàn)
* @return
*/
public abstract float cost();
}
【緩沖類:整合所有咖啡的共同點,這個類不一定要寫,要結合實際情況】
package com.atguigu.decorator;
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
【單品咖啡:意大利咖啡】
package com.atguigu.decorator;
public class Espresso extends Coffee {
public Espresso() {
setDes(" 意大利咖啡 ");
// 初始化意大利咖啡的價格
setPrice(6.0f);
}
}
【單品咖啡:美式咖啡】
package com.atguigu.decorator;
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longblack ");
setPrice(5.0f);
}
}
【單品咖啡:濃咖啡】
package com.atguigu.decorator;
public class ShortBlack extends Coffee{
public ShortBlack() {
setDes(" shortblack ");
setPrice(4.0f);
}
}
【裝飾者】
package com.atguigu.decorator;
/**
* 裝飾物,繼承了Drink,還聚合了Drink
*/
public class Decorator extends Drink {
private Drink obj;
/**
* 聚合Drink
* @param obj
*/
public Decorator(Drink obj) {
this.obj = obj;
}
@Override
public float cost() {
// getPrice 自己價格 + 咖啡的價格
return super.getPrice() + obj.cost();
}
/**
* 輸出信息
* @return
*/
@Override
public String getDes() {
// obj.getDes() 輸出被裝飾者的信息
return des + " " + getPrice() + " && " + obj.getDes();
}
}
【具體裝飾者:巧克力】
package com.atguigu.decorator;
/**
* 具體的Decorator, 這里就是調(diào)味品
*/
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes(" 巧克力 ");
// 調(diào)味品 的價格
setPrice(3.0f);
}
}
【具體裝飾者:牛奶】
package com.atguigu.decorator;
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes(" 牛奶 ");
setPrice(2.0f);
}
}
【具體裝飾者:豆?jié){】
package com.atguigu.decorator;
public class Soy extends Decorator{
public Soy(Drink obj) {
super(obj);
setDes(" 豆?jié){ ");
setPrice(1.5f);
}
}
【主類】
package com.atguigu.decorator;
public class CoffeeBar {
public static void main(String[] args) {
System.out.println("============== 訂單1 =============");
// 裝飾者模式下的訂單:2份巧克力+一份牛奶的LongBlack
// 1. 點一份 LongBlack
Drink order = new LongBlack();
System.out.println("費用=" + order.cost());
System.out.println("描述=" + order.getDes());
System.out.println();
// 2.加入一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 費用 =" + order.cost());
System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
System.out.println();
// 3.加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 費用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());
System.out.println();
// 4.加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入2份巧克力 費用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDes());
System.out.println();
}
}
【運行】
============== 訂單1 =============
費用=5.0
描述= longblack
order 加入一份牛奶 費用 =7.0
order 加入一份牛奶 描述 = 牛奶 2.0 && longblack
order 加入一份牛奶 加入一份巧克力 費用 =10.0
order 加入一份牛奶 加入一份巧克力 描述 = 巧克力 3.0 && 牛奶 2.0 && longblack
order 加入一份牛奶 加入2份巧克力 費用 =13.0
order 加入一份牛奶 加入2份巧克力 描述 = 巧克力 3.0 && 巧克力 3.0 && 牛奶 2.0 && longblack
咖啡樣式拓展代碼實現(xiàn)
只需要新增一個單品咖啡類,就可以購買了,拓展性非常強大
【新增單品咖啡:無因咖啡】
package com.atguigu.decorator;
public class DeCaf extends Coffee {
public DeCaf() {
setDes(" 無因咖啡 ");
setPrice(1.0f);
}
}
【主類】
package com.atguigu.decorator;
public class CoffeeBar {
public static void main(String[] args) {
System.out.println("============== 訂單2 =============");
Drink order2 = new DeCaf();
System.out.println("order2 無因咖啡 費用 =" + order2.cost());
System.out.println("order2 無因咖啡 描述 = " + order2.getDes());
System.out.println();
order2 = new Milk(order2);
System.out.println("order2 無因咖啡 加入一份牛奶 費用 =" + order2.cost());
System.out.println("order2 無因咖啡 加入一份牛奶 描述 = " + order2.getDes());
System.out.println();
}
}
【運行】
============== 訂單2 =============
order2 無因咖啡 費用 =1.0
order2 無因咖啡 描述 = 無因咖啡
order2 無因咖啡 加入一份牛奶 費用 =3.0
order2 無因咖啡 加入一份牛奶 描述 = 牛奶 2.0 && 無因咖啡
Process finished with exit code 0
案例二
類圖
代碼實現(xiàn)
【抽象主體】
package com.atguigu.decorator.Sample;
public abstract class Display {
/**
* 獲取橫向字符數(shù)(抽象方法,需要子類去實現(xiàn))
* @return
*/
public abstract int getColumns();
/**
* 獲取縱向行數(shù)(抽象方法,需要子類去實現(xiàn))
* @return
*/
public abstract int getRows();
/**
* 獲取第row行的字符串(抽象方法,需要子類去實現(xiàn))
* @param row
* @return
*/
public abstract String getRowText(int row);
/**
* 顯示所有行的字符串
*/
public void show() {
// 遍歷行數(shù)
for (int i = 0; i < getRows(); i++) {
// 獲取改行的字符串來打印出來
System.out.println(getRowText(i));
}
}
}
【具體主體】
package com.atguigu.decorator.Sample;
/**
* 該類用來顯示單行字符串
*/
public class StringDisplay extends Display {
/**
* 要顯示的字符串
*/
private String string;
/**
* 構造方法
*
* @param string 要顯示的字符串
*/
public StringDisplay(String string) {
this.string = string;
}
@Override
public int getColumns() {
// 字符數(shù)
return string.getBytes().length;
}
@Override
public int getRows() {
// 行數(shù)是1
return 1;
}
/**
* 只有第0行可以獲取到字符串,其他都是空
* @param row
* @return
*/
@Override
public String getRowText(int row) {
// 僅當row為0時返回值
if (row == 0) {
return string;
} else {
return null;
}
}
}
【抽象裝飾者】
package com.atguigu.decorator.Sample;
/**
* 裝飾者抽象類,注意要繼承抽象主體,并聚合抽象主體
*/
public abstract class Border extends Display {
/**
* 表示被裝飾物
*/
protected Display display;
protected Border(Display display) {
// 在生成實例時通過參數(shù)指定被裝飾物
this.display = display;
}
}
【具體修飾者1】
package com.atguigu.decorator.Sample;
/**
* 在字符串的左右兩側添加邊框
*/
public class SideBorder extends Border {
/**
* 表示裝飾邊框的字符
*/
private char borderChar;
/**
* 通過構造函數(shù)指定Display和裝飾邊框字符
* @param display
* @param ch
*/
public SideBorder(Display display, char ch) {
super(display);
this.borderChar = ch;
}
/**
* 字符數(shù)為字符串字符數(shù)加上兩側邊框字符數(shù)
* @return
*/
public int getColumns() {
return 1 + display.getColumns() + 1;
}
/**
* 行數(shù)即被裝飾物的行數(shù)
* @return
*/
public int getRows() {
// 在字符串的兩側添加字符并不會增加行數(shù),所以直接返回主體的行數(shù)即可
return display.getRows();
}
/**
* 指定的那一行的字符串為被裝飾物的字符串加上兩側的邊框的字符
* @param row
* @return
*/
public String getRowText(int row) {
return borderChar + display.getRowText(row) + borderChar;
}
}
【具體裝飾者2】
package com.atguigu.decorator.Sample;
/**
* 在字符串的上下左右都加上裝飾框
*/
public class FullBorder extends Border {
public FullBorder(Display display) {
super(display);
}
public int getColumns() {
// 字符數(shù)為被裝飾物的字符數(shù)加上兩側邊框字符數(shù)
return 1 + display.getColumns() + 1;
}
public int getRows() {
// 行數(shù)為被裝飾物的行數(shù)加上上下邊框的行數(shù)
return 1 + display.getRows() + 1;
}
/**
* 指定的那一行的字符串
*
* @param row
* @return
*/
public String getRowText(int row) {
if (row == 0) { // 上邊框
return "+" + makeLine('-', display.getColumns()) + "+";
} else if (row == display.getRows() + 1) { // 下邊框
return "+" + makeLine('-', display.getColumns()) + "+";
} else { // 其他邊框
return "|" + display.getRowText(row - 1) + "|";
}
}
/**
* 生成一個重復count次字符ch的字符串
*
* @param ch
* @param count
* @return
*/
private String makeLine(char ch, int count) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < count; i++) {
buf.append(ch);
}
return buf.toString();
}
}
【主類】
package com.atguigu.decorator.Sample;
public class Main {
public static void main(String[] args) {
Display b1 = new StringDisplay("Hello, world.");
Display b2 = new SideBorder(b1, '#');
Display b3 = new FullBorder(b2);
b1.show();
b2.show();
b3.show();
Display b4 =
new SideBorder(
new FullBorder(
new FullBorder(
new SideBorder(
new FullBorder(
new StringDisplay("你好,世界。")
),
'*'
)
)
),
'/'
);
b4.show();
}
}
【運行】
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+------------------------+/
/|+----------------------+|/
/||*+------------------+*||/
/||*|你好,世界。|*||/
/||*+------------------+*||/
/|+----------------------+|/
/+------------------------+/
Process finished with exit code 0
裝飾著模式在IO流源碼的應用
- InputStream 是抽象類, 類似我們前面講的 Drink
- FileInputStream 是 InputStream 子類,類似我們前面的 DeCaf, LongBlack
- FilterInputStream 是 InputStream 子類:類似我們前面 的 Decorator 修飾者
- DataInputStream 是 FilterInputStream 子類,具體的修飾者,類似前面的 Milk, Soy 等
- FilterInputStream 類 有 protected volatile InputStream in; 即聚合了
被裝飾者
總結
- 在裝飾者模式中,裝飾者與被裝飾者具有一致性。裝飾者類是表示被裝飾者的類的子類,這就體現(xiàn)了它們之間的一致性,它們具有相同的接口,這樣,就算被裝飾者被裝飾了,接口還是向外暴露的(接口的透明性)
- 可以在不改變被裝飾者的前提下增加功能,如案例中在顯示字符串之前對字符串進行修飾
- 只需要一些裝飾物即可添加許多功能:通過自由組合調(diào)料,可以讓咖啡擁有各種不同的味道
- 裝飾者模式也有缺點:會導致程序中增加許多功能類似的很小的類
什么是父類和子類的一致性
可以將子類的實例保存到父類的變量中,也可以直接調(diào)用從父類中繼承的方法
如何讓自己和被委托對象有一致性
使用委托讓接口具有透明性時,自己和被委托對象具有一致性
Rose和Violet都有相同的method方法。Rose將method方法的處理委托給了 Violet。這兩個類雖然都有 method 方法,但是沒有明確在代碼中體現(xiàn)出“共通性”。
如果要明確地表示method方法是共通的,只需要像下面這樣編寫一個抽象類Flower,然后讓Rose和Violet都繼承并實現(xiàn)方法即可。
或者讓Flower作為接口文章來源:http://www.zghlxwxcb.cn/news/detail-609199.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-609199.html
文章說明
- 本文章為本人學習尚硅谷的學習筆記,文章中大部分內(nèi)容來源于尚硅谷視頻(點擊學習尚硅谷相關課程),也有部分內(nèi)容來自于自己的思考,發(fā)布文章是想幫助其他學習的人更方便地整理自己的筆記或者直接通過文章學習相關知識,如有侵權請聯(lián)系刪除,最后對尚硅谷的優(yōu)質(zhì)課程表示感謝。
- 本人還同步閱讀《圖解設計模式》書籍(圖解設計模式/(日)結城浩著;楊文軒譯–北京:人民郵電出版社,2017.1),進而綜合兩者的內(nèi)容,讓知識點更加全面
到了這里,關于【設計模式——學習筆記】23種設計模式——裝飾器模式Decorator(原理講解+應用場景介紹+案例介紹+Java代碼實現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!