设计模式之装饰者模式
- 装饰者模式定义
- 意图,解决的问题及优缺点
- 装饰者模式使用场景以及注意事项
- 装饰者模式案例一
- 装饰者模式案例二
- JavaIO装饰者模式扩展案例
装饰者模式定义
- 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。- 装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
看类图结构
意图,解决的问题及优缺点
- 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
- 一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
- 目的就是要 在不想增加很多子类的情况下扩展类。
- 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
- 缺点:多层装饰比较复杂。
装饰者模式案例一
要实现一个咖啡的点单系统,点单的时候,可以只点单品(有很多中),也可以加各种各样的调料,同时要能迅速的得到总的订单的描述,以及总的价格。
方案一
直接使用继承,这样的不好之处在于,如果品种不断的增加,各个品种和调料之间的组合,会产生类的爆炸。
方案二
第二种方案是使用一些bool型变量表示这个品种有没有调料,存在的问题是增加调料的时候,要修改类,已经添加多分调料的问题。
方案三
- 这种方案就是使用装饰者模式来设计;
- 首先组件(Component(也就是被装饰者包着的))就是各种各样的咖啡,他们都继承自Coffe类(也可以不继承,直接继承Drink类),这里只是一个中间类;
- 然后就是各种装饰者(也就是各种调料),都继承Decorator;
- 最后就是Coffee类和Decorator都继承Drink类;
在这种情况下某份订单是这样组成的
具体代码实现:
首先看Drink类
package decorator.custom.drink;
/**
* Component的超类
*
* 单品和装饰者都要继承自这个类
*/
public abstract class Drink {
private String description = ""; //一开始没有描述
private double price = 0; //一开始价格为0
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;
}
/**
* 抽象方法
* 如果是单品的话就直接是自己的价格
* 如果是装饰者的话就还要加上装饰品自己的价格
* @return
*/
public abstract double cost();
}
然后是各种咖啡
先看咖啡的父类
package decorator.custom.component;
import decorator.custom.drink.Drink;
/**
* 这个是所有咖啡的父类 (相当于和Drink类有一个过渡的类)
* Component
*/
public class Coffee extends Drink {
@Override
public double cost() {
return getPrice();//super.getPrice()//这个就是父类的价格(自己什么也没加)
}
@Override
public String getDescription() {
return super.getDescription() + "-" + cost();
}
}
然后看具体的各个品种
package decorator.custom.component;
public class Decaf extends Coffee {
public Decaf() {
super.setDescription("Decaf");
super.setPrice(3); //3块钱
}
}
package decorator.custom.component;
public class Espresso extends Coffee {
public Espresso(){
super.setDescription("Espresso");
super.setPrice(4);
}
}
package decorator.custom.component;
public class LongBlack extends Coffee {
public LongBlack() {
super.setDescription("LongBlack");
super.setPrice(5);
}
}
package decorator.custom.component;
public class ShortBlack extends Coffee {
public ShortBlack() {
super.setDescription("ShortBlack");
super.setPrice(6);
}
}
然后再看装饰者包
先有一个装饰者的父类
package decorator.custom.decorator;
import decorator.custom.drink.Drink;
public class Decorator extends Drink{
/**
*这个引用很重要,可以是单品,也可以是被包装过的类型,所以使用的是超类的对象
* 这个就是要被包装的单品(被装饰的对象)
*/
private Drink drink; //这里要拿到父类的引用,因为要控制另一个分支
public Decorator(Drink drink) {
this.drink = drink;
}
/**
* 如果drink是已经被装包过的,那么就会产生递归调用 最终到单品
* @return
*/
@Override
public double cost() {
return super.getPrice() + drink.cost(); //自己的价格和被包装单品的价格
}
@Override
public String getDescription() {
return super.getDescription() + "-" + super.getPrice()
+ "&&" + drink.getDescription();
}
}
然后就是各种装饰者
package decorator.custom.decorator;
import decorator.custom.drink.Drink;
/**
* 这个是具体的装饰者() --> 继承自中间的装饰着Decorator
*/
public class Chocolate extends Decorator{
public Chocolate(Drink drink) { //如果父类搞了一个 带参数的构造函数,子类必须显示的使用super调用
super(drink);
super.setDescription("Chocolate");
super.setPrice(1);
}
}
package decorator.custom.decorator;
import decorator.custom.drink.Drink;
public class Milk extends Decorator{
public Milk(Drink drink) {
super(drink); //调用父类的构造函数
super.setDescription("Milk");
super.setPrice(3);
}
}
package decorator.custom.decorator;
import decorator.custom.drink.Drink;
/**
* 具体的装饰者二
* 大豆奶饮品
*/
public class Soy extends Decorator {
public Soy(Drink drink) {
super(drink);
super.setDescription("Soy");
super.setPrice(2);
}
}
总的测试类:
package decorator.custom.test;
import decorator.custom.component.Decaf;
import decorator.custom.component.LongBlack;
import decorator.custom.decorator.Chocolate;
import decorator.custom.decorator.Milk;
import decorator.custom.drink.Drink;
public class MyTest {
public static void main(String[] args) {
//只点一个单品
Drink order = new Decaf();
System.out.println("order description : " + order.getDescription());
System.out.println("order price : " + order.cost());
System.out.println("---------------加了调料的----------------");
order = new LongBlack();
order = new Milk(order);
order = new Chocolate(order);
order = new Chocolate(order);
System.out.println("order description : " + order.getDescription());
System.out.println("order price : " + order.cost());
}
}
运行结果
注意上面的cost和description在装饰者的一层一层的包裹下计算的时候有递归的调用。
装饰者模式案例二
再来看一个更加简单的例子,模拟的是游戏中英雄学习技能;比如某个英雄盲僧可以学习各种技能,和各种技能搭配(装饰盲僧)
Hero接口(总的Compoent)
/**
* 英雄接口
*/
public interface Hero {
void learnSkills();
}
具体的英雄(被修饰的)
/**
* 具体的一个英雄
*/
public class BlindMonk implements Hero{
private String name;
public BlindMonk(String name) {
this.name = name;
}
@Override
public void learnSkills() {
System.out.println(name + "学习了以上技能!");
}
}
装饰者
/**
* 技能栏
* 这个就是装饰者,装饰某个英雄的
*/
public class Skills implements Hero{
//持有一个英雄对象接口 注意这里一定要有Hero接口的对象
private Hero hero;
public Skills(Hero hero) {
this.hero = hero;
}
@Override
public void learnSkills() {
if(hero != null) hero.learnSkills();
}
}
具体的两个装置者:
/**
* 装饰者1
*/
public class Skill_Q extends Skills {
private String skillName;
public Skill_Q(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println("学习了技能Q:" +skillName);
super.learnSkills();
}
}
/**
* 装饰者2
*/
public class Skill_W extends Skills{
private String skillName;
public Skill_W(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println("学习了技能W:" + skillName);
super.learnSkills();
}
}
测试
public class MyTest {
public static void main(String[] args) {
Hero mang = new BlindMonk("盲僧");
mang = new Skill_Q(mang,"神龙摆尾");
mang = new Skill_W(mang,"猛虎下山");
mang.learnSkills();
}
}
输出:
学习了技能W:猛虎下山
学习了技能Q:神龙摆尾
盲僧学习了以上技能!
JavaIO装饰者模式扩展案例
- Java中的IO流设计就是使用的装饰者模式;
- 我们可以自己扩展,设计自己的输入流;
如下面的例子,使用输入流将读入的字符转化成大写,我们重新装饰流,这里的FilterInputStream就是我们的Decorator;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 自己定义的输入流
* 扩展FilterInputStream(这个类就是我们的Decorator) 中间装饰者
* 所以我们只要继承这个就可以扩展自己的输入流装饰者
*/
public class UpperCaseInputStream extends FilterInputStream{
protected UpperCaseInputStream(InputStream in) { //这个InputStream就是我们的Drink 类(超类)
super(in);
}
//重写 相当于cost和description
@Override
public int read() throws IOException {
int index = super.read(); //读取一个字节
return index == -1 ? index : Character.toUpperCase((char)(index)); //小写转换成大写
}
//字节数组
@Override
public int read(byte[] b, int off, int len) throws IOException {
int index = super.read(b, off, len);
for(int i = 0; i < index; i++){
b[i] = (byte)Character.toUpperCase((char)(b[i]));
}
return index;
}
}
测试
import java.io.*;
public class MyTest {
public static void main(String[] args) throws IOException {
InputStream in = new UpperCaseInputStream(new BufferedInputStream(new FileInputStream("/home/zxzxin/Java_Maven/DesignPatterns/src/main/java/decorator/java/in.txt")));
int len;
while((len = in.read()) >= 0){
System.out.print((char)(len));
}
in.close();
}
}
效果