装饰者模式

1、现有咖啡馆订单系统项目(示例项目)

咖啡种类:Espresso、ShortBlack、LongBlack、Decaf

调料:Milk、Soy、Chocolate

实现客户点单时,点个Decaf+milk+Soy可以立刻知道它的价格和描述,点个Decaf需要知道它的价格和描述

2、以传统的面向对象的原则设计这个项目。

1)定义一个超类

public abstract class Drink{

    protected Stringdescription ="";

    protected float price;

    protected abstract StringgetDescription();

    protected abstract float cost();

}

2)把所有的能单点的饮料都封装好描述和价格,继承Drink

public class Coffee extends Drink {

    public Coffee(){

        this.price =5.0f;

        this.description ="coffee"+"-"+this.price;

    }

    @Override

    protected String getDescription() {

        return this.description;

    }

    @Override

    protected float cost() {

        return this.price;

    }

}

3)如果要多点的则把单点的饮料作为引用放在此类中

public class CoffeeAndMilkextends Drink {

    private Coffeecoffee;

    private Milkmilk;

    public CoffeeAndMilk(Coffee coffee,Milk milk){

        this.coffee = coffee;

        this.milk = milk;

    }

@Override

    protected StringgetDescription() {

        return this.coffee.getDescription()+"&&"+this.milk.getDescription();

    }

@Override

    protected float cost() {

        return this.milk.cost()+this.coffee.cost();

    }

}

4)运行结果

Coffee coffee =new Coffee();

System.out.println("描述:"+coffee.getDescription());

System.out.println("价格:"+coffee.cost());

System.out.println("********************************************************");

Milk milk =new Milk();

System.out.println("描述:"+milk.getDescription());

System.out.println("价格:"+milk.cost());

System.out.println("********************************************************");

CoffeeAndMilk coffeeAndMilk =new CoffeeAndMilk(coffee,milk);

System.out.println("描述:"+coffeeAndMilk.getDescription());

System.out.println("价格:"+coffeeAndMilk.cost());

结果:

描述:coffee-5.0

价格:5.0

********************************************************

描述:milk-3.0

价格:3.0

********************************************************

描述:coffee-5.0&&milk-3.0

价格:8.0

问题:这样设计的话,增删饮料种类就很有问题,添加新的混合饮料的话非常麻烦,需要改动两个地方,新增单品饮料,新增复合饮料。搭配品种更不方便,假如我现在coffee不和牛奶混合,coffee和啤酒混合,就必须新建个复合饮料coffeeAndBeer类,扩展性不好,维护起来麻烦。

3、接下来我采用装饰者模式的方式改造代码

   1)封装一个抽象接口,在装饰者模式中的角色是抽象组件角色。

    public abstract class Drink{

        public Stringdescription ="";

        private float price =0f;

        public void setDescription(String description) {

            this.description = description;

        }

        public void setPrice(float price) {

            this.price = price;

        }

        public StringgetDescription() {

            return description+"-"+this.getPrice();

        }

       public float getPrice() {

            return price;

        }

        public abstract float cost();

}

    2)为这个抽象接口提供具体实现类,这里我还封装了一个中间层,这个抽象接口的具体实现应该是这个中间层的具体实现

Coffee中间层代码:

    public class Coffeeextends Drink {

        @Override

        public float cost() {

            return super.getPrice();

        }

    }

具体实现Decaf代码:

public class Decafextends Coffee {

    public Decaf(){

        super.setDescription("Decaf");

        super.setPrice(3.0f);

    }

}

3)创建抽象装饰接口Decorator,包含抽象接口Drink的引用

public class Decorator extends Drink {

private Drinkobj;

    public Decorator(Drink obj){

        this.obj = obj;

    }

    @Override

    public float cost() {

        return super.getPrice()+obj.cost();

    }

    @Override

    public StringgetDescription() {

        return super.description+"-"+super.getPrice()+"&&"+obj.getDescription();

    }

}

4)创建具体装饰接口的实现类Chocolate、Milk,负责具体的装饰

Chocolate代码:

public class Chocolate extends Decorator {

    public Chocolate(Drink obj) {

        super(obj);

        super.setDescription("Chocolate");

        super.setPrice(3.0f);

    }

}

Milk代码:

public class Milkextends Decorator {

public Milk(Drink obj) {

super(obj);

        super.setDescription("Milk");

        super.setPrice(2.0f);

    }

}

