您的位置:首页 > 编程语言

如何将多种设计模式结合使用(有原代码)

2006-03-09 17:30 911 查看
如何将多种设计模式结合使用
   作者:Breman Sinaga
phoenixbing 译自http://www.codeproject.com/csharp/sinagastorageexplorer.asp 2006.3.4




介绍

目 前已经有大部分关于“四人帮”设计模式的资料出版,然而大部分资料的例子都是按照某一种设计模式的方法设计单独完成的,而且这些例子的代码是不能和具有真 正功能的实用程序相提并论的。本文的资源管理器程序的例子是一个将很多设计模式结合起来使用开发的一个小程序。本文的主要目的是通过代码告诉读者设计模式 的使用方法。关于程序的介绍也是围绕设计模式展开的。
这 篇文章讨论了程序的结构,我应该使用哪几种设计模式?它们是怎么工作的以及使用它们的优点是什么?以我个人的观点设计模式是非常值得学习的。掌握设计模式 技能的开发者可以通过辨别使用的设计模式分析清楚程序的意图和目的。对于设计者和开发者而言设计模式应该成为一种思维模式。
程序用来统计机硬盘中的文件组成结构。使用到的设计模式有:
Strategy,Observer,Adapter,Template Method,Singleton and Wrapper Façade.前5个模式是“四人帮”设计模式中介绍过的,最后一种是在POSA中介绍的(POSA book volume-2)。
背景

产生设计这个程序的意图是我很迫切的需要一个类似Windows资源管理器的程序,它能告诉我在我的硬盘中文件容量的组成。我希望知道,比如 ,在Program Files 文件夹下空间是怎么使用的。包括它们的百分比。我也想知道我的d:盘中MP3文件与JPG文件哪个占用了更大的空间。我想看到的信息是这样的。



和这样



我需要通过一个图表的方式咱现这些信息这样帮助我更直观的了解这些数据信息。
特性

这个程序有如下的特性:

一个树形视图(treeview)包括目前文件系统的目录。选择后可以显示信息关于这个路径的文件结构。

       显示文件大小的信息以文件夹分组。

       显示文件大小信息以文件类型分组。

       信息以右侧面板中列表视图、饼图或柱图的形式展现出来

程序中使用的设计模式

程序中使用的设计模式如下图所示:



