JAVA设计模式之装饰者模式
一,什么时候使用装饰者模式?
比如有一家店卖饮品,饮品就有不少种,每一种还可以加项,比如给可乐加冰,加糖,兑水什么的,每次加项的价格还不同,就会将代码弄的很繁琐,这种情况下就可以使用装饰者模式来实现.
二,什么是装饰者模式?
上述的例子中,可以以饮品为主体,用不用的各种需求来装饰它,比如有一个可乐对象,那我用一个加冰对象装饰一下,再用加糖对象装饰一下,最后能得到一个加冰加糖可乐,这时候就将原可乐对象扩展,得到了加冰和加糖两种装饰。
装饰者模式: 动态地将责任附加到对象上,对扩展功能来说,装饰者比继承更有弹性更灵活(因为子类继承父类扩展功能的前提,是已知要扩展的功能是什么样的,而这是在编译时就要确定的,但是装饰者模式可以实现动态(在运行时)去扩展功能).
三,装饰者模式结构
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()); }
还要加醋,加完了
饮料兑水....尴尬不老铁...
饮品名:加醋的兑水了的可乐---价格:37
六,自定义IO流演示
Java中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("不好意思我遇到异常了,向上转型失败啦"); } } }
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(); } } }
七,总结
- 装饰者模式可以非常的贴合六大原则之一的开闭原则。
-
继承虽然也可以实现扩展,但是继承的扩展需要已经扩展需求,在编译期扩展,而装饰者模式可以实现在运行期扩展。
- 装饰者可以在被装饰者的行为上扩展行为,也可以完全替代被装饰者的行为。
- 一般情况下的引用使用的都是父类的引用,如果使用了装饰者的引用,那么也就使用该装饰者独有的方法了。
- 使用装饰者模式,类的数量会增长很多,不能过度使用,主要是看别人代码的时候如果遇到装饰者模式而自己不懂的话,看懵逼是很正常的。
- 都是站在巨人的肩膀上,一起加油吧。
转载自:https://blog.csdn.net/android_zyf/article/details/68343953