一、前言
相信学习Java的同学都知道面向对象的几个特征,其中继承是面向对象编程语言的重要特征之一。我们都知道继承是有一个子父类的关系,子类通过extends父类,使得子类拥有父类的某些特征和行为,同时子类可以添加属于自己的一些特征和行为,从而可以增强子类的功能。
正如上面讲到,通过继承可以增强子类的功能,但是假如我们要构造出具有很多个不同行为特征的类,使用继承这将导致我们系统中产生很多且关系复杂的类。
今天,我就来跟大家分享一下Java设计模式中的装饰模式,用于解决既增强了功能又不会导致类的数量急剧增长的问题。
二、什么是装饰模式
1、大家都知道,在Java IO(InputStream和OutputStream)体系中,我们不仅可以构造出简单的文件流,字节流或者字符流,为了提高IO的性能或者其他需求,我们可以在使用文件流的同时加上缓冲流,过滤流或者其他。这也如上面所说起到了增强功能的作用。但是我们通过查看JDK API知道,Java中关于IO的类并不是很多,但是却能实现我们各种各样关于文件处理的需求,这就是装饰模式带来的好处。可以说,在IO体系中,基本上是基于装饰模式展开的。
2、装饰模式的特征
1)装饰模式可以实现在不增加更多子类的情况下扩展对象(注意这里说的是对象而不是类,而继承则是扩展了子类的功能)的功能。
2)装饰模式又可称之为包装(Wrapper)模式,可以对客户端(也就是调用端)透明的方式下扩展对象的功能,可以说是继承关系的一种替代方式。
3)装饰模式通过对客户端透明的方式动态的为对象增加更多更强的功能或者说责任。所谓透明,就是客户端不会觉得对象在装饰前和装饰后有什么不一样的地方。
3、装饰模式涉及到的角色
1)抽象构建角色:给出一个抽象接口,用来规范准备接受附加功能的对象。通常由接口来充当
2)具体构建角色:定义一个将要接受附加功能的类。
3)装饰角色:它持有构建角色对象的引用,同时定义了一个与抽象构建角色一致的接口,也就是实现了抽象构建角色这一接口。
4)具体装饰角色:它负责给构建对象附加额外的功能。
三、装饰模式的实现
1、角色对应代码例子
1)抽象构建角色,例子代码如下:
/**
*
* @ClassName: IComponent
* @Description: 抽象构建角色
* @author admin
* @date 2016年12月24日 下午3:59:01
* @version V1.0
*/
public interface IComponent {
public abstract void function();
}
2)具体构建角色,例子代码如下:
/**
*
* @ClassName: ComponentImpl
* @Description: 具体构建角色
* @author admin
* @date 2016年12月24日 下午3:59:43
* @version V1.0
*/
public class ComponentImpl implements IComponent {
@Override
public void function() {
System.out.println("操作1");
}
}
3)装饰角色,例子代码如下:
/**
*
* @ClassName: Decorator
* @Description: 装饰角色
* @author admin
* @date 2016年12月24日 下午4:02:00
* @version V1.0
*/
public class Decorator implements IComponent {
private IComponent component;
public Decorator(IComponent component) {
this.component = component;
}
@Override
public void function() {
component.function();
}
}
4)具体装饰角色1,例子代码如下:
/**
*
* @ClassName: ConcreteDecoratorOne
* @Description: 具体装饰角色
* @author admin
* @date 2016年12月24日 下午4:04:55
* @version V1.0
*/
public class ConcreteDecoratorOne extends Decorator {
public ConcreteDecoratorOne(IComponent component) {
super(component);
}
@Override
public void function() {
super.function();
this.subFunction();
}
//具体装饰角色自己的方法
private void subFunction(){
System.out.println("操作2");
}
}
5)具体装饰角色2,例子代码如下:
/**
*
* @ClassName: ConcreteDecoratorTwo
* @Description: 具体装饰角色
* @author admin
* @date 2016年12月24日 下午4:07:42
* @version V1.0
*/
public class ConcreteDecoratorTwo extends Decorator {
public ConcreteDecoratorTwo(IComponent component) {
super(component);
}
@Override
public void function() {
super.function();
this.subFunction();
}
//具体装饰角色自己的方法
private void subFunction(){
System.out.println("操作3");
}
}
6)客户端,例子代码如下:
/**
*
* @ClassName: Client
* @Description: 客户端调用者
* @author admin
* @date 2016年12月24日 下午5:14:41
* @version V1.0
*/
public class Client {
public static void main(String[] args) {
// 相当于IO中的节点流
IComponent component = new ComponentImpl();
component.function();
System.out.println("-----------------");
// 相当于IO中的过滤流
IComponent component2 = new ConcreteDecoratorOne(component);
component2.function();
System.out.println("-----------------");
// 相当于IO中的过滤流
IComponent component3 = new ConcreteDecoratorTwo(component2);
component3.function();
System.out.println("-----------------");
IComponent iComponent = new ConcreteDecoratorOne(new ConcreteDecoratorTwo(new ComponentImpl()));
iComponent.function();
}
}
2、执行结果,如下:
3、分析
从上面的例子代码和执行结果可以知道,假如我们使用component对象去执行,那么只实现了一种操作;假如使用component2对象去执行,那么实现了两种操作;假如使用component3对象去执行,那么最终将实现三种操作。
对于component2对象,它装饰了component对象;而对于component3对象,它又装饰了component2对象,那么也就间接的装饰了component对象。这样一来,被装饰的component对象就好像被附加了额外的责任,从而也增强了此对象的功能。
而对于最后一种情况,iComponent对象也被增强可功能。但是它在代码的写法上,我们应该是很熟悉的,那就是我们在编写IO相关的代码的时候,为了实现我们的功能,我们会一层一层的包装那个最终我们需要去操作的对象,从而实现了例如“可以处理字节的,带缓冲的文件流”这么一种功能。
四、装饰模式与基础PK
1、装饰模式:
1)不需要子类,防止由于子类过多而导致系统的复杂性和混乱。
2)用来扩展对象的功能。
3)对于一个给定的对象以及一组不同的装饰对象,客户端可以通过自己的需要选择合适的装饰对象来包装给定的对象进行增强其功能,灵活性高。
2、继承
1)需要子类,会导致系统中很多子类的产生
2)用于扩展类的功能,也可以说是扩展一类对象的功能。
3)子类繁多,为了增强某个子类的功能,就必须与其他类建立关联,耦合性高且缺乏灵活性。