序:
在拙文《<让僵冷的翅膀飞起来>系列之一——从实例谈OOP、工厂模式和重构》中,冰汽水提出了一个问题,“如果我想让RM, MPEG类具有自己的一些特定属性的话怎么做呢?”原来的RM和MPEG类继承了VideoMedia抽象类,而VideoMedia类又实现了IMedia接口,该接口仅仅提供了Play()方法。冰汽水的意思是希望为RM,MPEG提供与AudioMedia不同的属性和方法。例如,对于视频媒体而言,应该有一个调整画面大小的方法,如Resize()。而这个方法是IMedia接口所不具备的。
那么怎样为RM,MPEG类提供IMedia接口所不具备的Resize()方法呢?非常自然地,通过这个问题我们就引出Adapter模式的命题了。首先,要假设一个情况,就是原文的所有代码,我们是无法改变的,这包括暴露的接口,类与接口的关系等等,都无法通过编码的方式实现新的目标。只有这样,引入Adapter模式才有意义。
类的Adapter模式
对象的Adapter模式
具体实现细节请参考原文
在随后的讨论中 大家主要的关注于最后的那个Adaptor是否应该实现IMedia接口.因为在原来的环境中我们一直使用IMedia来Play,那么为了不改动原有的代码,显然VedioAdaptor也应该实现IMedia接口,这样原来的client代码才不会发生太大的变动.
于是wayfarer给出了下面这个解决方案.
这样添加的VedioDecorator仍然可以使用IMedia来Play,似乎很好的解决了问题.其实此时的Adaptor模式已变为Proxy模式,只是在wayfarer的文中没有提到.
其后的Decorator模式已不是为了原来的目的,我在此也就不再叙述.
不识庐山真面目,只缘身在此山中
我们身在何处
到目前为止,你注意过我们是如何使用Adaptor对象的吗?
来看看wayfarer给出的代码:
1. 类的Adapter模式
{
public static void Main()
{
RMAdapter rmAdapter = new RMAdapter();
MPEGAdapter mpegAdapter = new MPEGAdapter();
rmAdapter.Play();
rmAdapter.Resize();
mpegAdapter.Play();
mpegAdapter.Resize();
}
}
2、 对象的Adapter模式
{
public static void Main()
{
VedioAdapter rmAdapter = new VedioAdapter(new RM());
VedioAdapter mpegAdapter = new VedioAdapter(new MPEG());
rmAdapter.Play();
rmAdapter.Resize();
mpegAdapter.Play();
mpegAdapter.Resize();
}
}
具体类看到了吗?在代码中不要出现具体类,这是我们的一个原则.
也许是因为wayfarer偷了懒 :P 也许是他没意识到这样做对我们所产生的影响.
但是在随后的文章中你会发现,这里的简单示例确实给我们带来了巨大的影响,以致走上一条错误的思维道路.
旁观者清,当局者迷
旁观者清
让我们仔细考虑一下该如何使用Adaptor对象,从而达到我们的目的,并遵守一系列在oo中应该遵守的原则.
我们的目的是让Vedio类型的Media增加实现Resize()的功能. 也就是增加VideoMedia类的实现接口, 加上IResizable接口.
那么我们调用的Resize功能的时候,显然应该使用IResizable接口来调用这个功能,而不是使用VedioResizeAdaptor来调用.(这里我为了让命名更加规范易懂对wayfarer的命名进行了修改: IVedioScreen à IResizable, VedioAdaptorà VedioResizeAdaptor)
也就是说客户端的代码应该变成下面这个样子:
{
public static void Main()
{
RMVedio rm = new RMVedio();
IMedia media=rm as IMedia;
IResizable resizer=rm.GetIResizableAdaptor();
media.Play();
resizer.Resize();
}
}
这时再让我们来想一想,此时rm.GetIResizableAdaptor()得到的VedioResizeAdaptor还需要实现IMedia接口吗?
当然不再需要,因为我们Play的时候一直是用的RM,Mpeg等等媒体类型,根本不会使用VedioAdaptor来Play. Adaptor的作用仅仅用于为Vedio增加Resize的功能. 而且在使用的过程中根本不会也不应该出现那些个XXXAdaptor,此过程对于用户应该是完全透明的.
走出山中,再回过头来看看,你发现了什么?
在wayfarer文中一系列的讨论都是围绕着一个根本不存在的问题, 你明白了吗?
所以此问题其实wayfarer早已给出答案:
往往梦里寻仙踪,如今不知何人采此景
走出庐山
上一节从一个旁观者的角度,让你一窥庐山的真貌. 就好比给了你一张地图, 然而如何从山中走出去就又是另一回事了.
IResizable resizer=rm.GetIResizableAdaptor();
为了实现GetIResizableAdaptor()
我们在VedioMedia类中添加方法如下:
public IResizable GetIResizableAdaptor()
{
return new VedioResizeAdaptor(this);
}
}
这是最后的解决之道吗?
如果又要实现新的功能怎么办?难道再添加新的GetXXXAdaptor() ? 看来这不是一个好的方法. 不如就实现一个GetAdaptor方法,然后通过给出不同的类型参数来获得不同的Adaptor.
也就是 Ixxx GetXXXAdaptor() à object GetAdaptor(Type t)
可以看出GetAdaptor是一个比较通用的方法,因为很多类都会想在将来实现新的接口,用于扩展.
所以将GetAdaptor方法抽出成为一个接口.IAdaptor其中就一个方法object GetAdaptor(Type t)
{
Object GetAdaptor(Type t);
}
VedioMedia类型不是想扩展实现新的功能(即实现新的接口)吗?
public object GetAdaptor(Type t)
{
If (t== typeof(IResizable))
return new VedioResizeAdaptor(this);
}
}
再想实现新的接口呢?
在这个函数中加入新的实现就可以了.
如 实现 CapturePicture功能
public object GetAdaptor(Type t)
{
if (t==typeof(IResizable))
return new VedioResizeAdaptor(this);
if(t==typeof(ICapturable))
return new VedioCapture(this);
}
}
好了,大功告成.
最后为了不再误入歧途,让我们看看最后的client代码
{
public static void Main()
{
RMVedio rm = new RMVedio();
IMedia media=rm as IMedia;
IResizable resizer= rm.GetAdaptor(typeof (IResizable)) as IResizable;
ICapturable capturer=rm.GetAdaptor(typeof(ICapturable)) as IResizable;
media.Play();
resizer.Resize();
capturer.Capture();
}
}
至此我们在领略了庐山的艰险之后,终于走出来了.
回头一瞥
回过头再看看,目前的解决方案是否就完美了呢?
如果要实现新的功能,我们必须添加新的Adaptor,也就是要不断的增加RMVedio类中GetAdaptor()方法的实现, 这显然是不太合理的.RMVedio类不应该负责这个.
根据责任分离的思想,很容易想到把责任分开,将GetAdaptor交给另一个类专门负责.
交给谁? Factory!
{
public object GetAdaptor(object o, Type adaptorType)
{
if (adaptorType==typeof(IResizable)
return new VedioResizeAdaptor ((VedioMedia)this);
}
}
如何使用?
IAdaptorFactory mediaAdaptorFactory;
public IAdaptorFactory MediaAdaptorFactory
{
set
{
mediaAdaptorFactory=value;
}
}
public object GetAdaptor(Type t)
{
mediaAdaptorFactory.GetAdaptor(this, t);
}
}
}
如果通过设值的方式为每个实现IMedia的类型注入mediaAdaptorFactory 显然你会烦不甚烦.
一次只出现一次,一个功能的代码只出现一次是我们追求的目标.如何实现?看看下面的方法.
IAdaptorFactory mediaAdaptorFactory= AdaptorManager.GetAdaptorFactory(typeof(IMedia));
public object GetAdaptor(Type t)
{
mediaAdaptorFactory.GetAdaptor(this, t)}
}
}
自然你会问AdaptorManager.GetAdaptorFactory是如何实现的.
很简单在AdaptorManager中有一个hashtable保存了相应类型的AdaptorFactory.
现在我们只要注入一次就可以了.
AdaptorManager.Register(factory,typeof(IMedia))
通过以上的介绍, 我们就可以很方便的加入为IMedia类型加入不同的Adaptor,以实现新的功能.
经过了这么久修改, 我们终于可以方便的为现有的类型添加新的行为,而又不影响原来的操作.
费了这么大的劲,为什么?
想必大家也看累了,下回分解吧.
参考资料: Contributing to Eclipse