不一样的装饰器模式(设计模式二)

前言

我是一名博客园的博主,目前排名第199675位,虽然排名稍稍落后,但这并不影响我向大家学习然后自己吹水。

在常见的设计模式中,每个项目或者说产品可以说装饰器几乎必用。

装饰器是decorator,别名wrapper,也称为包装器,是创建型、结构型、行为型分类的结构型,具体有什么用呢?

说有什么用,不如直接看下,通过使用装饰器有什么结构变化,是如何演化过来的。

通过演化过程,自然知道为什么装饰器划分为结构型,装饰器解决了什么问题,作用是啥。

上车出发

在流中,有文件流、内存流以及网络流,他们的读取方式都不一样,但是它们都继承steam 这个类。
像这样:

public abstract class Stream
{
	protected abstract char Read(int number);

	protected abstract void seek(int position = 0);

	protected virtual void write(byte data) { }
}

假设让我们来分别设计文件流、内存流与网络流
文件流:

public class FileStream: Stream
{

	public virtual void write(byte data) {
		// 写入文件
	}

	public override char Read(int number)
	{
		throw new NotImplementedException();
	}

	public override void seek(int position = 0)
	{
		throw new NotImplementedException();
	}
}

内存流:

public class MemoryStream:Stream
{

	public virtual void write(byte data)
	{
		// 写内存流
	}

	public override char Read(int number)
	{
		throw new NotImplementedException();
	}

	public override void seek(int position = 0)
	{
		throw new NotImplementedException();
	}
}

网络流:

public class NetworkStream: Stream
{

	public virtual void write(byte data)
	{
		// 写网络流
	}

	public override char Read(int number)
	{
		throw new NotImplementedException();
	}

	public override void seek(int position = 0)
	{
		throw new NotImplementedException();
	}
}

我们在读取或者写入这些流的时候,可能会进行加密或者说缓存。
第一版设计,假设我们以继承的方式实现。
加密的文件流:

public class CryptoBufferedFileStream: FileStream
{
	public override  char Read(int number)
	{
		base.Read(number);
		//额外的加密操作
		throw new NotImplementedException();
	}
	public override void seek(int position)
	{
		//额外的加密操作
		base.seek(position);
	}
	public override void write(byte data)
	{
		//额外的加密操作...
		base.write(data);
	}
}

加密的内存流:

public class CryptoMemoryStream : MemoryStream
{
	public override char Read(int number)
	{
		base.Read(number);
		//额外的加密操作
		throw new NotImplementedException();
	}
	public override void seek(int position)
	{
		//额外的加密操作
		base.seek(position);
	}
	public override void write(byte data)
	{
		//额外的加密操作...
		base.write(data);
	}
}

加密的网络流

public class CryptoNetworkStream : NetWorkStream
{
	public override char Read(int number)
	{
		base.Read(number);
		//额外的加密操作
		throw new NotImplementedException();
	}
	public override void seek(int position)
	{
		//额外的加密操作
		base.seek(position);
	}
	public override void write(byte data)
	{
		//额外的加密操作...
		base.write(data);
	}
}

同样我们还要实现缓存流:

//文件流缓冲
public class BufferedFileStream: FileStream
{
	public override char Read(int number)
	{
		base.Read(number);
		//额外的缓冲操作
		throw new NotImplementedException();
	}
	public override void seek(int position)
	{
		//额外的缓冲操作
		base.seek(position);
	}
	public override void write(byte data)
	{
		//额外的缓冲操作...
		base.write(data);
	}
}
//内存流缓冲
public class BufferedMemoryStream : MemoryStream
{
	public override char Read(int number)
	{
		base.Read(number);
		//额外的缓冲操作
		throw new NotImplementedException();
	}
	public override void seek(int position)
	{
		//额外的缓冲操作
		base.seek(position);
	}
	public override void write(byte data)
	{
		//额外的缓冲操作...
		base.write(data);
	}
}
//网络流缓冲
public class BufferedNetworkStream : NetWorkStream
{
	public override char Read(int number)
	{
		base.Read(number);
		//额外的缓冲操作
		throw new NotImplementedException();
	}
	public override void seek(int position)
	{
		//额外的缓冲操作
		base.seek(position);
	}
	public override void write(byte data)
	{
		//额外的缓冲操作...
		base.write(data);
	}
}

写完之后,会发现几个致命性问题:

1.没有实现我们要加密也要缓存,如果要写下去那么还需要3个类,感觉没有尽头。