后文中给出每种模式的介绍。
策略模式(Strategy

意图:“定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。”(Gof)。



程序探索文件系统使用两个不同的算法,它们通过两个策略类实现:FolderStrategy和FileTypeStrategy(如上图)。FolderStrategy类实现了一种计算在某一路径下不同文件夹中文件容量总合的算法。FileTypeStrategy类实现了一种计算某一路径下具有相同类型的文件容量总和的算法。

现在假设我们想要添加一个新的算法。可以通过创建一个继承自ExporationStrategy类的子类并重新实现函数Explore()来实现。通过运行时创建新类的实例并将它动态的赋值给explorer对象。当我们调用explore.Explore()函数时合适的算法会运行(多态机制)。这就是使用策略模式的目的:封装它们并使它们可以切换。实现过程如下:

public abstract class ExplorationStrategy

{

  public virtual void Explore (...){}

}

 

public class NewAlgorithm : ExplorationStrategy

{

  public override void Explore (...)

  {

    // the new algorithm

  }

}

 

public class StorageExplorerForm : System.Windows.Forms.Form

{

  // Somewhere in the initialization section

  ExplorationStrategy explorer;

  ExplorationStrategy folderStrategy = new FolderStrategy ();

  ExplorationStrategy fileTypeStrategy = new FileTypeStrategy ();

  ExplorationStrategy newStrategy = new NewAlgorithm ();

 

  // Somewhere else, an algorithm is selected

  switch (...)

  {

    case 0: explorer = folderStrategy; break;

    case 1: explorer = fileTypeStrategy; break;

    case 2: explorer = newStrategy; break;

  }

 

  // Execute the algorithm

  explorer.Explore (...);

}   


观察者模式(Observer)

意图:“定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。”(GoF)



在程序中,这个模式在对象(具体的ExplorationStrategy对象)和观察者(具体的ExplorationObserver 对象)间创建了一种联系,这样当一个访问过程完成,被观察的对象通知观察者,这时观察者显示结果(如上图)。具体的对象(FolderStrategy 或 FileTypeStrategy)通知观察者(ListViewAdapter,PieChartAdapter 和 BarChartAdapter)通过调用OnFinish()函数,并且产生一个事件。这个事件被发送到观察者那里并被观察者处理。抽象类中定义具体的关系,并且让具体的类继承。

对象和观察者之间的关系是通过注册观察者的UpdateDisplay()函数到ExplorationStrategy.Finish事件完成的。如下:

public abstract class ExplorationObserver

{

  public void SubscribeToExplorationEvent (ExplorationStrategy obj)

  {

    obj.Finish += new ExplorationFinishEventHandler (UpdateDisplay);

  }

}

当程序初始化的时候(程序的窗体),具体的观察者对象调用SubscribeToExplorationEvent()函数并传递具体的策略作为参数。这时对象-观察者的关系被确定起来。如下:

// Initialization in the client:

// Create the concrete subject

ExplorationStrategy folderStrategy = new FolderStrategy ();

ExplorationStrategy fileTypeStrategy = new FileTypeStrategy ();

 

// Create the concrete observer

ExplorationObserver pieChart = new PieChartAdapter ();

 

// Subscribe the concrete observer object to the concrete strategy object

pieChart.SubscribeToExplorationEvent (folderStrategy);

pieChart.SubscribeToExplorationEvent (fileTypeStrategy);

现在让我们看一个下这种模式的动人之处。如果我们想改变程序去适应一个新的需求。我们除了将它显示在屏幕上外还还想保存访

问结果在一个文件中。为了实现这个目的,建立一个继承自Exploration Observer类的新类并且SubscribeToExplorationEvent()使用

ExplorationStrategy对象为参数。最后当程序运行时候,信息会发送到屏幕上并保存到文件中。代码如下:

public class NewConcreteObserver : ExplorationObserver

{

  public override void UpdateDisplay (object o, ExplorationFinishEventArgs e)

  {

    Hashtable result = e.ExplorationResult;

 

    // Write the result to a file

  } 

}

 

// Somewhere, during the initialization in the client

...

ExplorationObserver fileSaver = new NewConcreteObserver ();

fileSaver.SubscribeToExplorationEvent (folderStrategy);

fileSaver.SubscribeToExplorationEvent (fileTypeStrategy);

 

观察者模式是经常使用的在编写有事件驱动机制的程序时。这种观念经常被使用虽然程序员可能没意识到。然而认识它是非常重要的如果在我们把它和别的模式(比如适配器模式,下一章介绍)结合使用的情况下。

适配器模式(Adapter)

意图:“将一个类的接口转换成客户希望的另外一个接口。A d a p t e r模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。”(Gof)

现在事情是这样的:我想将访问结果显示在一个.NET列表视图(ListView类)里。然而当一个访问结束时我需要去修改ListView的能力,它必须能自动接收通知。这意味着我需要改变ListView类去响应ExploratrionObserver类的通知信号因此我可以覆盖当Finish事件发生时调用的UpdateDisplay()函数。不幸的是,我不能修改.NET的listView类。解决办法是创建一个新类ListViewAdapter,这个类拥有ListView的能力也能接收ExplorationObserver类的通知信号。现在适配器模式扮演了这个角色。ListViewAdapter类是一个适配器。在GoF条款中:ListViewAdapter类修改ListView类的接口从而拥有了ExplorationObserver的接口,这正是EplorationStrategy需要的。

适配器模式有两种形式:类适配器和对象适配器。类适配实现通过创建继承自ListView 和
ExplorationObserver类的ListViewAdapter。因为C#不支持多重继承所以这是行不通的。实现上如果ExplorationObserver是一个接口而不是抽象类那就有可能实现。ListViewAdapter这时实现接口并继承自类ListView。但是在本文的情况中ExplorationObserver已经在类中实现了,所以把它作为接口也不是可行的。

第二种形式,对象适配器,这个程序中就是使用的这种形式,使用对象组合。(如下图):



类ListViewAdapter继承自ExplorationObserver类。为了拥有ListView的能力,适配器创建了ListView对象并在需要时使用它。这样做的优点是ListView和它的成员被隐藏在ListViewAdapter下。代码如下:

public class ListViewAdapter : ExplorationObserver

{

  protected ListView listView;

 

  public ListViewAdapter()

  {

    // Create a ListView object and initialze it

    listView = new ListView();

    ...

  }

}

同样的方式被应用于PieChartAdapter类和BarCharAdapter类。假设我已经拥有了那些表格类并且我不想修改他们。解决的办法同样是创建一个继承自ExploratrionObserver类的适配器类并创建图表对象在这个适配器中(如下图:)



 

模板方法模式(Template Method)

意图:“定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Te m p l a t e M e t h o d使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。”(GoF)
模板方法本身是一个类中的方法,它是调用其它简单的方法,这样当子类覆盖简单的方法时,模板方法在衍生类中产生不同的结果。现在让我们看模板方法的实现在这个程序中(如下图:)。



模板方法在图表中的是OnPaint()函数。在OnPaint()函数中定义了一些画图表的步骤。这写步骤包括初始化图形对象,画图列和画图表。如果我想继承这个类去实现不同的图表像PieChart类和BarChart类,这时在OnPaint()函数中转换画图表的操作为DrawChart()函数调用。在Chart类中DrawChart()是一个空函数。在继承自Chart类的PieChart类中,DrawChart()实现了如何画饼图,同样在BarChart类中DrawChart()函数是画柱状图。我们能以同样的方法的去画图列,标题,和所有涉及画图表的处理过程。代码如下:

public abstract class Chart : Control

{

  // OnPaint is a template method. It contains steps

  // necessary to draw a complete chart

  public OnPaint ()

  {

    ...

    DrawLegend (...);

    DrawChart (...);

  }

 

  protected abstract void DrawChart (...)

  protected virtual void DrawLegend (...) { // Draw legend }

}

 

public abstract class PieChart : Chart

{

  protected virtual void DrawChart (...)

  {

    // Draw the pie chart image

  }

}

 

public abstract class BarChart : Chart

{

  protected virtual void DrawChart (...)

  {

    // Draw the bar chart image

  }

}

 

我使用同样的处理方法处理ListViewAdapter类。它有两个派生类,FoldListView类和FileTypeListView类。它们在显示信息时采用非常相似的步骤,除了一个设置图标的步骤。FolderListView设置一个图表表示文件夹FileTypeListView选择很多图片从ImageList.因为这点不同,这个步骤被顺延到派生类中了(如下图)



使 用模板方法的关键是定义一个算法的框架在基类中,这个框架越通用越好。顺延那些不同的步骤到派生类中。当然在基类中我们需要为顺延到派生类的步骤实现相同 默认的操作当派生类不需要覆盖这个步骤时。一旦我们拥有一个完整的模板和默认的实现,这时我们就能轻松的创建不同的派生类。

单件模式(Singleton)

意图:“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”(GoF)

在这个程序中有两个类只需要一个实例去运行:FileIcons类和IconReader类。



FileIcon类是用来从Windows得到并缓存界面图标的。这个类显示一个图标的集合在系统中。通过定义我知道它只需要一个实例在程序运行的过程中。IconReader类是用来从系统中得到图标但不存入缓存。它调用系统的API函数。我们也只需要一个这个类实例,所以我们使用单件模式来实现它。

MSDN提供一个怎样用C#实现单件模式的例子。在本文的这个程序中.NET单件模式是这样实现的:

sealed class FileIcons

{

  ...

 

  // By making it private, the class cannot be explicitly created

  private FileIcons () {}

 

  // This is the statement that ensures only one instance exists

  public static readonly FileIcons Instance = new FileIcons();

 

  public int GetIconIndex (...)

  {

    ...

  }

}

去使用FileIcons类,我们仅需使用FileIcons.Instance属性不需要创建这个类,如下:

int iconIndex = FileIcons.Instance.GetIconIndex (...);

Wrapper Facade模式不是GoF中提出的,所以我没看过不敢妄自翻译,以下原文附上

Wrapper Façade

Intent: “ Encapsulate the functions and data provided by existing non-object oriented APIs within more concise, robust, portable, maintainable, and cohesive object-oriented class interfaces” (POSA volume 2).



To access the shell icons from the windows operating system we must call the Shell API and Win API functions. The function call involves strict usage of data structure and complex parameter. To encapsulate that complexity I created an
IconReader
class. POSA says this is different from Facade pattern since the Facade encapsulates relationship complexity among classes, while the Wrapper Facade encapsulates non OO functions. Also this pattern is more specific in its purpose. For example, the Shell API function that is used to retrieve icon from the Windows Shell is the
SHGetFileInfo()
function. This function can be used to do a lot of job, not only for icon retrieval. Because the
IconReader
class is specific for Icon related jobs then I create only methods specific for it, they are
GetSmallFileTypeIcon()
method (to retrieve icon representing file extension) and
GetClosedFolderIcon()
(to retrieve icon representing a closed folder). The icon retrieval also needs to call Win32 API
DestroyIcon()
function. The idea behind this pattern is: don't look at to the complexity behind the process, as long as it is for delivering robust and cohesive functionalities, then use all the resources needed, and present only the cohesive interface to the class user.

 

 我发现此文后,一直想翻译,但时间很紧张。也只能乱翻了,难免有错误。就当是抛砖引玉吧。如果英语好的可以去阅读原文,呵呵。希望大家能给我提些修改意见。另原文的连接中有本文的原代码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