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

参考: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. 解决Zend OPcache huge_code_pages: mmap(HUGETLB) failed: Cannot allocate memory报错

    前几日看到鸟哥介绍的 <让你的PHP7更快之Hugepage>, 于是想试试手给服务器加上,参照格式安装好扩展,调整好配置文件,然后重启php-fpm,结果启动一直报Zend OPcach ...

  2. JVM - 复习

    内存模型图 程序计数器(PC) 程序计数器的特点 PC是一小块内存空间,用于记录当前线程执行的字节码指令的地址.如果执行的是本地方法(native),PC里此时显示Undefined 优点: 控制程序 ...

  3. java中多线程 - 多线程中的基本方法

    介绍一下线程中基本的方法使用 线程睡眠sleep() Thread.sleep(毫秒);我们可以通过sleep方法设置让线程睡眠.可以看到sleep是个静态方法 public static nativ ...

  4. Java HashMap底层实现原理源码分析Jdk8

    在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依 ...

  5. SpringBoot配置文件之Yml语法

    一 使用 YAML 而不是 Properties YAML是 JSON 的超集,因此,它是用于指定分层配置数据的便捷格式.只要 class 路径上有SnakeYAML library,SpringAp ...

  6. 陈莉君教授: 回望踏入Linux内核之旅

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 陈莉君 来源: 微信公众号linux阅码场(id: linuxdev) 初次踏入Linux 几多耕耘,几多收获 ...

  7. Flask:Flask的模板系统和静态文件

    1.Flask模板系统 Django框架有自己独立的模板系统,而Flask是没有的,Flask默认采用jinjia2模板系统,jinjia2是仿写Django模板系统的一个第三方模块,但性能上要比Dj ...

  8. 还不会用FindBugs?你的代码质量很可能令人堪忧

    前言 项目中代码质量,往往需要比较有经验的程序员的审查来保证.但是随着项目越来越大,代码审查会变得越来越复杂,需要耗费越来越多的人力.而且程序员的经验和精力都是有限的,能审查出问题必定有限.而在对代码 ...

  9. 学习笔记14Js使用技巧

    **页面加载完毕时执行一段代码$(fuction(){ initialMethod(); }); *将一个<form>序列化为json对象并传给服务器var postData = $(&q ...

  10. NOIP模拟测试7

    期望得分:60+60+60 实际得分:60+60+0 这次考试主要是T3搜索打挂了(我可是靠搜索吃饭的); 1.数组开小了,不过开大数组只拿到了10分的好成绩. 2.题意没审清(其实是他没说清). 以 ...