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

参考: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();
} } }

装饰者模式学习:模拟咖啡馆的点单系统来剖析装饰者模式的使用 + 装饰者模式在java I/O 中的应用的更多相关文章

  1. SQL反模式学习笔记1 开篇

    什么是“反模式” 反模式是一种试图解决问题的方法,但通常会同时引发别的问题. 反模式分类 (1)逻辑数据库设计反模式 在开始编码之前,需要决定数据库中存储什么信息以及最佳的数据组织方式和内在关联方式. ...

  2. SQL反模式学习笔记5 外键约束【不用钥匙的入口】

    目标:简化数据库架构 一些开发人员不推荐使用引用完整性约束,可能不使用外键的原因有一下几点: 1.数据更新有可能和约束冲突: 2.当前的数据库设计如此灵活,以至于不支持引用完整性约束: 3.数据库为外 ...

  3. SQL反模式学习笔记3 单纯的树

    2014-10-11 在树形结构中,实例被称为节点.每个节点都有多个子节点与一个父节点. 最上层的节点叫做根(root)节点,它没有父节点. 最底层的没有子节点的节点叫做叶(leaf). 中间的节点简 ...

  4. SQL反模式学习笔记2 乱穿马路

    程序员通常使用逗号分隔的列表来避免在多对多的关系中创建交叉表, 将这种设计方式定义为一种反模式,称为“乱穿马路”. 目标:  存储多属性值,即多对一 反模式:将多个值以格式化的逗号分隔存储在一个字段中 ...

  5. SQL反模式学习笔记4 建立主键规范【需要ID】

    目标:建立主键规范 反模式:每个数据库中的表都需要一个伪主键Id 在表中,需要引入一个对于表的域模型无意义的新列来存储一个伪值,这一列被用作这张表的主键, 从而通过它来确定表中的一条记录,即便其他的列 ...

  6. SQL反模式学习笔记6 支持可变属性【实体-属性-值】

    目标:支持可变属性 反模式:使用泛型属性表.这种设计成为实体-属性-值(EAV),也可叫做开放架构.名-值对. 优点:通过增加一张额外的表,可以有以下好处 (1)表中的列很少: (2)新增属性时,不需 ...

  7. SQL反模式学习笔记7 多态关联

    目标:引用多个父表 反模式:使用多用途外键.这种设计也叫做多态关联,或者杂乱关联. 多态关联和EAV有着相似的特征:元数据对象的名字是存储在字符串中的. 在多态关联中,父表的名字是存储在Issue_T ...

  8. SQL反模式学习笔记8 多列属性

    目标:存储多值属性 反模式:创建多个列.比如一个人具有多个电话号码.座机号码.手机号码等. 1.查询:多个列的话,查询时可能不得不用IN,或者多个OR: 2.添加.删除时确保唯一性.判断是否有值:这些 ...

  9. SQL反模式学习笔记9 元数据分裂

    目标:支持可扩展性.优化数据库的结构来提升查询的性能以及支持表的平滑扩展. 反模式:克隆表与克隆列 1.将一张很长的表拆分成多张较小的表,使用表中某一个特定的数据字段来给这些拆分出来的表命名. 2.将 ...

随机推荐

  1. unittest-A接口的返回结果作为B接口的入参(设置全局变量)

    在A接口用例中设置全局变量: globals()["a"] = "用例A的返回结果" 在B接口用例中使用全局变量: b = globals()["a& ...

  2. 百万年薪python之路 -- 数据库初始

    一. 数据库初始 1. 为什么要有数据库? ​ 先来一个场景: ​ 假设现在你已经是某大型互联网公司的高级程序员,让你写一个火车票购票系统,来hold住十一期间全国的购票需求,你怎么写? 由于在同一时 ...

  3. JVM学习记录1--JVM内存布局

    先上个图 这是根据<Java虚拟机规范(第二版)>所画的jvm内存模型. 程序计数器:程序计数器是用来记录当前线程方法执行顺序的,对应的就是我们编程中一行行代码的执行顺序,如分支,跳转,循 ...

  4. Java基于回调的观察者模式详解

    本文由“言念小文”原创,转载请说明文章出处 一.前言 什么是回调?回调如何使用?如何优雅的使用?本文将首先详解回调的原理,然后介绍回调的基本使用方法,最后介绍基于回调的“观察者模式”实现,演示如何优化 ...

  5. OracleService服务不见了|OracleServiceXE服务没有了

    服务里面本来应该有OracleService的(或者是Express版的OracleServiceXE),而服务列表没有此服务项,而启动数据库时出现: TNS监听程序当前无法识别连接描述符中请求的服务 ...

  6. WordCount的实现和测试

    WordCount 一.开头 (1)合作者:201631107110,201631083416 (2)代码地址:https://gitee.com/zhaoxiaoqin/WordCount.git ...

  7. Docker入门详解——安装docker并利用docker搭建lnmp

    首先我们需先安装docker环境,这个比较简单,以centos7为例 docker在centos7上安装需要系统内核版本3.10+,可以通过uname -r查看内核版本号,如果版本不符请自行查阅资料更 ...

  8. (Java) JWT-TokenUtils

    package com.vcgeek.hephaestus.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; imp ...

  9. 一篇文章让你彻底理解java中抽象类和接口

    目录 1.我所理解的抽象类 2.我所理解的接口 3.抽象类和接口本质区别 相信大家都有这种感觉:抽象类与接口这两者有太多相似的地方,又有太多不同的地方.往往这二者可以让初学者摸不着头脑,无论是在实际编 ...

  10. 【Linux】【自学笔记】docker搭建一个spring-boot程序

    写在开始    最近捣腾Linux,安装虚拟机VMware并安装了CentOS 7系统,开始研究Linux,但是无从下手,就结合工作中用到的东西一步一步研究,事实并不是那么顺利.特此开博客,记录在过程 ...