通过模拟咖啡馆的点单系统来剖析装饰者模式的使用

参考:https://blog.csdn.net/gududedabai/article/details/81989196

一)、传统的点单系统构建,每一个种类的咖啡都定义一个类

  • 弊端:
  1. 如果为每一种混合咖啡都定义一个类,那么,会产生很多的类对象。
  2. 混合咖啡的价格是在单品咖啡的基础上的,如若某一单品咖啡的价格

    发生改变,那么就要修改与之关联的所有的混合咖啡的价格。

咖啡的共同属性:

/**
* 抛转引玉
* --:咖啡订单系统
* 1.咖啡店售卖四款基础咖啡
* Espressio, ShortBlack, LongBlack, Decaf
* 2.可以在四款基础咖啡的基础上加入调料,例如Milk,Soy、Chocolate ,组成混合咖啡
* 3.每款咖啡都共同的description属性,和getDecription(),指料明加入的调料,cost()计算咖啡所需的价格。
*/ public abstract class Coffee {
/**
* 描述咖啡加入的调料+单品种类
*/
private String description; public Coffee() {
} public void setDescription(String description) {
this.description = description;
} public Coffee(String description) {
this.description = description;
} /**
* 打印购买的咖啡信息
*/
void getDescription(){
System.out.println(description);
} /**
* 计算咖啡所需花费的价格
* @return
*/
public abstract int CoffeePrice();
}

单品咖啡:

/**
* 低糖咖啡
*/
public class Decaf extends Coffee {
Decaf(String description){
super(description);
}
@Override
public int CoffeePrice() {
return 30;
}
}

单品咖啡:

/**
* 浓咖啡
*/
public class Espressio extends Coffee {
Espressio(String description){
super(description);
}
@Override
public int CoffeePrice() {
return 10;
}
}

单品咖啡:

/**
* 黑咖啡
*/
public class LongBlack extends Coffee{
LongBlack(String description){
super(description);
}
@Override
public int CoffeePrice() {
return 20;
}
}

单品咖啡:

/**
* 浓缩咖啡
*/
public class ShortBlack extends Coffee{
ShortBlack(String description){
super(description);
}
@Override
public int CoffeePrice() {
return 15;
}
}

混合咖啡:

/**
* 组合咖啡: 无糖+牛奶
*/
public class DecafAndMilk extends Coffee{
DecafAndMilk(String description){
super(description);
}
@Override
public int CoffeePrice() {
return 35;
}
}

售卖咖啡:

   1)、在new 对象时就指名加入的调料,getDescription()时打印咖啡种类和加

	入的调料,直接调用CoffeePrice()返回咖啡的价格。

2)、需要为每一种混合咖啡都创建一个咖啡对象

/**
* 售卖咖啡
* 使用传统方式来构建售卖咖啡的类:
* --: 所有的混合咖啡都实现了超类Coffee
* 此时,出现了一个问题
* ---》1.因为调料的种类很多,调料与调料之间的组合方式也很多,这时咖啡类的数量就会增多。
* 2.因为所有的混合咖啡都是在单品咖啡的基础上构建的,当单品咖啡的价格发生了调整,所有
* 与单品咖啡相关的混合咖啡的价格都要进行调整。
*/
public class SaleCoffee {
public static void main(String[] args) {
Coffee coffee = new Decaf("无糖咖啡");
//打印咖啡的种类和价格
coffee.getDescription();
System.out.println(coffee.CoffeePrice()); //无糖+牛奶的咖啡
Coffee coffee1 = new DecafAndMilk("无糖咖啡:+牛奶");
coffee1.getDescription();
System.out.println(coffee1.CoffeePrice());
}
}

结果:

无糖咖啡
30
无糖咖啡:+牛奶
35

二)、将调料声明在超类中,在单品咖啡的基础上加入调料,只需定义单品咖啡类即可

  • 好处

    1.减少了组合咖啡类的定义,通过判断hasXxx()可以在四个单品咖啡的 基础上加调料,即可以通过四个单品类来得到很多的组合咖啡
  • 弊端
  1. 当需要加入一种调料时,需要修改超类中的代码,这样违反了代码的开闭原则, 一旦修改了代码就会有产生bug的风险。
  2. 当用户需要加两份调料,如:加入两份牛奶时不能通过hasMilk()来计算咖啡的价格。