5)运行。

若你需要单品咖啡,你可以直接new

Drink order =null;

order =new Decaf();

System.out.println("order one price:"+order.cost());

System.out.println("order one desc:"+order.getDescription());

运行结果:

order one price:3.0

order one desc:Decaf-3.0

若你需要巧克力+牛奶+decaf,你可以现new一个单品(具体组件角色),然后利用装饰接口实现类进行装饰

Drink order =null;

order =new Decaf();

order =new Chocolate(order);

order =new Milk(order);

System.out.println("order two price:"+order.cost());

System.out.println("order two desc:"+order.getDescription());

运行结果:

order two price:8.0

order two desc:Milk-2.0&&Chocolate-3.0&&Decaf-3.0

以后需要怎样搭配就用装饰接口的实现类装饰具体饮料就行,这就解决了前面传统面向对象方式出现的问题,每新出一种混合饮料,都要新建一个混合饮料类的问题。

4、装饰模式概念

1)装饰者模式

    动态地将功能附加到对象上,在对象功能扩展方面,它比继承更有弹性。装饰者模式又叫做包装模式。通过一种对客户端透明的方式来扩展对象的功能,是继承关系的一种替代方案。

2)装饰者模式的结构

3)装饰者模式的角色和职责

    1、抽象组件角色:一个抽象接口,是被装饰类和被装饰类继承的父接口(本例中的抽象组件角色是Drink)

    2、具体组件角色:为抽象组件的实现类(本例中的具体组件角色是Coffee)

    3、抽象装饰角色:包含一个组件的引用,并定义了与抽象组件一致的接口(本例中的抽象装饰角色是Decorator)

    4、具体装饰角色:为抽象装饰角色的实现类。负责具体的装饰。(本例中的具体装饰角色是Milk和Chocolate)

5、jdk中的装饰者模式

    我们来看下java中的I/O类

    

接下来我们分析下,这些类充当的装饰者模式中的角色与职责

1)InputStream:这无疑是抽象组件角色,顶级接口

2)FileInputStream/StringBufferInputStream/ByteArrayInputStream:这些类是具体组件角色,是被装饰者角色

3)FilterInputStream:这个类是抽象装饰角色,是装饰者角色

4)BufferInputStream/DataInputStream/LineNumberInputStream:这些类是具体装饰角色,负责具体的装饰。

接着我们自己实现一个具体的装饰角色,自己编写一个自己的I/O装饰者,这个装饰者的功能是读取文件的字符全部大写

1)继承装饰者角色FilterInputStream

2)重写int read()方法和int read(byte[] b, int offset, int len)方法

public class UpperCaseInputStreamextends FilterInputStream {

public UpperCaseInputStream(InputStream in) {

super(in);

    }

@Override

    public int read()throws IOException {

int c =super.read();

        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

b[i]=(byte)Character.toUpperCase((char)(b[i]));

        }

return result;

    }

}

3)运行

public static void main(String[] args) {

int c =0;

    InputStream inputStream =null;

    try {

inputStream =new UpperCaseInputStream(new BufferedInputStream(new FileInputStream("D:\\test.txt")));

        while((c=inputStream.read())>=0) {

System.out.print((char)c);

        }

}catch (IOException e) {

e.printStackTrace();

    }finally {

try {

inputStream.close();

        }catch (IOException e) {

e.printStackTrace();

        }

}

}

运行结果

test.txt文件中的内容

abcdefghjzklmen

运行结果:

ABCDEFGHJZKLMEN

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 本章可以称为“给爱用继承的人一个全新的设计眼界” ,我们即将再度探讨典型的继承滥用问题。你将在本章学到如何使用对象...
    黑夜0411阅读 376评论 0 0
  • 1:什么是装饰者设计模式?百度百科:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。...
    pkingdog阅读 477评论 0 1
  • 引言 在介绍装饰者模式之前,我们先了解一个设计原则: 多用组合,少用继承。 在平时写代码时,我们应该减少类继承的使...
    Zentopia阅读 4,092评论 4 11
  • 为了防止被“杀”了祭天,学点设计模式,并总结下还是有必要的。 一:理解 顾名思义,装饰者模式可以在不修改基础类的前...
    阿菜的博客阅读 591评论 0 2
  • C++将函数名作为函数参数传递:函数指针,将函数名传递进去 树的先序、中序和后序遍历方式
    shandyone阅读 175评论 0 0