享受代码,享受人生

SOA is an integration solution. SOA is message oriented first.
The Key character of SOA is loosely coupled. SOA is enriched
by creating composite apps.
posts - 213, comments - 2315, trackbacks - 162, articles - 45
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

《让僵冷的翅膀飞起来》 外传

Posted on 2005-03-06 16:44  idior  阅读(...)  评论(...编辑  收藏

 

:

在拙文<让僵冷的翅膀飞起来>系列之一——从实例谈OOP、工厂模式和重构》中,冰汽水提出了一个问题,如果我想让RM, MPEG类具有自己的一些特定属性的话怎么做呢?原来的RMMPEG类继承了VideoMedia抽象类,而VideoMedia类又实现了IMedia接口,该接口仅仅提供了Play()方法。冰汽水的意思是希望为RMMPEG提供与AudioMedia不同的属性和方法。例如,对于视频媒体而言,应该有一个调整画面大小的方法,如Resize()。而这个方法是IMedia接口所不具备的。

那么怎样为RMMPEG类提供IMedia接口所不具备的Resize()方法呢?非常自然地,通过这个问题我们就引出Adapter模式的命题了。首先,要假设一个情况,就是原文的所有代码,我们是无法改变的,这包括暴露的接口,类与接口的关系等等,都无法通过编码的方式实现新的目标。只有这样,引入Adapter模式才有意义。

类的Adapter模式

 

对象的Adapter模式

 

具体实现细节请参考原文

在随后的讨论中 大家主要的关注于最后的那个Adaptor是否应该实现IMedia接口.因为在原来的环境中我们一直使用IMediaPlay,那么为了不改动原有的代码,显然VedioAdaptor也应该实现IMedia接口,这样原来的client代码才不会发生太大的变动.

于是wayfarer给出了下面这个解决方案.

 

这样添加的VedioDecorator仍然可以使用IMediaPlay,似乎很好的解决了问题.其实此时的Adaptor模式已变为Proxy模式,只是在wayfarer的文中没有提到.

其后的Decorator模式已不是为了原来的目的,我在此也就不再叙述.

 

不识庐山真面目,只缘身在此山中

我们身在何处

到目前为止,你注意过我们是如何使用Adaptor对象的吗?

来看看wayfarer给出的代码:

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(); 
 }
 
}
 

 

具体类看到了吗?在代码中不要出现具体类,这是我们的一个原则.

也许是因为wayfarer偷了懒 :P 也许是他没意识到这样做对我们所产生的影响.

 

但是在随后的文章中你会发现,这里的简单示例确实给我们带来了巨大的影响,以致走上一条错误的思维道路.

 

旁观者清,当局者迷

旁观者清

 

让我们仔细考虑一下该如何使用Adaptor对象,从而达到我们的目的,并遵守一系列在oo中应该遵守的原则.

我们的目的是让Vedio类型的Media增加实现Resize()的功能. 也就是增加VideoMedia类的实现接口, 加上IResizable接口.

那么我们调用的Resize功能的时候,显然应该使用IResizable接口来调用这个功能,而不是使用VedioResizeAdaptor来调用.(这里我为了让命名更加规范易懂对wayfarer的命名进行了修改: IVedioScreen à IResizable, VedioAdaptorà VedioResizeAdaptor)

也就是说客户端的代码应该变成下面这个样子:

 

public class Client 



 
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等等媒体类型,根本不会使用VedioAdaptorPlay.   Adaptor的作用仅仅用于为Vedio增加Resize的功能. 而且在使用的过程中根本不会也不应该出现那些个XXXAdaptor,此过程对于用户应该是完全透明的.

 

走出山中,再回过头来看看,你发现了什么?

wayfarer文中一系列的讨论都是围绕着一个根本不存在的问题, 你明白了吗?

 

所以此问题其实wayfarer早已给出答案:

 

往往梦里寻仙踪,如今不知何人采此景

走出庐山

 

