关于本期的Decorator设计模式,我想不出比较合适准确贴切的例子啊。
不如就这么直接跳过了吧!!!!
那么就举一个不贴切的例子吧。
我们有一个加减乘除计算器,但是我们需要一个新功能——求余运算,那么我们该怎么做呢?
如果已经存在的一个类缺少某些方法,或者须要给方法添加更多的功能,你也许会仅仅继承这个类来产生一个新类—这建立在额外的代码上。
对于继承来说,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
如果 你希望改变一个已经初始化的对象的行为,你怎么办?或者,你希望继承许多类的行为,改怎么办?前一个,只能在于运行时完成,后者显然时可能的,但是可能会导致产生大量的不同的类—可怕的事情。
对此我们考虑一下半篇的主题,装饰者模式。
我们有一个抽象的计算器类:
abstract class Caculator {
public int add(int x, int y) {
return x + y;
}
public int dec(int x, int y) {
return add(x, -y);
}
public int mul(int x, int y) {
return x * y;
}
public int div(int x, int y) {
return x / y;
}
abstract public int caculate(int x, int y, char operation);
}
该类提供基础的 +,-,x,/四则运算,但是主体的计算函数需要子类去实现。
class SimpleCaculator extends Caculator {
@Override
public int caculate(int x, int y, char operation) {
switch (operation) {
case '+':
return add(x, y);
case '-':
return dec(x, y);
case 'x':
return mul(x, y);
case '/':
return div(x, y);
default:
break;
}
return Integer.MIN_VALUE;
}
}
我们的简单计算器,实现了根据运算符去判断调用什么函数。
但是这个里面没有实现求余操作啊,我们该怎么办呢?
interface CaculatorWrapper {
public int mod(int x, int y);
}
class MoreComplexCaculator extends Caculator implements CaculatorWrapper {
private Caculator caculator = null;
public MoreComplexCaculator(Caculator caculator) {
this.caculator = caculator;
}
@Override
public int mod(int x, int y) {
return caculator.caculate(x, caculator.caculate(caculator.caculate(x, y, '/'), y, 'x'), '-');
}
@Override
public int caculate(int x, int y, char operation) {
switch (operation) {
case '%':
return mod(x, y);
default:
return caculate(x, y, operation);
}
}
}
利用 mod = x - x / y *y 计算余数
然后我们成功的给该计算器添加了新功能求余。
public class Decorator {
public static void main(String[] args) {
Caculator simple = new SimpleCaculator();
System.out.println(simple.caculate(22, 33, '%'));
Caculator complex = new MoreComplexCaculator(simple);
System.out.println(complex.caculate(22, 33, '%'));
}
}
输出
-2147483648
22
对此我们将上述例子可以总结以下:
你如何组织你的代码使其可以容易的添加基本的或者一些很少用到的 特性,而不是直接不额外的代码写在你的类的内部?
解决方案:
装饰器模式: 动态地给一个对象添加一些额外的职责或者行为。就增加功能来说, Decorator模式相比生成子类更为灵活。
装饰器模式提供了改变子类的灵活方案。装饰器模式在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
当用于一组子类时,装饰器模式更加有用。如果你拥有一族子类(从一个父类派生而来),你需要在与子类独立使用情况下添加额外的特性,你可以使用装饰器模式,以避免代码重复和具体子类数量的增加。
结构:
抽象组件角色(Component):定义一个对象接口,以规范准备接受附加责任的对象,即可以给这些对象动态地添加职责。
具体组件角色(ConcreteComponent) :被装饰者,定义一个将要被装饰增加功能的类。可以给这个类的对象添加一些职责
抽象装饰器(Decorator):维持一个指向构件Component对象的实例,并定义一个与抽象组件角色Component接口一致的接口
具体装饰器角色(ConcreteDecorator):向组件添加职责。
按照上例各类角色如下:
抽象组件角色:Caculator类
具体组件对象:SimpleCaculator类
抽象装饰器:CaculatorWrapper接口
具体装饰器类:ComplexCaculator类
效果
装饰模式的特点:
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的索引(reference)
(3) 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
Decorator模式至少有两个主要优点和两个缺点:
1) 比静态继承更灵活: 与对象的静态继承(多重继承)相比, Decorator模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类。这会产生许多新的类,并且会增加系统的复杂度。此外,为一个特定的Component类提供多个不同的 Decorator类,这就使得你可以对一些职责进行混合和匹配。使用Decorator模式可以很容易地重复添加一个特性。
2) 避免在层次结构高层的类有太多的特征 Decorator模式提供了一种“即用即付”的方法来添加职责。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并且用 Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。这样,应用程序不必为不需要的特征付出代价。同时更易于不依赖于 Decorator所扩展(甚至是不可预知的扩展)的类而独立地定义新类型的 Decorator。扩展一个复杂类的时候,很可能会暴露与添加的职责无关的细节。
3) Decorator与它的Component不一样 Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰不应该依赖对象标识。
4) 有许多小对象 采用Decorator模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。
具体实例:我们的IO流包中,很多的流类都是采用了装饰器模式。就比如:
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
public int read() throws IOException {
return in.read();
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
public long skip(long n) throws IOException {
return in.skip(n);
}
public int available() throws IOException {
return in.available();
}
public void close() throws IOException {
in.close();
}
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
public synchronized void reset() throws IOException {
in.reset();
}
public boolean markSupported() {
return in.markSupported();
}
}
BufferedInputStream,DataInputStream,PushbackInputStream 继承FilterInputStream 然后扩充其方法 功能。
在我们的spring中 你可以看到xxxxWrapper这些基本上都算是装饰器模式,就比如:
public interface BeanWrapper extends ConfigurablePropertyAccessor {
Object getWrappedInstance();
Class<?> getWrappedClass();
PropertyDescriptor[] getPropertyDescriptors();
PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
int getAutoGrowCollectionLimit();
}
BeanWrapper接口定义了提供一个获得bean 运行时状态的方法接口,以供分析操作该bean。
具体实现类为BeanWrapperImpl
总结:
使用装饰设计模式 不必重写任何已有的功能性代码,而是对某个基于对象应用增量变化。
Decorator模式采用对象组合而非继承的手法,实现了在运行时动态的扩展对象功能的能力,
而且可以根据需要扩展多个功能,避免了单独使用继承带来的“灵活性差”和“多子类衍生问题”。
同时它很好地符合面向对象设计原则中“优先使用对象组合而非继承”和“开放-封闭”原则。
以下情况使用Decorator模式
1. 需要扩展一个类的功能,或给一个类添加附加职责。
2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
Decorator设计模式和Adapter设计模式相比:
1.虽然Adapter模式也能达到和Decorator模式一样增加功能的效果,但是Decorator设计模式更侧重的是转换接口,使的不相容的接口能够兼容而工作。
2.适配器模式是用新接口来调用原接口,原接口对新系统是不可见或者说不可用的。装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。
3.关于其包裹的对象:适配器是知道被适配者的详细情况的(就是那个类或那个接口)。装饰者只知道其接口是什么,至于其具体类型(是基类还是其他派生类)只有在运行期间才知道。