所有咖啡的超类:

将所有的调料以boolean的形式声明在超类中,并通过hasXxx()来判断是否加

    入调料以及计算咖啡的价格。

import com.sun.xml.internal.ws.util.StringUtils;

/**
* 构建第二种形式的咖啡超类
* --: 一开始就给定了咖啡的调料
*/
public abstract class Coffee {
/**
* 描述咖啡的种类和调料
*/
private String description; /**
* 将咖啡的调料内置在超类中
*/
private boolean milk; private boolean soy; private boolean chocolate; public Coffee(String description) {
this.description = description;
} public Coffee() {
} /**
* 判断是否加了牛奶
* @return
*/
public Boolean hashMilk(){
return milk;
} /**
* 判断是否加了豆浆
* @return
*/
public Boolean hashSoy(){
return soy;
} /**
* 判断是否加了巧克力
* @return
*/
public Boolean hashChocolate(){
return chocolate;
} /**
* 根据咖啡的种类和调料计算咖啡的价格
*/
public abstract int CoffeePrice(); public void getDescription() {
System.out.println(description);
} public void setDescription(String description) {
this.description += description;
} public void setMilk(boolean milk) {
this.milk = milk;
if(milk == true) {
setDescription("+牛奶");
}
} public void setSoy(boolean soy){
this.soy = soy;
if(soy == true){
setDescription("+豆浆");
}
} public void setChocolate(boolean chocolate) {
this.chocolate = chocolate;
if(chocolate == true) {
setDescription("+chocolate");
}
}
}

单品咖啡:

通过hasXxx()来计算最终的价格。

**
* 低糖咖啡
*/
public class Decaf extends Coffee {
Decaf(String description){
super(description);
}
@Override
public int CoffeePrice() {
//单品低糖咖啡的价格为30
int cost = 30;
if(this.hashMilk()){
cost = cost + 5;
}
if(this.hashChocolate()){
cost = cost + 10;
}
if(this.hashSoy()){
cost = cost + 2;
}
return cost;
}
}

单品咖啡:

/**
* 浓咖啡
*/
public class Espressio extends Coffee {
Espressio(String description){
super(description);
}
@Override
public int CoffeePrice() {
//单品浓咖啡的价格为10
int cost = 10;
if(this.hashMilk()){
cost = cost + 5;
}
if(this.hashChocolate()){
cost = cost + 10;
}
if(this.hashSoy()){
cost = cost + 2;
}
return cost;
}
}

单品咖啡:

/**
* 黑咖啡
*/
public class LongBlack extends Coffee{
LongBlack(String description){
super(description);
} /**
* 判断当前种类的咖啡是否有加入调料,若有则加入调料的价格
* @return
*/
@Override
public int CoffeePrice() {
//单品黑咖啡的价格为20
int cost = 20;
if(this.hashMilk()){
cost = cost + 5;
}
if(this.hashChocolate()){
cost = cost + 10;
}
if(this.hashSoy()){
cost = cost + 2;
}
return cost;
}
}

单品咖啡:

/**
* 浓缩咖啡
*/
public class ShortBlack extends Coffee{
ShortBlack(String description){
super(description);
}
@Override
public int CoffeePrice() {
//单品浓缩咖啡的价格为15
int cost = 15;
if(this.hashMilk()){
cost = cost + 5;
}
if(this.hashChocolate()){
cost = cost + 10;
}
if(this.hashSoy()){
cost = cost + 2;
}
return cost;
}
}

售卖咖啡:

/**
* 将所有的调料放在超类中
* --:减少了组合咖啡类的定义,通过判断hasXxx()可以在四个单品咖啡的基础上加调料,即可以通过四个单品类来得到很多的组合咖啡
*
* 弊端:
* --:1.当需要加入一种调料时,需要修改超类中的代码,这样违反了代码的开闭原则,一旦修改了代码就会有产生bug的风险。
* 2.当用户需要加两份调料,如:加入两份牛奶时不能通过hasMilk()来计算咖啡的价格。
*
*/
public class SaleCoffee {
public static void main(String[] args) {
//先选择咖啡单品
Coffee coffee = new Decaf("无糖咖啡:");
//组合咖啡: 无糖 + 牛奶
coffee.setMilk(true);
coffee.setChocolate(false);
coffee.getDescription();
System.out.println(coffee.CoffeePrice());
}
} 结果: 无糖咖啡:+牛奶
35

