装饰模式WrapperMode

装饰模式

装饰模式,又称Decorator亦或Wrapper模式

参考了Alexander Shvets的深入设计模式

1.装饰模式的目的

是一种十分常见的设计模式,装饰者模式可以动态地给一个对象增加其他职责。就扩展对象功能来说,装饰者模式比生成子类更为灵活

  • 继承是静态的。 你无法在运行时更改已有对象的行为, 只能使用由不同子类创建的对象来替代当前的整个对象。
  • 子类只能有一个父类。 大部分编程语言不允许一个类同时继承多个类的行为。

当为增强功能而需要通过继承生成很多子类时,可以使用装饰模式

尝试分析一波

假如写了一个通知的功能,有一个实现

请添加图片描述

现在我们想拓展或者说增强通知的功能,第一感觉是写更多的子类(or实现类)

请添加图片描述

但如果可以使用为每一个功能都设置一个Wrapper或Decorator

就可以变成这样

请添加图片描述

亦或是你想设计一个非常牛逼Wrapper,它能完成所有的功能

请添加图片描述

显然,通过装饰器模式增强类(或接口)的功能比继承来的更加灵活

2.装饰模式的思路

  1. 确保业务逻辑可用一个基本组件及多个额外可选层次表示。

  2. 找出基本组件和可选层次的通用方法。 创建一个组件接口并在其中声明这些方法。

  3. 创建一个具体组件类, 并定义其基础行为。

  4. 创建装饰基类, 使用一个成员变量存储指向被封装对象的引用。 该成员变量必须被声明为组件接口类型, 从而能在运行时连接具体组件和装饰。 装饰基类必须将所有工作委派给被封装的对象。(和代理模式一样实现相同接口)

  5. 确保所有类实现组件接口。

  6. 将装饰基类扩展为具体装饰。 具体装饰必须在调用父类方法 (总是委派给被封装对象) 之前或之后执行自身的行为。(执行加强操作)

  7. 客户端代码负责创建装饰并将其组合成客户端所需的形式。(选择需要的功能的装饰器Wrapper,并将具体组件注入其中,通过Wrapper即可调用增强的方法or功能)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PCDljtKo-1631076356634)(F:\LocalTyproPictrue\knowit.png)]

3.代码实现

1.基础接口

/**
 * 
 * @author ASUS     
 * 	需要装饰的接口
 *  数据源接口
 * 	提供读和写数据的方法
 */
public interface DataSource {
	
	/**
	 * 读数据
	 * @throws FileNotFoundException 文件不存在异常
	 * @throws IOException 
	 */
	void ReaderData() throws  IOException;
	
	/**
	 * 写数据
	 * @throws FileNotFoundException 
	 * @throws IOException 
	 */
	void WriteData(String filename,String Context) throws IOException;

}

具体组件

/**
 * 
 * @author ASUS
 *     数据源接口的实现类
 *     文件数据源
 *     实现文件中数据的读写
 */
public class FileDataSource implements DataSource{
	
	/**
	 * 文件名
	 */
	private String filename;
	
	/**
	 * 构造方法指定数据源文件名
	 * @param filename
	 */
	public FileDataSource(String filename) {
		this.filename = filename;
	}

	@Override
	public void ReaderData() throws IOException {
		FileInputStream in = new FileInputStream(new File(filename));
		DataInputStream din = new DataInputStream(in);
		String Context = din.readUTF();
//		byte[] bytes = new byte[1024];
//		int len = 0;
//		while ((len = in.read(bytes))!=-1) {
//			System.out.println(new String(bytes, 0, len));
//		}
		System.out.println(Context);
		in.close();
	}

	@Override
	public void WriteData(String filename,String Context) throws IOException {
		FileOutputStream out = new FileOutputStream(new File(filename),true);
		DataOutputStream dout = new DataOutputStream(out);
		dout.writeUTF(Context);
		dout.close();
		out.close();
	}
	
	

}

基础包装类:

/**
 *      数据源装饰类
 * @author ASUS
 *     实现了和具体组件一样的基础接口,并组合一个接口的实例,让关联的实例去执行操作的方法,这点和代理模一样
 */
public class DataSourceWrapper implements DataSource {
	
	/**
	 * 组合(也可以说是关联一个增强接口的实例)
	 */
	private DataSource datasource;
	
	/**
	 *     通过构造方法注入一个DataSource
	 * @param daSource
	 */
	public DataSourceWrapper(DataSource datasource) {
		this.datasource = datasource;
	}

    //基础装饰类可以不对datasource对象增强,
    //而是通过关联对象执行方法,在具体装饰类中进行增强
	@Override
	public void ReaderData() throws IOException {
		datasource.ReaderData();
	}

