在咖啡厅中,有多种不同类型的咖啡,客户在预定了咖啡之后,还可以选择添加不同的调料来调整咖啡的口味,当客户点了咖啡添加了不同的调料,咖啡的价格需要做出相应的改变。
要求
:程序实现具有良好的拓展性、改动方便、维护方便
【方案一】
写一个抽象类,然后将所有咖啡和调料组合形成多个类来继承抽象类,缺点
:当增加一个单品咖啡,或者调味,类的数量就会大增,产生类爆炸问题
【方案二】
分析:
【被装饰主体】
package com.test.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;
}
/**
* 计算费用的抽象方法,需要子类来实现
* @return
*/
public abstract float cost();
}
【缓冲类:整合所有咖啡的共同点,这个类不一定要写,要结合实际情况】
package com.test.decorator;
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
【单品咖啡:意大利咖啡】
package com.test.decorator;
public class Espresso extends Coffee {
public Espresso() {
setDes(" 意大利咖啡 ");
// 初始化意大利咖啡的价格
setPrice(6.0f);
}
}
【单品咖啡:美式咖啡】
package com.test.decorator;
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longblack ");
setPrice(5.0f);
}
}
【单品咖啡:浓咖啡】
package com.test.decorator;
public class ShortBlack extends Coffee{
public ShortBlack() {
setDes(" shortblack ");
setPrice(4.0f);
}
}
【装饰者】
package com.test.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.test.decorator;
/**
* 具体的Decorator, 这里就是调味品
*/
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes(" 巧克力 ");
// 调味品 的价格
setPrice(3.0f);
}
}
【具体装饰者:牛奶】
package com.test.decorator;
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes(" 牛奶 ");
setPrice(2.0f);
}
}
【具体装饰者:豆浆】
package com.test.decorator;
public class Soy extends Decorator{
public Soy(Drink obj) {
super(obj);
setDes(" 豆浆 ");
setPrice(1.5f);
}
}
【主类】
package com.test.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
只需要新增一个单品咖啡类,就可以购买了,拓展性非常强大
【新增单品咖啡:无因咖啡】
package com.test.decorator;
public class DeCaf extends Coffee {
public DeCaf() {
setDes(" 无因咖啡 ");
setPrice(1.0f);
}
}
【主类】
package com.test.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
【抽象主体】
package com.test.decorator.Sample;
public abstract class Display {
/**
* 获取横向字符数(抽象方法,需要子类去实现)
* @return
*/
public abstract int getColumns();
/**
* 获取纵向行数(抽象方法,需要子类去实现)
* @return
*/
public abstract int getRows();
/**
* 获取第row行的字符串(抽象方法,需要子类去实现)
* @param row
* @return
*/
public abstract String getRowText(int row);
/**
* 显示所有行的字符串
*/
public void show() {
// 遍历行数
for (int i = 0; i < getRows(); i++) {
// 获取改行的字符串来打印出来
System.out.println(getRowText(i));
}
}
}
【具体主体】
package com.test.decorator.Sample;
/**
* 该类用来显示单行字符串
*/
public class StringDisplay extends Display {
/**
* 要显示的字符串
*/
private String string;
/**
* 构造方法
*
* @param string 要显示的字符串
*/
public StringDisplay(String string) {
this.string = string;
}
@Override
public int getColumns() {
// 字符数
return string.getBytes().length;
}
@Override
public int getRows() {
// 行数是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.test.decorator.Sample;
/**
* 装饰者抽象类,注意要继承抽象主体,并聚合抽象主体
*/
public abstract class Border extends Display {
/**
* 表示被装饰物
*/
protected Display display;
protected Border(Display display) {
// 在生成实例时通过参数指定被装饰物
this.display = display;
}
}
【具体修饰者1】
package com.test.decorator.Sample;
/**
* 在字符串的左右两侧添加边框
*/
public class SideBorder extends Border {
/**
* 表示装饰边框的字符
*/
private char borderChar;
/**
* 通过构造函数指定Display和装饰边框字符
* @param display
* @param ch
*/
public SideBorder(Display display, char ch) {
super(display);
this.borderChar = ch;
}
/**
* 字符数为字符串字符数加上两侧边框字符数
* @return
*/
public int getColumns() {
return 1 + display.getColumns() + 1;
}
/**
* 行数即被装饰物的行数
* @return
*/
public int getRows() {
// 在字符串的两侧添加字符并不会增加行数,所以直接返回主体的行数即可
return display.getRows();
}
/**
* 指定的那一行的字符串为被装饰物的字符串加上两侧的边框的字符
* @param row
* @return
*/
public String getRowText(int row) {
return borderChar + display.getRowText(row) + borderChar;
}
}
【具体装饰者2】
package com.test.decorator.Sample;
/**
* 在字符串的上下左右都加上装饰框
*/
public class FullBorder extends Border {
public FullBorder(Display display) {
super(display);
}
public int getColumns() {
// 字符数为被装饰物的字符数加上两侧边框字符数
return 1 + display.getColumns() + 1;
}
public int getRows() {
// 行数为被装饰物的行数加上上下边框的行数
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.test.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
被装饰者
可以将子类的实例保存到父类的变量中,也可以直接调用从父类中继承的方法
使用委托让接口具有透明性时,自己和被委托对象具有一致性
Rose和Violet都有相同的method方法。Rose将method方法的处理委托给了 Violet。这两个类虽然都有 method 方法,但是没有明确在代码中体现出“共通性”。
如果要明确地表示method方法是共通的,只需要像下面这样编写一个抽象类Flower,然后让Rose和Violet都继承并实现方法即可。
或者让Flower作为接口