在拙文
《<让僵冷的翅膀飞起来>系列之一——从实例谈OOP、工厂模式和重构》中,
冰汽水提出了一个问题,“如果我想让RM, MPEG类具有自己的一些特定属性的话怎么做呢?”原来的RM和MPEG类继承了VideoMedia抽象类,而VideoMedia类又实现了 IMedia接口,该接口仅仅提供了Play()方法。冰汽水的意思是希望为RM,MPEG提供与AudioMedia不同的属性和方法。例如,对于视频媒体而言,应该有一个调整画面大小的方法,如Resize()。而这个方法是IMedia接口所不具备的。
那么怎样为RM,MPEG类提供IMedia接口所不具备的Resize()方法呢?非常自然地,通过这个问题我们就引出Adapter模式的命题了。首先,要假设一个情况,就是原文的所有代码,我们是无法改变的,这包括暴露的接口,类与接口的关系等等,都无法通过编码的方式实现新的目标。只有这样,引入Adapter模式才有意义。
熟悉Adapter模式的人都知道,Adapter模式分为两种:类的Adapter模式、对象的Adapter模式。下面我试图根据本例对两种方式进行说明及实现。在实现Adapter模式之前,有必要看看原来的类结构:
左边橙色的类为音频媒体类型,右边蓝色的类为视频媒体类型。所有的这些类型,包括类和接口都是无法改变的。现在我们的目的就是要让RM、MPEG具有Resize()方法。那么首先定义一个接口IVideoMedia,该接口具有Resize()方法。
下面我们就根据Adapter模式来实现需求。
一、 类的Adapter模式
既然要让RM、MPEG具有Resize()方法,最好的办法就是让它们直接实现IVedioScreen接口。然而受到条件的限制,这两个类类型是不可修改的。唯一可行的办法就是为相应的类新引入一个类类型,这就是Adapter模式中所谓的Adapter类了。它好比是一个转接头,通过它去实现 IVedioScreen接口,同时又令其继承原有的RM或MPEG类,以保留原有的行为。类图如下:
图中的类RMAdapter和MPEGAdapter就是通过Adapter模式获得的对象,它在保留了原有行为的同时,又拥有了IVedioScreen的功能。
代码如下:
public interface IVedioScreen
{
void Resize();
}
public class RMAdapter:RM,IVedioScreen
{
public void Resize()
{
MessageBox.Show("Change the RM screen's size.");
}
}
public class MPEGAdapter:MPEG,IVedioScreen
{
public void Resize()
{
MessageBox.Show("Change the MPEG screen's size.");
}
}
也许很多人已经注意到了,在使用这种方式建立Adapter时,存在一个局限,就是我们必须为每一个要包裹(Wrapping)的类,建立一个相应的 Adapter类。如上所述的RM对应RMAdapter,MPEG对应MPEGAdapter。必须如此,为什么呢?虽然RM和MPEG继承了同一个抽象类VedioMedia,但其Play()方法,可能是不相同的。此时,相对应的Adpater类只有直接继承该具体类,方才可以保留其原来的Play ()方法的行为本质。
OOP中很重要的思想是,尽量使用聚合而非继承。让我们换一种思路来考察Adapter模式。
二、 对象的Adapter模式
对象的Adapter模式,与类的Adapter模式,最大的区别就是不采用继承的方式,而是将要包裹的对象,以聚合的方式放到Adapter中,然后用委托的方式调用其对象的方法,实现类图如下:
比较两种实现方式的类图,可以得出两个结论:
1、 对象的Adapter模式,减少了对象的个数;
2、 耦合度更加松散;
代码如下:
public interface IVedioScreen
{
void Resize();
}
public class VedioAdapter:IVedioScreen
{
private vedioMedia _vedio;
public VedioAdapter(vedioMedia vedio)
{
_vedio = vedio;
}
public void Play()
{
_vedio.Play();
}
public void Resize()
{
if (_vedio is RM)
MessageBox.Show("Change the RM screen's size.");
else
MessageBox.Show("Change the MPEG screen's size.");
}
}
以这种方式形成的VedioAdapter,由于没有和RM、MPEG直接发生关系,并通过在构造函数传递参数的方式,等待客户端使用Adapter时,才将具体的VedioMedia对象传递给Adapter,显得耦合度更加松散,更加灵活。
我们来看客户端调用时,两者的区别:
1、 类的Adapter模式
public class Client
{
public static void Main()
{
RMAdapter rmAdapter = new RMAdapter();
MPEGAdapter mpegAdapter = new MPEGAdapter();
rmAdapter.Play();
rmAdapter.Resize();
mpegAdapter.Play();
mpegAdapter.Resize();
}
}
2、 对象的Adapter模式
public class Client
{
public static void Main()
{
VedioAdapter rmAdapter = new VedioAdapter(new RM());
VedioAdapter mpegAdapter = new VedioAdapter(new MPEG());
rmAdapter.Play();
rmAdapter.Resize();
mpegAdapter.Play();
mpegAdapter.Resize();
}
}
其实,对于对象的Adapter模式,还可以做一些改进,就是用属性或方法来取代构造函数传递被包裹对象的方式。代码修改如下:
public class VedioAdapter:IVedioScreen
{
private vedioMedia _vedio;
public VedioMedia Vedio
{
set {_vedio = value;}
}
……
}
这样,上面的客户端调用就更简单了:
public class Client
{
public static void Main()
{
VedioAdapter adapter = new VedioAdapter();
adapter.Vedio = new RM();
adapter.Play();
adapter.Resize();
adapter.Vedio = new MPEG();
adapter.Play();
adapter.Resize();
}
}
通过运用Adapter模式,扩展了新的接口,而原有的类型并不需要做任何改变,这就是Adapter模式的实质,也是为什么取名为Adapter 的原因之所在了。同时,我们要注意的是,在运用Adapter模式时,必须审时度势,根据具体的情况,抉择最优的方式,或者采用类的Adapter模式,或者采用对象的Adapter模式。决定权在与你,菜单给你送上来了,看看自己的腰包,想想点什么样的菜吧。
分享到:
相关推荐
《让僵冷的翅膀飞起来》系列之二——从实例谈Adapter模式 - Wayfarer's Prattle - 博客园
《让僵冷的翅膀飞起来》系列之一——从实例谈OOP、工厂模式和重构 - Wayfarer's Prattle - 博客园
《让僵冷的翅膀飞起来》系列之三——从Adapter模式到Decorator模式 - Wayfarer's Prattle - 博客园
《让僵冷的翅膀飞起来》系列之四——Visitor模式之可行与不可爱 - Wayfarer's Prattle - 博客园
我们需要给代码带去温暖的阳光,让僵冷的翅膀重新飞起来。结合实例,通过应用OOP、设计模式和重构,你会看到代码是怎样一步一步复活的。 为了更好的理解设计思想,实例尽可能简单化。但随着需求的增加,程序将...
上次上传出了些问题(9M的压缩包几天后突然....mht 面向对象经典语录.txt 设计模式迷你手册.chm 封装变化.doc 让僵冷的翅膀飞起来.rar C#设计模式随书源码.rar 用设计模式固化你的C#程序.doc <br>
【红包功能】打破工作沟通的僵冷局面,让企业氛围更有人情味,增强企业凝聚力; 【消息已读未读】私聊群聊消息发出均加标签,已读未读一目了然,沟通更高效; 【投票小应用】投票内容自定义,投票结果可见,发挥...
- 第一处引用,是为了展示山的描写可能带来的臃肿、崎岖、僵冷的意象,与海形成对比。 - 第二处引用,是为了突出海上的月出景象的妩媚和璀璨,进一步强化海的美。 10. 文章鼓励读者表达自己的喜好,并阐述理由,...