装饰模式
装饰模式:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
结构图
在软件开发过程中,可能经常会根据需要修改扩展对象的功能,如果不采用装饰模式的话,可能会使开发变得非常混乱并且不便于管理。我们先来看一个实例。
有个客户需要开发一个音乐的播放器,于是我们开发出了如下的代码:
public class MusicPlayer
{
public void Play(string userFilePath)
{
if(userFilePath 是音乐文件)
{
..........................
开始播放音乐
..........................
}
}
}
public class Program
{
public static void Main(string[] args)
{
MusicPlayer player = new MusicPlayer();
player.Play(userFile);
}
}
过了半年,程序运行的很稳定,有一天客户需要这个播放器能够放播放视频,为了满足客户的需求,我们开始修改代码。基于开发封闭原则,我们添加一个新的类
public class FilePlayer
{
public void Play(string userFilePath)
{
if(userFile 是音乐文件)
{
..........................
开始播放音乐
..........................
}
else if(userFile 是视频文件)
{
..........................
开始播放视频
}
}
}
过了一年,程序受到了客户的好评,于是很多客户都要求购买,但又都提出了一些各自的要求,客户A要求播放器需要能够播放音乐,视频,还有flash,客户B则只要求能够播放视频和flash,客户C则只要求能够播放音乐和flash,客户D要求只能播放视频......。现在我们该如何开发呢?
如果我们采用继承类或添加新类的方式开发,类会越来越多,而且不好管理。如果了解了装饰模式,那就变的简单了。采用装饰模式代码如下:
///<summary>
/// 简单播放器的统一接口
/// </summary>
public interface IPlayer
{
void Play(string userFilePath);
}
///<summary>
/// 装饰器的抽象基类
/// </summary>
public abstract class DecoratorPlayer : IPlayer
{
protected IPlayer _player = null;
public DecoratorPlayer(IPlayer player)
{
this._player = player;
}
public virtual void Play(string userFilePath)
{
if (this._player != null)
{
this._player.Play(userFilePath);
}
}
}
///<summary>
/// 音乐播放器
///</summary>
public class MusicPlayer : DecoratorPlayer
{
public MusicPlayer(IPlayer play)
: base(play)
{
}
public override void Play(string userFilePath)
{
if (false)
{
//播放音乐......
}
else
{
base.Play(userFilePath);
}
}
}
///<summary>
/// 视频播放器
/// ///</summary>
public class VedioPlayer : DecoratorPlayer
{
public VedioPlayer(IPlayer play)
: base(play)
{
}
public override void Play(string userFilePath)
{
if (false)
{
//播放视频
}
else
{
base.Play(userFilePath);
}
}
}
///<summary>
/// Flash播放器
///</summary>
public class FlashPlayer : DecoratorPlayer
{
public FlashPlayer(IPlayer play)
: base(play)
{
}
public override void Play(string userFilePath)
{
if (false)
{
//播放Flash
}
else
{
base.Play(userFilePath);
}
}
}
如果采用了装饰模式,我们可以对功能进行任意的组合,列如:
1.只要求能够播放音乐和flash
static void Main()
{
MusicPlayer play1 = new MusicPlayer(null);
VedioPlayer play2 = new VedioPlayer(play1);
play2.Play("");
}
2.需要能够播放音乐,视频,还有flash
static void Main()
{
MusicPlayer play1 = new MusicPlayer(null);
VedioPlayer play2 = new VedioPlayer(play1);
FlashPlayer play3 = new FlashPlayer(play2);
play3.Play("");
}
另外还可以在配置文件中配置对象来完成的装饰步骤,通过公共的配置读取类,通过字典缓存装饰的步骤,然后传入普通的对象,返回装饰后的对象。
配置文件的格式:
<?xml version="1.0" encoding="utf-8" ?>
<Decorator>
<Section>
<Class name="" type="">
<Step name="" type=""/>
<Step name="" type=""/>
<Step name="" type=""/>
</Class>
<Class name="" type="">
<Step name="" type=""/>
<Step name="" type=""/>
<Step name="" type=""/>
</Class>
<Class name="" type="">
<Step name="" type=""/>
<Step name="" type=""/>
<Step name="" type=""/>
</Class>
</Section>
</Decorator>
装饰器步骤构造器代码:
Dictionary<Type, List<IPlayer>> steps = new Dictionary<Type, List<IPlayer>>();
List<IPlayer> list = steps[typeof(T)];
foreach (IPlayer item in list)
{
play = (T)Activator.CreateInstance(item.GetType(), play);
}
return play;
适用性
在以下情况下应当使用装饰模式:
1.需要扩展一个类的功能,或给一个类增加附加责任。
2.需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
3.需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
总结
Decorator模式采用对象组合而非继承的手法,实现了在运行时动态的扩展对象功能的能力,而且可以根据需要扩展多个功能,避免了单独使用继承带来的“灵活性差”和“多子类衍生问题”。同时它很好地符合面向对象设计原则中“优先使用对象组合而非继承”和“开放-封闭”原则。