一夜一发设计模式:装饰者模式

这是我为了防止日更中断从我博客上拔下来的。

一,什么时候使用装饰者模式?

比如有一家店卖饮品,饮品就有不少种,每一种还可以加项,比如给可乐加冰,加糖,兑水什么的,每次加项的价格还不同,就会将代码弄的很繁琐,这种情况下就可以使用装饰者模式来实现.

何时使用

二,什么是装饰者模式?

上述的例子中,可以以饮品为主体,用不用的各种需求来装饰它,比如有一个可乐对象,那我用一个加冰对象装饰一下,再用加糖对象装饰一下,最后能得到一个加冰加糖可乐,这时候就将原可乐对象扩展,得到了加冰和加糖两种装饰.
装饰者模式: 动态地将责任附加到对象上,对扩展功能来说,装饰者比继承更有弹性更灵活(因为子类继承父类扩展功能的前提,是已知要扩展的功能是什么样的,而这是在编译时就要确定的,但是装饰者模式可以实现动态(在运行时)去扩展功能).

三,装饰者模式结构

装饰者模式结构


Decorator:是装饰者的父类,每个装饰者都需要继承这个抽象类(或实现这个接口).
ConcreteDecoratorA/B:具体的装饰者,就对应上述例子中的加冰,加糖等.
ConcreteComponent:具体的对象,就是上述例子中的可乐.
Component:装饰者模式中最顶级的父类,装饰者与被装饰者都是它的子类或实现类才行.

四,代码走起

代码目录结构:

目录结构

建立最顶级的父类

饮品类:所有的被装饰的类都需要继承它.

package componet;

/**
 * Created by zyf on 2017/3/30.
 * 装饰者模式中最顶级的父类
 */
public abstract class 饮品 {
    String name;

    /**
     * 每个饮品的价格不同,所以讲price方法抽象化<br/>
     * 让每个实现"饮品"类的子类自己决定是多少钱
     * */
    public abstract int price();

    /***
     * 得到饮品的名字
     * @return 名字
     */
    public String getName(){
        return name;
    }
}

被装饰的可乐Component类

package componet;

/**
 * Created by zyf on 2017/3/30.
 */
public class 可乐Component extends 饮品 {

    public 可乐Component() {
        //设置name为可乐
        //这个name属性是从饮品类中继承来的
        name = "可乐";
    }

    /***
     * 实现父类的抽象方法
     * @return 可乐的价格
     */
    @Override
    public int price() {
        //可乐30块一瓶~
        return 30;
    }
}

被装饰的啤酒Component类

package componet;

/**
 * Created by zyf on 2017/3/30.
 */
public class 啤酒Component  extends 饮品{
    public 啤酒Component() {
        //设置name为啤酒
        //这个name属性是从饮品类中继承来的
        name = "啤酒";
    }

    /***
     * 实现父类的抽象方法
     * @return 啤酒的价格
     */
    @Override
    public int price() {
        //啤酒3块一瓶~
        return 3;
    }
}

Decorator类,所有装饰类的父类

package decorator;

import componet.饮品;

/**
 * Created by zyf on 2017/3/30.
 * 装饰者模式中,所有装饰者的父类
 */
public abstract class Decorator extends 饮品 {

    /***
     * 声明一个饮品引用,准备接受一个饮品对象<br/>
     */
    protected 饮品 yp;

    public Decorator(饮品 yp) {
        this.yp = yp;
    }

}

装饰类:加醋Decorator类

package decorator;

import componet.饮品;

/**
 * Created by zyf on 2017/3/30.
 */
public class 加醋Decorator extends Decorator {


    public 加醋Decorator(饮品 yp) {
        super(yp);
    }

    public void addVinegar(){
        System.out.println("还要加醋,加完了");
    }

    /***
     * 那么加醋后的价格应该是多少呢?<br/>
     * 应该是加粗的价格加饮品的价格
     * @return 加醋五块
     */
    @Override
    public int price() {
        return 5 + yp.price();
    }

    /***
     * 再复写一个名字的方法<br/>
     * 现在已经不是单纯的饮品了
     * @return
     */
    @Override
    public String getName() {
        //在这里加个醋
        addVinegar();
        return "加醋的" + yp.getName();
    }
}

装饰类:兑水Decorator类

package decorator;

import componet.饮品;

/**
 * Created by zyf on 2017/3/30.
 */
public class 兑水Decorator extends Decorator {


