不一样的装饰器模式(设计模式二)
前言
我是一名博客园的博主,目前排名第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时候两者相等,所以作用为:可以解决继承导致的子类膨胀问题,膨胀问题属于结构问题,解决的是结构问题,所以归属为结构型。
                    
                
                
            
        
浙公网安备 33010602011771号