三)你要喝什么味的咖啡?

使用装饰者模式来实现不同咖啡种类的搭配:

(装饰者模式的模型)

主体接口类:

被装饰类:

    装饰类:

           具体装饰类1;具体装饰类2;具体装饰类3;

           具体装饰类具有叠加效果

主体接口类:

/**
* 实现装饰者模式的逻辑:
* 1.公共接口类:
* --:需要一个公共接口类
* 该接口定义了被装饰类的主要逻辑方法,被装饰者和装饰者分别去实现或继承这个接口
* 2.被装饰类:
* 被装饰者实现公共接口类,并对接口方法做具体实现
* 3.装饰类:
* 装饰类接收被装饰类对象,调用被装饰类的方法
* 4.具体装饰类
* 继承装饰类,做具体的装饰逻辑实现
*/ /**
* 公共接口类:
* --: 定义需要装饰的接口方法以及公共的对象属性
*/
public abstract class Coffee {
private String description; private double price; /**
* 获取咖啡的价格
* @return
*/
public abstract double getCoffeePrice(); public abstract String getCoffeeDescription(); public String getDescription() {
return description;
} public void setDescription(String description) {
this.description = description;
} public double getPrice() {
return price;
} public void setPrice(double price) {
this.price = price;
}
}

被装饰类:

/**
* 被装饰类:
* --:该类为咖啡单品类,可以为该咖啡加各种调料,然后计算咖啡的价格以及获取咖啡的种类和所加的调料
*/
public class Decaf extends Coffee{ /**
* 因为是单品咖啡,所以对象刚创建时价格和种类就已经确定了
*/
Decaf(){
setPrice(30);
setDescription("低糖:");
}
@Override
public double getCoffeePrice() {
return this.getPrice();
} @Override
public String getCoffeeDescription() {
return this.getDescription();
}
}

装饰类:

/**
* 装饰类:
* --:接收被装饰对象,调用被装饰对象的方法
*/
public class Decorator extends Coffee{
/**
* 被装饰对象
*/
private Coffee coffee = null;
/**
* 一初始化就有传入一个被装饰对象
*/
Decorator(Coffee coffee){
this.coffee = coffee;
} @Override
public double getCoffeePrice() {
//使用被装饰者的功能
return coffee.getCoffeePrice();
} @Override
public String getCoffeeDescription(){
return coffee.getCoffeeDescription();
}
}

具体装饰类:

/**
* 具体的装饰者实现类
* --:继承装饰类
*/
public class Milk extends Decorator{
Milk(Coffee coffee) {
super(coffee);
setPrice(10);
setDescription("+牛奶");
}
@Override
public double getCoffeePrice() {
//使用被装饰者的功能
return super.getCoffeePrice()+this.getPrice();
} @Override
public String getCoffeeDescription(){
return super.getCoffeeDescription()+this.getDescription();
}
}

具体装饰类:

public class Chocolate extends Decorator {
Chocolate(Coffee coffee) {
super(coffee);
setPrice(15);
setDescription("+chocolate");
}
@Override
public double getCoffeePrice() {
//使用被装饰者的功能
return super.getCoffeePrice()+this.getPrice();
} @Override
public String getCoffeeDescription(){
return super.getCoffeeDescription()+this.getDescription();
} }

生产咖啡:

public class SaleCoffee {
public static void main(String[] args) {
//加双份牛奶
Decorator decorator = new Decorator(new Milk(new Milk(new Decaf())));
System.out.println(decorator.getCoffeeDescription());
System.out.println(decorator.getCoffeePrice());
}
}

结果:

低糖:+牛奶+牛奶
50.0

四)、装饰者模式举例二、你要吃什么味道的鸡腿堡?

参考: https://blog.csdn.net/jason0539/article/details/22713711

主题接口类:

