装饰模式
装饰模式的动机(Motivation)
- 在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
- 如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降为最低?
装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。UML结构图如下:
其中,Component是抽象构件,定义一个对象接口,可以给这些对象动态地添加职责;ConreteComponent定义一个具体对象,也可以给这个对象添加一些职责;Decorator是装饰抽象类,实现接口或抽象方法;ConreteDecorator是具体装饰对象,起到给Component添加职责的功能。
下面是代码的基本实现
Component抽象类
Component是一个接口或是抽象类,就是定义我们最核心的对象,也就是最原始的对象。
package 装饰模式;
public abstract class Component {
public abstract void show();
}
ConretetComponent类
具体构件,通过继承实现Component抽象类中的抽象方法。是最核心、最原始、最基本的接口或抽象类的实现,我们要装饰的就是它。
public class ConcreteComponent extends Component {
@Override
public void operation() {
System.out.println("具体对象的操作");
}
}
Decorator装饰类
一般是一个抽象类,在其属性里必然有一个private变量指向Component抽象构件。
public abstract class Decorator extends Component {//注意这里,装饰类也继承Component抽象类
private Component component = null;//组合Component抽象类
//通过构造函数传递给被修饰者
public Decorator(Component component) {
this.component = component;
}
//委托给被修饰者执行
@Override
public void operation() {//重写继承来的Operation()
//实际执行的是成员component的Operation()
if(component != null) {
this.component.operation();
}
}
}
ConcreteDecorator类
- 我们可以写多个具体实现类,把最核心的、最原始的、最基本的东西装饰成其它东西。
- 这里就写两个类,稍改一下二者的实现顺序,看看结果。
- A类,它的operation()方法先执行了method1()方法,再执行了Decorator的operation()方法。
public class ConcreteDecoratorA extends Decorator {
//定义被修饰者
public ConcreteDecoratorA(Component component) {
super(component);
}
//定义自己的修饰方法
private void method1() {//本类的独有的功能,以区别于ConcreteDecoratorB
System.out.println("method1 修饰");
}
@Override
public void operation() {
this.method1();//首先运行原Decorator的Operation(),再执行本类的功能
super.operation();
}
}
B类,它的operation()方法先执行了Decorator的operation()方法,再执行了method2()方法。
public class ConcreteDecoratorB extends Decorator {
//定义被修饰者
public ConcreteDecoratorB(Component component) {
super(component);
}
//定义自己的修饰方法
private void method2() {
System.out.println("method2 修饰");
}
@Override
public void operation() {
super.operation();
this.method2();
}
}
Client客户端
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
//第一次修饰
component = new ConcreteDecoratorA(component);
//第二次修饰
component = new ConcreteDecoratorB(component);
//修饰后运行
component.operation();
}
}
小结
- 装饰模式常常被称为包裹模式,就是因为每一个具体装饰类都将下一个具体装饰类或者具体构件类包裹起来。
- 这样每个装饰对象的实现和如何使用这个对象分离开了。每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链中。
要点总结
- 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
- Decorator类在接口上表现为is-aComponent的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a
Component的组合关系,即Decorator类又使用了Component类。 - Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
本质
动态组合
动态是手段,组合是目的。
装饰模式实例
服饰搭配问题:写一个可以给人搭配不同服饰的程序
- 怎么搭配?
- 可以是嘻哈服, 可以是西装, 也可以是运动装. 就像qq或者 一些服饰搭配软件那样, 给人进行不同的服饰搭配. 展现不同风格。
- 分析:
- 搭配的对象是人, 人就是主体对象
- 搭配的服饰: 有衣服,裤子, 领带, 帽子, 鞋, 大衣等等,这些 衣服可以随意搭配,不同的服饰搭配出来不同的风格。
Component类
abstract class Component {
public abstract void Show(); //相当于Operation
}
Person类(ConcreteComponment)
public class Person extends Component{
public Person(){}
private String name;
public Person(String name) {
this.name = name;
}
public void show() {
System.out.println("装扮的"+name);
}
}
服饰类Decorator
package 装饰模式;
public class Finery extends Component {//继承Component类
protected Component component;//组合Component类
public void Decorate(Component component){//相当于SetComponent
this.component=component;
}
@Override
public void show() {
if(component != null){
component.show();
}
}
}
具体服饰类 (ConcreteDecorator类
public class Tshirts extends Finery{
public void show(){
//后装扮的先显示,如果划线语句调换一下,就变成先装扮的先显示
System.out.print(" 大T恤");
super.show();
}
}
package 装饰模式;
public class Sneakers extends Finery{
public void show(){
System.out.print(" 球鞋");
super.show();
}
}
package 装饰模式;
public class Suit extends Finery{
public void show(){
System.out.print(" 西装");
super.show();
}
}
package 装饰模式;
public class Tie extends Finery{
public void show(){
System.out.print(" 领带");
super.show();
}
}
客户端实现
package 装饰模式;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Person zyz=new Person("小政");
//第一种装扮
System.out.println("第一种装扮:");
Sneakers qx=new Sneakers();
BigTrouser kk=new BigTrouser();
Tshirts dtx=new Tshirts();
qx.Decorate(zyz);
kk.Decorate(qx);
dtx.Decorate(kk);
dtx.show();
//第二种装扮
System.out.println("第二种装扮:");
LeatherShoes px=new LeatherShoes();
Tie ld=new Tie();
Suit xz=new Suit();
px.Decorate(zyz);
ld.Decorate(px);
xz.Decorate(ld);
xz.show();
}
}
结果:
使用装饰模式主要有以下的优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能, 但是装饰模式可以提供比继承更多的灵活性。
- 通过使用不同的具体装饰类以及这些装饰类的排列组 合,设计师可以创造出很多不同行为的组合。
装饰模式缺点:
- 会产生很多细粒度对象
装饰模式与策略模式
- 策略模式改变的是对象的内核
- 装饰模式改变的是对象的外壳
- 策略模式是一层调用
- 装饰模式是递归调用
- 可以有机结合
以上部分摘取自朱红梅老师2020年5月的课件