一、
先来复习几个设计原则:
- 封装变化
(把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。) - 针对接口编程,而不是针对实现编程。
- 多用组合,少用继承。
(因为使用组合建立系统具有很大的弹性,不仅可将算法封装成类,更可以在运行时动态地改变行为,只要组合的行为对象符合正确的接口标准即可。)
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在 运行时 动态地进行扩展。
通过动态地组合组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进 bug 或产生意外副作用的机会将大幅减少。
【装饰者模式】体现的设计原则:
类应该对扩展开放,对修改关闭。
- 装饰者和被装饰者有相同的超类。
- 可以用一个或多个装饰类包装一个对象。
- 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)场合,可以用装饰过的对象代替它。
- 装饰者可以在所委托被装饰者的行为之前 与/或 之后,加上自己的行为,以达到特定的目的。
- 对象可以在任何时候装饰,所以可以在运行时动态地、不限量地用喜欢的装饰者来装饰对象。
定义装饰者模式:
装饰者模式 动态地 将责任附加在对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
java.io 包中很多类都是装饰者。
BufferedInputStream 及 LineNumberInputStream 都扩展自 FilterInputStream ,而 FilterInputStream 是一个抽象的装饰类。
“输出”流的设计方式也是一样的,而且 Reader/Writer 流(作为基于字符数据的输入输出)和 输入/输出流的类 相当类似。
但是 Java I/O 也引出装饰者模式的一个“缺点”:
利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此 API 的程序员的困扰。
编写一个 Java I/O 装饰者:
要求:
编写一个装饰者,把输入流内的所有大写字符转成小写。
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in)
{super(in);}
public int read() throws IOException
{
int c=super.read();
return (c==-1?c:Character.toLowerCase((char)c));
}
public int read(byte[] b,int offset,int len)throws IOException
{
int result=super.read(b,offset,len);
for(int i=offset;i<offset+result;i++)
{
b[i]=(byte) Character.toLowerCase((char)b[i]);
}
return result;
}
}
测试类:
public class InputTest {
public static void main(String[] args) {
int c;
try
{
InputStream in=new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("/jia/files/hello.txt")));
while((c=in.read())>=0)
{
System.out.print((char)(c));
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
要点:
- 继承属于 扩展 的形式之一,但不见得是达到弹性设计的最佳方式。
- 在设计中,应该允许行为被扩展,而无须修改现有的代码。
- 组合和委托可用于在运行时动态地加上新的行为。
- 除了继承,装饰者模式也可以让我们扩展行为。
- 装饰者模式意味着一群装饰者类,这些类用来包装具体组件。
装饰者模式反映出被装饰的组件类型(事实上,题目具有相同的类型,都经过接口或继承实现)。 - 装饰者可以在被装饰者的行为前面/与后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
- 可以用无数个装饰者包装一个组件。
- 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。
- 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。
二、
为了多态,通过父类指针指向其具体子类,但是这会造成另一问题:
当我们需要为子类添加新的职责,就必须向其父类添加一个这个功能的抽象接口,否则通过父类指针无法调用这个方法了。
这样处于高层的父类就含有太多的的方法, 并且继承自这个父类的所有子类都不可避免继承了父类的这些接口,但是这些可能并不是所有子类所需要的。
🌹为此我们采用组合的方式而不是继承方式。🌹
当需要添加一个操作的时候就可以通过 Decorator 模式来解决。
装饰模式通过一个包装对象,以对客户端透明的方式动态地给一个对象附加上更多的责任。
public class DecorationTest {
public static void main(String[] args) {
//采用这样的方式我们可以通过ConcreteDecorator的构造函数
// 来确定传递某个具体的ConcreteComponent类
Component component = new ConcreateComponet();
component.operation();
System.out.println();
Component component1=new ConcreateDecorator1(new ConcreateComponet());
component1.operation();
System.out.println();
Component component2=new ConcreateDecorator2(new ConcreateDecorator1(new ConcreateComponet()));
component2.operation();
}
}
// 抽象构件角色(Component):给出一个抽象接口,以规范准备接受附加责任的对象
interface Component
{
public void operation();
}
class ConcreateComponet implements Component
{
@Override
public void operation() {
System.out.println("拍照片");
}
}
// 装饰角色(Decorator):持有一个构件(Component)对象的引用,并定义一个与抽象接口一致的接口
abstract class Decorator implements Component
{
Component component;
public Decorator(Component component)
{this.component=component;}
}
//具体装饰角色(Concrete Decorator):负责给构件对象添加附加的责任
class ConcreateDecorator1 extends Decorator
{
public ConcreateDecorator1(Component component)
{super(component);}
@Override
public void operation() {
component.operation();
System.out.println("加滤镜");
}
}
class ConcreateDecorator2 extends Decorator
{
public ConcreateDecorator2(Component component)
{super(component);}
@Override
public void operation() {
component.operation();
System.out.println("加贴纸");
}
}
运行结果:
拍照片
拍照片
加滤镜
拍照片
加滤镜
加贴纸