/**
* 使用汉堡店买汉堡的例子来实现装饰者模式
* --:汉堡可以选择加生菜、火腿、沙拉、番茄酱
*/
public abstract class Hamburger {
/**
* 汉堡名字
*/
private String name; /**
* 汉堡的价格
*/
private double price; /**
* 制作汉堡
*/
public abstract String product(); public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public double getPrice() {
return price;
} public void setPrice(double price) {
this.price = price;
}
}

被装饰类:

/**
* 基础汉堡
*/
public class ChickenBurger extends Hamburger{
ChickenBurger(){
setName("鸡腿堡");
setPrice(25);
}
@Override
public String product() {
return this.getName()+": " + this.getPrice();
}
}

装饰类:

public class Decorator extends Hamburger {
Hamburger hamburger;
Decorator(Hamburger hamburger){
this.hamburger = hamburger;
} public Decorator() {
} @Override
public String product() {
return hamburger.product();
}
}

具体装饰类:

/**
* 装饰类,给汉堡加生菜
*/
public class Lettuce extends Decorator{
Lettuce(Hamburger hamburger) {
this.hamburger = hamburger;
setName(hamburger.getName()+"+生菜");
setPrice(hamburger.getPrice()+2);
}
@Override
public String product(){
return getName()+": "+getPrice();
}
}

具体装饰类:

/**
* 给火腿加鸡蛋
*/
public class Age extends Decorator{
Age(Hamburger hamburger) {
this.hamburger = hamburger;
setName(hamburger.getName()+"+鸡蛋");
setPrice(hamburger.getPrice()+1.5);
} @Override
public String product() {
return getName()+": "+getPrice();
}
}

制作汉堡:

public class SaleHamburger {
public static void main(String[] args) {
//鸡腿堡 +生菜 + 鸡蛋
Decorator decorator = new Decorator(new Lettuce(new Age(new ChickenBurger())));
//原味鸡腿堡
Decorator decorator1 = new Decorator(new ChickenBurger());
//鸡腿堡 +生菜
Decorator decorator2 = new Decorator(new Lettuce(new ChickenBurger()));
//鸡腿堡 +鸡蛋
Decorator decorator3 = new Decorator(new Age(new ChickenBurger())); System.out.println(decorator.product());
System.out.println(decorator1.product());
System.out.println(decorator2.product());
System.out.println(decorator3.product());
}
}

结果:

鸡腿堡+鸡蛋+生菜: 28.5
鸡腿堡: 25.0
鸡腿堡+生菜: 27.0
鸡腿堡+鸡蛋: 26.5

五)、装饰者模式在java Jdk中的应用

装饰者模式应用在java Jdk api文档中的IO流机制

I) :主题接口类:InputStream

   II):被装饰类:

         FileInputStream; StringBufferInputStream, ByteArrayInputStream

        装饰类:

         FilterInputStream

                III): 具体实现类:

                         BufferInputStream; DataInputStream; LineNumberInputStream

使用jdk设计好的装饰者模式,将流对象中的小写字母转为大写字母

具体装饰类:

 继承FilterInpustream

/**
* java的Io流机制就使用了装饰模式
* InputStream: 接口类
* FileInputStream: StringBufferInputStream: ByteArrayInPutStream : 被装饰类
* FilterInputStream: 装饰类接口
* --: 具体装饰类
* BufferInputStream: DataInputStream: LineNumberInputStream
*
*/ import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream; /**
* 实现一个将流中的小写字母转为大写字母的装饰者对象
*/
public class UpperCaseInputStream extends FilterInputStream { protected UpperCaseInputStream(InputStream in) {
super(in);
} @Override
//读取流,将流中的小写字母转为大写字母
public int read() throws IOException//单字符的读
{
int c=super.read();//这个super.read()就是调用上面super(in);的主题对象
return c==-1?c:Character.toUpperCase((char)(c));
} @Override
public int read(byte[] b,int offset,int len) throws IOException//多字符的读
{
int result=super.read(b,offset,len);
for(int i=0;i<result;i++)
{
b[i]=(byte)Character.toUpperCase((char)(b[i]));
}
return result;
}
}

使用类:

public class Test {
public static void main(String[] args) {
int c;
try {
InputStream in = new UpperCaseInputStream(new BufferedInputStream(
new FileInputStream("F:\\test.txt")));
while((c=in.read())>=0)
{
System.out.print((char)c); }
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} } }
05-08 08:33