	@Override
	public void WriteData(String filename, String Context) throws IOException {
		datasource.WriteData(filename, Context);
	}
	
}

具体包装类:

/**
 *       增强包装类
 *  @author ASUS
 * 	通过继承基础包装类,并在其中添加增强的功能
 */
public class EncrptionDataSourceWrapper extends DataSourceWrapper{

	public EncrptionDataSourceWrapper(DataSource daSource) {
		super(daSource);
	}

	@Override
	public void ReaderData() throws IOException {
		// TODO Auto-generated method stub
		super.ReaderData();
	}

	@Override
	public void WriteData(String filename, String Context) throws IOException {
		// TODO Auto-generated method stub
		//调用增强方法对接口功能进行增强
		super.WriteData(filename, EncoderContext(Context));
	}
	
	/**
	 * 
	 * @param 为接口提供的增强方法
	 * @return 对数据进行加密
	 */
	public String EncoderContext(String Context) {
		Encoder encoder = Base64.getEncoder();
		return encoder.encodeToString(Context.getBytes());
	}

}

具体包装类:

/**
 * 具体装饰者类
 * @author ASUS
 * 增强功能:压缩
 * 继承自基础装饰者类
 * DataSourceWrapper
 */
public class CompressDataSourceWrapper extends DataSourceWrapper{

	//注入组合的具体实现类,用以调用基础方法
	public CompressDataSourceWrapper(DataSource datasource) {
		super(datasource);
	}

	@Override
	public void ReaderData() throws IOException {
		super.ReaderData();
	}

	@Override
	public void WriteData(String filename, String Context) throws IOException {
        super.WriteData(filename,Context);
		doZip(new File(filename), new ZipOutputStream(new FileOutputStream(new File("shit.zip"))),
              "dir",Context);
	}
	
    /**
     为接口提供增强方法
     @param: File infile压缩包里的目标文件名
     @param: ZipOutputStream out压缩输出流
     @param: String dir 压缩文件目录名
     @param: String Context 压缩目标文件内容
    */
	public void doZip(File outFile, ZipOutputStream out, String dir,String Context) throws IOException {
        String entryName = null;
        if (!"".equals(dir)) {
            entryName = dir + "/" + outFile.getName();
        } else {
            entryName = outFile.getName();
        }
        ZipEntry entry = new ZipEntry(entryName);
        out.putNextEntry(entry);
        DataOutputStream dout = new DataOutputStream(out);
        dout.writeUTF(Context);
        
        out.closeEntry();
        dout.close();
    }
    

}

客户端代码:

/**
 * 测试装饰模式
 * @author ASUS
 *
 */
public class AppMain {
	public static void main(String[] args) {
        
        //创建具体组件对象
		DataSource source = new FileDataSource("fuck.txt");
		
        //创建具体包装类,并注入具体组件
		DataSourceWrapper wrapper = new EncrptionDataSourceWrapper(source);
		DataSourceWrapper wrapper2 = new CompressDataSourceWrapper(source);
		
		try {
			//wrapper.WriteData("sure.txt", "你看是不是密文就完事了");
            //通过包装类执行增强后的方法
			wrapper2.WriteData("sure.txt", "刷新项目查看是否出现压缩包?");
			
		} catch (IOException e) {
			
			e.printStackTrace();
		}
	}

}

请添加图片描述

和代理模式一样,都是利用组合的思想,把具体方法的执行交给组合的对象,然后在具体的装饰类中对方法进行增强

4.装饰模式的优缺点

  • 你无需创建新子类即可扩展对象的行为。

  • 你可以在运行时添加或删除对象的功能。

  • 你可以用多个装饰封装对象来组合几种行为。

  • 单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类。

  • 在封装器栈中删除特定封装器比较困难。

  • 实现行为不受装饰栈顺序影响的装饰比较困难。

  • 各层的初始化配置代码看上去可能会很糟糕。

5.捕风捉影

在IO流中,装饰模式的代码非常明显,

/**
 * This class is the superclass of all classes that filter output
 * streams. These streams sit on top of an already existing output
 * stream (the <i>underlying</i> output stream) which it uses as its
 * basic sink of data, but possibly transforming the data along the
 * way or providing additional functionality.
 * <p>
 * The class <code>FilterOutputStream</code> itself simply overrides
 * all methods of <code>OutputStream</code> with versions that pass
 * all requests to the underlying output stream. Subclasses of
 * <code>FilterOutputStream</code> may further override some of these
 * methods as well as provide additional methods and fields.
 *
 * @author  Jonathan Payne
 * @since   JDK1.0
 */
