(二)“单一职责”模式
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键就是划清责任。
1. Decorator 装饰模式
◆ 动机:
在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
如何使“对象功能的扩展”能够根据需要来动态实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降为最低?
首先,用代码举一个例子,这是一个操作数据流的类:
//业务操作
abstract class Stream {
public abstract char read(int number);
public abstract void seek(int position);
public abstract void write(char data);
}
//主体类
class FileStream extends Stream {
//读文件流
@Override
public char read(int number){
//***********
}
//定位文件流
@Override
public void seek(int position){
//***********
}
//写文件流
@Override
public void write(char data){
//***********
}
}
class NetworkStream extends Stream {
//读文件流
@Override
public char read(int number){
//###########
}
//定位文件流
@Override
public void seek(int position){
//###########
}
//写文件流
@Override
public void write(char data){
//###########
}
}
class MemoryStream extends Stream {
//读文件流
@Override
public char read(int number){
//@@@@@@@@@@@
}
//定位文件流
@Override
public void seek(int position){
//@@@@@@@@@@@
}
//写文件流
@Override
public void write(char data){
//@@@@@@@@@@@
}
}
//扩展操作
class CryptoFileStream extends FileStream {
@Override
public char read(int number) {
//额外的加密操作...
//读文件流
super.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位文件流
super.seek(position);
//额外的加密操作...
}
//写内存流
@Override
public void write(char data) {
//额外的加密操作...
//写文件流
super.write(data);
//额外的加密操作...
}
}
class CryptoNetworkStream extends NetworkStream {
@Override
public char read(int number) {
//额外的加密操作...
//读网络流
super.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位网络流
super.seek(position);
//额外的加密操作...
}
@Override
public void write(char data) {
//额外的加密操作...
//写网络流
super.write(data);
//额外的加密操作...
}
}
class CryptoMemoryStream extends MemoryStream {
@Override
public char read(int number) {
//额外的加密操作...
//读内存流
super.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位内存流
super.seek(position);
//额外的加密操作...
}
//写内存流
@Override
public void write(char data) {
//额外的加密操作...
//写内存流
super.write(data);
//额外的加密操作...
}
}
class BufferedFileStream extends FileStream {
}
class BufferedNetworkStream extends NetworkStream {
}
class BufferedMemoryStream extends MemoryStream {
}
class CryptoBufferedFileStream extends FileStream {
@Override
public char read(int number) {
//额外的加密操作...
//额外的缓冲操作...
//读文件流
super.read(number);
//额外的加密操作...
//额外的缓冲操作...
}
@Override
public void seek(int position) {
//额外的加密操作...
//额外的缓冲操作...
//定位文件流
super.seek(position);
//额外的加密操作...
//额外的缓冲操作...
}
@Override
public void write(char data) {
//额外的加密操作...
//额外的缓冲操作...
//写文件流
super.write(data);
//额外的加密操作...
//额外的缓冲操作...
}
}
class Client {
public static void main(String args[]) {
//编译时装配
CryptoFileStream fs1 = new CryptoFileStream();
BufferedFileStream fs2 = new BufferedFileStream();
CryptoBufferedFileStream fs3 = new CryptoBufferedFileStream();
}
}
代码的结构逻辑如下图,这样的数据机构会生成很多很多子类。注意,crypto、Bufferde这些方法是要在FileStream、NetwodStream等对象的基础上才能实现的。FileStream这些类叫做“主体类”,CryptoFileStream这些类叫做“扩展类”,
利用Decorator模式进行优化,优化后代码如下:
//业务操作
abstract class Stream {
public abstract char read(int number);
public abstract void seek(int position);
public abstract void write(char data);
}
//主体类
class FileStream extends Stream {
//读文件流
@Override
public char read(int number){
//***********
}
//定位文件流
@Override
public void seek(int position){
//***********
}
//写文件流
@Override
public void write(char data){
//***********
}
}
class NetworkStream extends Stream {
//读文件流
@Override
public char read(int number){
//###########
}
//定位文件流
@Override
public void seek(int position){
//###########
}
//写文件流
@Override
public void write(char data){
//###########
}
}
class MemoryStream extends Stream {
//读文件流
@Override
public char read(int number){
//@@@@@@@@@@@
}
//定位文件流
@Override
public void seek(int position){
//@@@@@@@@@@@
}
//写文件流
@Override
public void write(char data){
//@@@@@@@@@@@
}
}
//继承:接口协议
class CryptoStream extends Stream {
/**
* 组合:复用实现
*/
Stream s;// s=new FileStream(); s=new NetworkStream(); s=new MemoryStream();
// 在子类中以父类的实例作为一个成员变量,这是典型的decrorator模式
/**
* 通过构造函数传递一个具体的类型进来
*/
public CryptoStream(Stream s)
{
this.s=s;
}
@Override
public char read(int number) {
//额外的加密操作...
//读文件流
s.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位文件流
s.seek(position);
//额外的加密操作...
}
//写内存流
@Override
public void write(char data) {
//额外的加密操作...
//写文件流
s.write(data);
//额外的加密操作...
}
}
class BufferedStream extends Stream {
Stream s;
public BufferedStream(Stream s)
{
this.s=s;
}
}
class Client {
public static void main(String args[]) {
//运行时装配
Stream s1=new CryptoStream( new FileStream());
Stream fs2 = new BufferedStream(new FileStream());
Stream fs3 = new CryptoStream(new BufferedStream(new FileStream()));
}
}
这样,Decorator模式基本上已经算完成了,不过我们还可以再进行一个小优化:
//业务操作
abstract class Stream {
public abstract char read(int number);
public abstract void seek(int position);
public abstract void write(char data);
}
//主体类
class FileStream extends Stream {
//读文件流
@Override
public char read(int number){
//***********
}
//定位文件流
@Override
public void seek(int position){
//***********
}
//写文件流
@Override
public void write(char data){
//***********
}
}
class NetworkStream extends Stream {
//读文件流
@Override
public char read(int number){
//###########
}
//定位文件流
@Override
public void seek(int position){
//###########
}
//写文件流
@Override
public void write(char data){
//###########
}
}
class MemoryStream extends Stream {
//读文件流
@Override
public char read(int number){
//@@@@@@@@@@@
}
//定位文件流
@Override
public void seek(int position){
//@@@@@@@@@@@
}
//写文件流
@Override
public void write(char data){
//@@@@@@@@@@@
}
}
/**
* 一个基类,用于提取CryptoStream和BufferedStream共同的成员变量s
* 因为这个类没有继承父类所有属性,所以是抽象类
* 根据重构的原则,继承自同一父类的几个子类含有相同的字段或变量时,应该把它往上提,
* 而直接放在Stream类里面的话,会影响FileStream、MemoryStream等类的使用,所以我们就要一个基类实现重构
*/
//继承:接口协议
abstract DecroratorStream extends Stream{
//组合:复用实现
protected Stream s;// s=new FileStream(); s=new NetworkStream(); s=new MemoryStream();
protected DecroratorStream(Stream s){
this.s=s;
}
}
class CryptoStream extends DecroratorStream {
public CryptoStream(Stream s)
{
//改为继承父类的方法
super(s);
}
@Override
public char read(int number) {
//额外的加密操作...
//读文件流
s.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位文件流
s.seek(position);
//额外的加密操作...
}
//写内存流
@Override
public void write(char data) {
//额外的加密操作...
//写文件流
s.write(data);
//额外的加密操作...
}
}
class BufferedStream extends DecroratorStream {
Stream s;
public BufferedStream(Stream s)
{
super(s);
}
//....
}
class Client {
public static void main(String args[]) {
//运行时装配
FileStream fs=new FileStream();
Stream s1=new CryptoStream( new FileStream());
Stream s2 = new BufferedStream(new FileStream());
Stream s3 = new CryptoStream(new BufferedStream(new FileStream()));
}
}
现在逻辑结构如下图,可以看到,我们的代码结构简洁了很多!
◆ 模式定义:
动态地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类更为灵活(消除重复代码&减少子类个数)。
◆ 要点总结:
● 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”;
● Decorator类在接口上表现为 is-a Component的继承关系,即Decorator类继承了Component类所有的接口。但在实现上又表现为 has-a Component的组合关系,即Decorator类又使用了另一个Component类。这就是Decorator模式的典型特征;(见下图)
● Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主题类在
多个方向上的扩展功能” —— 是为“装饰”的含义;