装饰者模式
装饰者模式可以实现动态(在编译期是固定的,但是在运行期是随着Java程序运行的不同而变化的)地为一个对象增加新的功能;是使用组合或者聚合的形式代替继承的方式进行扩展新的功能(即扩展新的功能无需通过继承的方式增加子类)这样就使整个功能模块更加灵活,很好的避免了新增功能使得类快速增多从而使整个软件类复杂度极高的情况。
进一步阐述:装饰者模式就像一个牙膏的生产过程,有主体和外包装,主体可以单独存在并成为一个产品,主体也可以加上外包装使之成为一个新的主体也是一个产品。
装饰者模式包含四种角色:
- Component(抽象的主体角色): 是一个具体的主体对象和装饰者对象都要实现的接口或者继承的抽象类。这样,客户端就能够像调用具体的主体对象相同的方式去调用装饰者对象完成功能。
- ConcreteComponent(具体的主体即被装饰者角色):就是一个用于被装饰的实体,也可以单独调用。
- Decorator(抽象装饰者角色):一般为一个抽象类,也可以是实体类,它持有一个抽象主体的引用,并接收所有客户端的请求,并把这些请求转发给具体的被装饰者对象去处理,这样就能在具体被装饰者调用前后增加新的功能。
- ConcreteDecorator(具体装饰者角色):负责给被装饰者扩展新的功能。
案例:
以吃一碗面为例,用户可以根据自己的喜好选择不同类型面的搭配,这就是装饰者模式最好的体现。以下案例中Noodles面条类就是抽象的主题角色;BeefNoodles牛肉面类和HotDryNoodles热干面条类就是具体的被装饰者角色;Decorator类就是抽象装饰者角色;Egg鸡蛋类和HamSausage火腿肠类就是具体的装饰者角色。
UML类图:
客户端Client类:
java
/**
* 使用装饰者模式
*/
public class Client {
public static void main(String[] args) {
//吃牛肉鸡蛋面
Noodles noodles1 = new BeefNoodles();
noodles1 = new Egg(noodles1);
//点一份牛肉鸡蛋火腿肠面
noodles1 = new HamSausage(noodles1);
System.out.println("吃" + noodles1.getDesc() + "的价格是:" + noodles1.cost() + "元");
//吃普通热干面
Noodles noodles2 = new HotDryNoodles();
System.out.println("吃" + noodles2.getDesc() + "的价格是:" + noodles2.cost() + "元");
}
}
面条Noodles抽象类:
java
/**
* 面条抽象类(抽象的主体被装饰者)
*/
public abstract class Noodles {
protected String desc;
public String getDesc() {
return desc;
}
/**
* 计算面条价格方法(根据不同的配料计价所以是抽象方法)
*/
public abstract double cost();
}
牛肉面条BeefNoodles类:
java
/**
* 牛肉面条类(具体的主体被装饰者)
*/
public class BeefNoodles extends Noodles {
public BeefNoodles() {
this.desc = "牛肉面";
}
public String getDesc() {
return desc;
}
/**
* 计算面条价格方法(根据不同的配料计价所以是抽象方法)
*/
@Override
public double cost() {
//没有过多的if-- else if的判断
return 20.0;
}
}
热干面HotDryNoodles类:
java
/**
* 热干面条类(具体的主体被装饰者)
*/
public class HotDryNoodles extends Noodles {
public HotDryNoodles() {
this.desc = "热干面";
}
public String getDesc() {
return desc;
}
/**
* 计算面条价格方法(根据不同的配料计价所以是抽象方法)
*/
@Override
public double cost() {
return 15.0;
}
}
抽象装饰者Decorator类:
java
/**
* 抽象装饰者类(抽象装饰者)
*/
public abstract class Decorator extends Noodles {
/*
* 持有一个抽象被装饰者的引用
*/
protected Noodles noodles;
public Decorator(Noodles noodles) {
this.noodles = noodles;
this.desc = noodles.getDesc();
}
/**
* 计算面条价格方法(根据不同的配料计价所以是抽象方法)
*/
public abstract double cost();
}
鸡蛋Egg类:
java
/**
* 鸡蛋类(具体装饰者类)
*/
public class Egg extends Decorator {
public Egg(Noodles noodles) {
super(noodles);
this.desc += "鸡蛋";
}
public String getDesc() {
return desc;
}
/**
* 计算价格方法
*/
public double cost() {
return noodles.cost() + 2.0;
}
}
火腿肠HamSausage类:
java
/**
* 火腿肠类(具体装饰者类)
*/
public class HamSausage extends Decorator {
public HamSausage(Noodles noodles) {
super(noodles);
this.desc += "火腿肠";
}
public String getDesc() {
return desc;
}
/**
* 计算价格方法
*/
public double cost() {
return noodles.cost() + 1.5;
}
}
总结:
- 装饰者模式(Decorator)也叫包装器模式(Wrapper)它降低系统的耦合度,拥有可以动态的增加或删除对象的职责,并使得具体被装饰者类和具体装饰者类可以独立变化,以便扩展新的具体被装饰者类和具体装饰者类。
- 扩展对象功能,比继承灵活,不会导致类个数因为扩展而暴增的情况降低了类的复杂度。
- 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的可使用对象,具体被装饰者类和具体装饰者类可以独立变化,用户可以根据需要自己增加新的具体被装饰者类和具体装饰者类,这样就具有了很好的可扩展性和可靠性,遵守了开闭原则(OCP原则)。
- 由于在扩展时是使用关联去实现扩展,所以遵守了合成复用原则。
- 装饰者模式和适配器模式的区别: 装饰者与适配器都有一个别名叫做包装模式(Wrapper),它们看似都是起到包装一个类或对象的作用,但是使用它们的目的很不一样。适配器模式的意义是要将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的。而装饰者模式不是要改变被装饰者对象的接口,而是恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式而提升性能。所以这两个模式设计的目的是不同的。
- 关于新职责:适配器也可以在转换时增加新的职责,但主要目的不在此。装饰者模式主要是给被装饰者增加新职责的。
- 关于原接口:适配器模式是用新接口来调用原接口,原接口对新系统是不可见或者说不可用的。装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。(增加新接口的装饰者模式可以认为是其变种--"半透明"装饰者)。
- 关于其包裹的对象:适配器是知道被适配者的详细情况的(就是哪个类或哪个接口)。装饰者只知道其接口是什么,至于其具体类型(是基类还是其他派生类)只有在运行期间才知道。