public
class FilterOutputStream extends OutputStream {
    /**
     * The underlying output stream to be filtered.
     */
    protected OutputStream out;

    /**
     * Creates an output stream filter built on top of the specified
     * underlying output stream.
     *
     * @param   out   the underlying output stream to be assigned to
     *                the field <tt>this.out</tt> for later use, or
     *                <code>null</code> if this instance is to be
     *                created without an underlying stream.
     */
    public FilterOutputStream(OutputStream out) {
        this.out = out;
    }

    /**
     * Writes the specified <code>byte</code> to this output stream.
     * <p>
     * The <code>write</code> method of <code>FilterOutputStream</code>
     * calls the <code>write</code> method of its underlying output stream,
     * that is, it performs <tt>out.write(b)</tt>.
     * <p>
     * Implements the abstract <tt>write</tt> method of <tt>OutputStream</tt>.
     *
     * @param      b   the <code>byte</code>.
     * @exception  IOException  if an I/O error occurs.
     */
    public void write(int b) throws IOException {
        out.write(b);
    }

    /**
     * Writes <code>b.length</code> bytes to this output stream.
     * <p>
     * The <code>write</code> method of <code>FilterOutputStream</code>
     * calls its <code>write</code> method of three arguments with the
     * arguments <code>b</code>, <code>0</code>, and
     * <code>b.length</code>.
     * <p>
     * Note that this method does not call the one-argument
     * <code>write</code> method of its underlying stream with the single
     * argument <code>b</code>.
     *
     * @param      b   the data to be written.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FilterOutputStream#write(byte[], int, int)
     */
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    /**
     * Writes <code>len</code> bytes from the specified
     * <code>byte</code> array starting at offset <code>off</code> to
     * this output stream.
     * <p>
     * The <code>write</code> method of <code>FilterOutputStream</code>
     * calls the <code>write</code> method of one argument on each
     * <code>byte</code> to output.
     * <p>
     * Note that this method does not call the <code>write</code> method
     * of its underlying input stream with the same arguments. Subclasses
     * of <code>FilterOutputStream</code> should provide a more efficient
     * implementation of this method.
     *
     * @param      b     the data.
     * @param      off   the start offset in the data.
     * @param      len   the number of bytes to write.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FilterOutputStream#write(int)
     */
    public void write(byte b[], int off, int len) throws IOException {
        if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
            throw new IndexOutOfBoundsException();

        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }

    /**
     * Flushes this output stream and forces any buffered output bytes
     * to be written out to the stream.
     * <p>
     * The <code>flush</code> method of <code>FilterOutputStream</code>
     * calls the <code>flush</code> method of its underlying output stream.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FilterOutputStream#out
     */
    public void flush() throws IOException {
        out.flush();
    }

    /**
     * Closes this output stream and releases any system resources
     * associated with the stream.
     * <p>
     * The <code>close</code> method of <code>FilterOutputStream</code>
     * calls its <code>flush</code> method, and then calls the
     * <code>close</code> method of its underlying output stream.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FilterOutputStream#flush()
     * @see        java.io.FilterOutputStream#out
     */
    @SuppressWarnings("try")
    public void close() throws IOException {
        try (OutputStream ostream = out) {
            flush();
        }
    }
}

OutputStreamWriter的源码

public class OutputStreamWriter extends Writer {

    private final StreamEncoder se;

    /**
     * Creates an OutputStreamWriter that uses the named charset.
     *
     * @param  out
     *         An OutputStream
     *
     * @param  charsetName
     *         The name of a supported
     *         {@link java.nio.charset.Charset charset}
     *
     * @exception  UnsupportedEncodingException
     *             If the named encoding is not supported
     */
    public OutputStreamWriter(OutputStream out, String charsetName)
        throws UnsupportedEncodingException
    {
        super(out);
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
    }

StreamEncoder的结果中也包装了输出流的实例

public class StreamEncoder extends Writer {
    private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
    private volatile boolean isOpen;
    private Charset cs;
    private CharsetEncoder encoder;
    private ByteBuffer bb;
    //在其中也包装了输出流实例
    private final OutputStream out;
    private WritableByteChannel ch;
    private boolean haveLeftoverChar;
    private char leftoverChar;
    private CharBuffer lcb;
}

6.反思

在装饰模式里,一个增强功能就像一件衣服,套在原来的对象上,为其实现某个功能。

当需要拓展功能时套上,不需要时也可以脱下

就像io流中的众多对象一样,都需要把另一个流对象注入

在调用其原有方法的基础上加入增强方法,完成装饰

请添加图片描述

把拓展功能从增加子类转移到添加包装器上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗马苏丹默罕默德

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值