1.定义

动态的给一个对象添加一个额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

2.解决问题

——奖金计算

不用模式的解决方案

public class Prize {
    public double calcPrize(String user, Date begin, Date end) {
        double prize = 0.0;
        // 计算当月业务奖金,所有人都会计算
        prize = this.monthPrize(user, begin, end);
        // 计算累计奖金
        prize += this.calcPrize(user, begin, end);
        // 需要判断该人员是普通人员还是业务经理,团队奖金只有业务经理才有
        if(this.isManager(user)) {
            prize += this.groupPrize(user, begin, end);
        }
        return prize;
    }
    private double groupPrize(String user, Date begin, Date end) {
        return 0;
    }
    private boolean isManager(String user) {
        return false;
    }
    private double monthPrize(String user, Date begin, Date end) {
        return 0;
    }
}

使用装饰模式来解决问题

public abstract class Component {
    public abstract double calcPrize(String user, Date begin, Date end);
}
public abstract class Decorator extends Component {
    protected Component c;
    public Decorator(Component c) {
        super();
        this.c = c;
    }
    public double calcPrize(String user, Date begin, Date end) {
        // 转调组件对象的方法
        return c.calcPrize(user, begin, end);
    }
}
public class MonthPrizeDecorator extends Decorator {
    public MonthPrizeDecorator(Component c) {
        super(c);
    }
    public double calcPrize(String user, Date begin, Date end) {
        // 先获取前面运算出来的奖金
        double money = super.calcPrize(user, begin, end);
        // 然后计算当月业务奖金
        double prize = 0.0;
        return money + prize;
    }
}
public class SumPrizeDecorator extends Decorator {
    public SumPrizeDecorator(Component c) {
        super(c);
    }
    public double calcPrize(String user, Date begin, Date end) {
        // 先获取前面运算出来的奖金
        double money = super.calcPrize(user, begin, end);
        // 然后计算累计奖金
        double prize = 1000000 * 0.001;
        return money + prize;
    }
}
public class Client {
    public static void main(String[] args) {
        Component c1 = new ConcreteComponent();
        Decorator d1 = new MonthPrizeDecorator(c1);
        Decorator d2 = new SumPrizeDecorator(d1);
        double userA = d1.calcPrize("a", null, null);
        double userB = d2.calcPrize("b", null, null);
    }
}

案例说明:

170404990.png

3.模式讲解

由于奖金的计算方式经常发生变动,几乎每个季度都有小调整,每年都有大调整,这要求软件实现要足够灵活,能够很快进行相应的调整和修改,否则就不能满足实际业务的需要。把问题抽象以下:设若有一个计算奖金的对象,现在需要能够灵活的给它增加和减少功能,还需要能够动态的组合功能,每个功能就相当于在计算奖金的某个部分。

则问题就是:如何能透明地给一个对象增加功能,并实现功能的动态组合。

解决思路

所谓透明地给一个对象增加功能,即给一个对象增加功能,但不能让这个对象知道,也就不是不去改动这个对象,而实现给一个对象透明的增加功能,这就实现了功能的动态组合。

实现透明地给一个对象增加功能,即要扩展对象功能,可使用继承

为了能够实现和原来使用被装饰对象的代码无缝结合,通过顶一个抽象类,让这个类实现与被装饰对象相同的接口,然后在具体实现类中,转调被装饰的独享,在转调前后添加新的功能,这就实现了给装饰对象增加功能。

在转调的时候,如果觉得被装饰对象的功能不再被需要,还可以直接替换,也就是不在转调,而是在装饰杜相忠完成全新的实现。

示例代码

public abstract class Component {
    public abstract void operation();
}
public class ConcreteComponent extends Component {
    public void operation() {
        // 相应的功能处理
    }
}
public abstract class Decorator extends Component {
    protected Component component;
    public Decorator(Component component) {
        super();
        this.component = component;
    }
    public void operation() {
        // 转发请求给组件独享,可以在转发前后执行一些附加动作
        component.operation();
    }
}
public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }
    private String addedState;
    public void operation() {
        // 调用父类的方法,可以在调用前后执行一些附加动作
        // 在这里进行处理的时候,可以使用添加的状态
        super.operation();
    }
}
public class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }
    private void addBehavior() {
        // 需要添加的职责实现
    }
    public void operation() {
        // 调用父类的方法,可以在调用前后执行一些附加动作
        super.operation();
        this.addBehavior();
    }
}

应用范围

装饰模式能够实现动态添加,是从一个对象外部来给对象添加功能,相当于改变了对象的外观。当装饰后,从外部使用系统的角度,就不再是使用原来是的那个类,而是使用被一系列装饰器包装过后的对象。

对象组合

在面向对象的设计中,有一条基本规则就是"尽量使用对象组合,而不是继承"来扩展和复用功能。装饰模式就是这个规则。

假如有一个对象A,实现了a1方法,而C1对象想要扩展A的功能,给它增加一个c1的方法。

使用继承:

public class A {
    public void a1() {}
}
public class C extends A {
    public void c1() {}
}

使用对象组合:

public class C {
    private A a = new A();
    public void a1() {
        a.a1();
    }
    public void c1() {}
}

装饰器和组件类的关系

装饰器是用来修饰组件的,装饰器一定要实现和组件类一致的接口。组件类是不知道装饰器的存在的,装饰器为组件提那家功能是一种透明的包装。

装饰器的应用—— I/O流

170501449.png

InputStream就相当于装饰模式中的Component;

FileInputStream、ObjectInputStream、StringBufferInputStream这些对象都是直接继承InputStream,这些相当于装饰模式中的ConcreteComponent,是可以被装饰器装饰的对象;

FilterInputStream相当于装饰器模式中的Decorator,而它的子类DataInputStream、BufferedInputStream、LinkNumberInputStream和PushbackInputStream 相当于装饰器模式中的ConcreteDecorator。

装饰模式的优缺点:

优点:比继承更灵活、更容易复用功能、简化高层定义

缺点:会产生很多细粒度对象

4.思考

装饰模式的本质是:动态组合

动态是手段,组合才是目的。这里组合有两个意思,一个是动态功能的组合,也就是动态进行装饰器的组合;另一个是指对象组合,通过对象组合来实现为装饰对象透明的增加功能。

何时选用装饰模式模式

如果想:在不影响其他对象的情况下,以动态、透明的方式给对象添加职责,可以使用装饰模式。

如果:不适合使用子类来扩展的时候,可以使用装饰模式。



说明:笔记内容摘自《研磨设计模式》陈臣,王斌

关联:整理了一些Java软件工程师的基础知识点