上一节从一个旁观者的角度,让你一窥庐山的真貌. 就好比给了你一张地图, 然而如何从山中走出去就又是另一回事了.

IResizable resizer=rm.GetIResizableAdaptor();

为了实现GetIResizableAdaptor()

我们在VedioMedia类中添加方法如下:

 

class VedioMedia: IMedia {…
    
public IResizable GetIResizableAdaptor()
{
    
return new VedioResizeAdaptor(this);
}

}

这是最后的解决之道吗?

如果又要实现新的功能怎么办?难道再添加新的GetXXXAdaptor() ? 看来这不是一个好的方法. 不如就实现一个GetAdaptor方法,然后通过给出不同的类型参数来获得不同的Adaptor.

也就是 Ixxx GetXXXAdaptor() à object GetAdaptor(Type t)

 

可以看出GetAdaptor是一个比较通用的方法,因为很多类都会想在将来实现新的接口,用于扩展.

所以将GetAdaptor方法抽出成为一个接口.IAdaptor其中就一个方法object GetAdaptor(Type t)

 

public interface IAdaptor 

  Object GetAdaptor(Type t); 
}
 

 

VedioMedia类型不是想扩展实现新的功能(即实现新的接口)?

class VedioMedia : IMedia,IAdaptor {… 
    
public object GetAdaptor(Type t) 
   

    If (t
== typeof(IResizable)) 
       
return new VedioResizeAdaptor(this); 
   }
 
}
 

 

再想实现新的接口呢?

在这个函数中加入新的实现就可以了.

实现 CapturePicture功能

class VedioMedia : IMedia,IAdaptor {… 
    
public object GetAdaptor(Type t) 
   

      
if (t==typeof(IResizable)) 
         
return new VedioResizeAdaptor(this); 
      
if(t==typeof(ICapturable)) 
         
return new  VedioCapture(this); 
   }
 
}

 

好了,大功告成.

最后为了不再误入歧途,让我们看看最后的client代码

public class 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!

class MediaAdaptorFactory
{
   
public object GetAdaptor(object o, Type adaptorType)
   
{
    
if (adaptorType==typeof(IResizable)
          
return new VedioResizeAdaptor  ((VedioMedia)this);
   }

}


如何使用?

class VedioMedia :IMedia, IAdaptor {…

   IAdaptorFactory mediaAdaptorFactory;

   
public IAdaptorFactory MediaAdaptorFactory
   
{
      
set
      
{
         mediaAdaptorFactory
=value;
      }

   }


   
public object GetAdaptor(Type t)
   
{
      mediaAdaptorFactory.GetAdaptor(
this, t);
   }

}


}

 

如果通过设值的方式为每个实现IMedia的类型注入mediaAdaptorFactory 显然你会烦不甚烦.
一次只出现一次,一个功能的代码只出现一次是我们追求的目标.如何实现?看看下面的方法.

 

class VedioMedia :IMedia, IAdaptor {…
  
    IAdaptorFactory mediaAdaptorFactory
=    AdaptorManager.GetAdaptorFactory(typeof(IMedia));

    
public object GetAdaptor(Type t)
    
{
       mediaAdaptorFactory.GetAdaptor(
this, t)}

    }

}

 

自然你会问AdaptorManager.GetAdaptorFactory是如何实现的.

很简单在AdaptorManager中有一个hashtable保存了相应类型的AdaptorFactory.

现在我们只要注入一次就可以了.

IAdaptorFactory factory=new MediaAdaptorFactory();

AdaptorManager.Register(factory,
typeof(IMedia))

 

通过以上的介绍, 我们就可以很方便的加入为IMedia类型加入不同的Adaptor,以实现新的功能.

 

经过了这么久修改, 我们终于可以方便的为现有的类型添加新的行为,而又不影响原来的操作.

费了这么大的劲,为什么?

 

想必大家也看累了,下回分解吧.


参考资料: Contributing to Eclipse