    public 兑水Decorator(饮品 yp) {
        super(yp);
    }

    public void 兑水(){
        System.out.println("饮料兑水....尴尬不老铁...");
    }

    /***
     * 那么兑水后的价格应该是多少呢?<br/>
     * 应该是兑水的价格加饮品的价格
     * @return 兑水2块
     */
    @Override
    public int price() {
        return 2 + yp.price();
    }

    /***
     * 再复写一个名字的方法<br/>
     * 现在已经不是单纯的饮品了
     * @return
     */
    @Override
    public String getName() {
        兑水();
        return "兑水了的" + yp.getName();
    }
}

五,测试类

public static void main(String[] args) {
  //可以看到,我们操作的引用一直是这个yp
  //但是这个引用指向的对象已经换了好几次了
  //这就是为什么装饰类也要是饮品类的子类,因为只有这样,装饰类与被装饰类才能被当做同一个类型使用(通过接口或继承实现)
    饮品 yp = new 可乐Component();
    yp = new 兑水Decorator(yp);
    yp = new 加醋Decorator(yp);
//  上面与下面这一行是一样的,是不是和IO流很像?
//  yp = new 加醋Decorator(new 兑水Decorator(new 可乐Component()));

    System.out.println("饮品名:" + yp.getName() + "---价格:" + yp.price());
}


测试输出

六,自定义IO流演示

Java中IO流就是装饰者模式的典型案例

io示例


那么我们可以自己写一个装饰类演示一下,完成一个将读取到的英文字符全切换成大写的转换流.

package main;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Created by zyf on 2017/3/29.
 */
public class ToUpperCaseInputStream extends FilterInputStream {

    InputStream inputStream;

    /**
     * Creates a <code>FilterInputStream</code>
     * by assigning the  argument <code>in</code>
     * to the field <code>this.in</code> so as
     * to remember it for later use.
     *
     * @param in the underlying input stream, or <code>null</code> if
     *           this instance is to be created without an underlying stream.
     */
    protected ToUpperCaseInputStream(InputStream in) {
        super(in);
        this.inputStream = in;
    }

    /**
     * 读取单个字节
     * @return
     * @throws IOException
     */
    @Override
    public int read() throws IOException {
        //获取父类读取的结果
        int result = super.read();
        //如果读取到字符a,就抛出异常
        if(result == 'a'){
            throw new ToUpperException();
        }
        //如果等于-1,说明无内容
        //否则,将字节转成char,再将char转换成大写的后返回
        //返回值类型是int类型,这里返回一个字符会被自动转型
        return (result == -1 ? result : Character.toUpperCase(Character.toChars(result)[0]));
    }

    /**
     * 这个方法是给定b字节数组,从off,读取len长度的字节<br/>
     * 所以下面的for循环i的初始值设置为off,也就是从off开始变化大写
     * @param b
     * @param off
     * @param len
     * @return
     * @throws IOException
     */
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = super.read(b,off,len);
        for (int i = off; i < off + result; i++) {
            //将字节转成大写字符后再转成字节
            b[i] = (byte) Character.toUpperCase((char)b[I]);
        }
        return result;
    }

    /***
     * 这里是一个内部类,自定义异常
     */
    class ToUpperException extends IOException {
        @Override
        public void printStackTrace() {
            System.out.println("不好意思我遇到异常了,向上转型失败啦");
        }
    }
}


test.txt


内容

private static void toUpper() {
    int result = 0;
    InputStream inputStream = null;
    try {
        inputStream = new ToUpperCaseInputStream(new BufferedInputStream(new FileInputStream("test.txt")));

        while ((result = inputStream.read()) >= 0) {
            System.out.print((char) result);
        }

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


异常信息

七,总结

  • 装饰者模式可以非常的贴合六大原则之一的开闭原则.
  • 继承虽然也可以实现扩展,但是继承的扩展需要已经扩展需求,在编译期扩展,而装饰者模式可以实现在运行期扩展.
  • 装饰者可以在被装饰者的行为上扩展行为,也可以完全替代被装饰者的行为.
  • 一般情况下的引用使用的都是父类的引用,如果使用了装饰者的引用,那么也就使用该装饰者独有的方法了.
  • 使用装饰者模式,类的数量会增长很多,不能过度使用,主要是看别人代码的时候如果遇到装饰者模式而自己不懂的话,看懵逼是很正常的.
  • 都是站在巨人的肩膀上,一起加油吧.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容