装饰模式
装饰模式,又称Decorator亦或Wrapper模式
参考了Alexander Shvets的深入设计模式
1.装饰模式的目的
是一种十分常见的设计模式,装饰者模式可以动态地给一个对象增加其他职责。就扩展对象功能来说,装饰者模式比生成子类更为灵活
- 继承是静态的。 你无法在运行时更改已有对象的行为, 只能使用由不同子类创建的对象来替代当前的整个对象。
- 子类只能有一个父类。 大部分编程语言不允许一个类同时继承多个类的行为。
当为增强功能而需要通过继承生成很多子类时,可以使用装饰模式
尝试分析一波
假如写了一个通知的功能,有一个实现
现在我们想拓展或者说增强通知的功能,第一感觉是写更多的子类(or实现类)
但如果可以使用为每一个功能都设置一个Wrapper或Decorator
就可以变成这样
亦或是你想设计一个非常牛逼Wrapper,它能完成所有的功能
显然,通过装饰器模式增强类(或接口)的功能比继承来的更加灵活
2.装饰模式的思路
-
确保业务逻辑可用一个基本组件及多个额外可选层次表示。
-
找出基本组件和可选层次的通用方法。 创建一个组件接口并在其中声明这些方法。
-
创建一个具体组件类, 并定义其基础行为。
-
创建装饰基类, 使用一个成员变量存储指向被封装对象的引用。 该成员变量必须被声明为组件接口类型, 从而能在运行时连接具体组件和装饰。 装饰基类必须将所有工作委派给被封装的对象。(和代理模式一样实现相同接口)
-
确保所有类实现组件接口。
-
将装饰基类扩展为具体装饰。 具体装饰必须在调用父类方法 (总是委派给被封装对象) 之前或之后执行自身的行为。(执行加强操作)
-
客户端代码负责创建装饰并将其组合成客户端所需的形式。(选择需要的功能的装饰器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流中的众多对象一样,都需要把另一个流对象注入
在调用其原有方法的基础上加入增强方法,完成装饰
把拓展功能从增加子类转移到添加包装器上