假设steam 有n 个继承基础类,如FileSteam,需要实现m个扩展功能,比如说加密缓冲等。
那么计算是n(C(m,1),c(m,2)....,C(m,m))
计算C(m,1),c(m,2)....,C(m,m)=2^m-1
答案是n(2^m-1)
不好意思,我的数学不是很好,如果有错误,望请指正。
2.无论加密还是缓存都是操作byte,而且操作都是read、write与seek,感觉有太多相似性代码了。

根据这两点我们改一下。
加密流:

public class CryptoBufferedStream:Stream
{
	Stream stream;

	public CryptoBufferedStream(Stream stream)
	{
		this.stream = stream;
	}

	public override char Read(int number)
	{
		stream.Read(number);
		//额外的加密操作
		throw new NotImplementedException();
	}
	public override void seek(int position)
	{
		//额外的加密操作
		stream.seek(position);
	}
	public override void write(byte data)
	{
		//额外的加密操作...
		stream.write(data);
	}
}

缓冲流:

public class BufferedMemoryStream : Stream
{
	Stream stream;

	public BufferedMemoryStream(Stream stream) {
		this.stream = stream;
	}

	public override char Read(int number)
	{
		stream.Read(number);
		//额外的缓冲操作
		throw new NotImplementedException();
	}
	public override void seek(int position)
	{
		//额外的缓冲操作
		stream.seek(position);
	}
	public override void write(byte data)
	{
		//额外的缓冲操作...
		stream.write(data);
	}
}

这样似乎就看起来舒服很多了,这里可能会有一个疑惑性的问题,
为什么还要继承Stream?
装饰器也叫包装器,我们使用CryptoBufferedStream把文件流包装成了加密文件流,包装完也是必须是流啊,打包完别忘了打标签哦。

调用示例:

static void Main(string[] args)
{
	FileStream fileStream = new FileStream();
	BufferedMemoryStream bufferedMemoryStream = new BufferedMemoryStream(fileStream);
}

这样就把fileStream给包装成了bufferedMemoryStream,后面我们使用的就是BufferedMemoryStream。
这样我们没有失去fileSteam 是流的特性,而且还要另外一个好处,既然还是流,那么我们还可以继续包装啊。

CryptoBufferedStream cryptoBufferedStream = new CryptoBufferedStream(bufferedMemoryStream);

这时候我们即加密了,也缓冲了,实现了我们原来没有写的3个类。
这里发现一个问题,那就是stream这个属性在CryptoBufferedStream 和 BufferedMemoryStream 都存在。
其实提取和不提取都没有关系,但是对我们写代码的人来说,每次都要去写个可以提取出来的字段,是相当不舒服的,而且还不开心。
终版:
将stream属性提取出来,提取到DecoratorStream中。

public abstract class DecoratorStream: Stream
{
	protected Stream stream;
	public DecoratorStream(Stream stream){
		this.stream = stream;
	}
}

public class CryptoBufferedStream: DecoratorStream
{

	public CryptoBufferedStream(Stream stream):base(stream)
	{
	}

	public override char Read(int number)
	{
		stream.Read(number);
		//额外的加密操作
		throw new NotImplementedException();
	}
	public override  void seek(int position)
	{
		//额外的加密操作
		stream.seek(position);
	}
	public override void write(byte data)
	{
		//额外的加密操作...
		stream.write(data);
	}
}

public class BufferedMemoryStream : DecoratorStream
{

	public BufferedMemoryStream(Stream stream):base(stream) {

	}

	public override char Read(int number)
	{
		stream.Read(number);
		//额外的缓冲操作
		throw new NotImplementedException();
	}
	public override void seek(int position)
	{
		//额外的缓冲操作
		stream.seek(position);
	}
	public override void write(byte data)
	{
		//额外的缓冲操作...
		stream.write(data);
	}
}

看起来就舒服很多了。
为什么看起来会舒服,一张图解释出来。

假设steam 有n 个继承基础类,如FileSteam,需要实现m个扩展功能,比如说加密缓冲等。按照装饰器的计算方式是:m+1
n(2^m-1)与m+1 两者的方式对比显而易见啊,现在可以证明为什么装饰器是一个结构模式了吧。
至于作用,可以看出,可以解决继承导致的子类膨胀问题。

uml图

后面补上,画图累啊。

总结

因为n(2m-1)在n>1或者m大于1的情况下,n(2m-1)=>m+1,且仅当m=2时候两者相等,所以作用为:可以解决继承导致的子类膨胀问题,膨胀问题属于结构问题,解决的是结构问题,所以归属为结构型。

posted @ 2019-12-23 09:40  敖毛毛  阅读(319)  评论(0编辑  收藏  举报