(转).NET中的设计模式

.NET中的设计模式一:工厂模式 选择自 lane_cn 的 Blog
作者:      来源:http://www.qqhit.com/     时间:2006-4-27 11:52:20

 

 

设计模式概述

设计模式从本质上说是一种规则,从形式上说,分为创建型、结构型、行为型。

设计模式的应用是为了实现软件设计中的几个原则,其中一个重要原则是:减少模块之间的耦合程度。为了确保这个目的,在设计一个类时,要针对接口,而非实现。(Programming to an Interface, not an Implementation)设计的时候只关心类的接口,编程的时候可以先实现一个简单的接口,供别的模块调用。使用一个类的时候只对接口工作,不关心具体的实现,也不关心具体的类型。这样也符合人类认识世界的规律,一般说来人们总是先了解一个事情的大概情况,比如,我们先了解一台电视机的大概功能,然后才能了解每个功能具体是怎样实现的。

开始的时候不提供实现,正是为了以后能够最大限度的实现。

设计模式不受语言的限制,使用.net或者java更容易实现。

工厂模式(Factory)

工厂模式属于一种创建型模式(Creational)。同样属于创建型模式的还有单件模式(Singleton),以后有机会再说。

    工厂模式的要点:

    1:存在一个创建对象的工厂;

    2:调用者从工厂中取得某些对象;

    3:由工厂决定如何创建对象;

    4:客户不知道对象是如何生成的。

举一个例子,下面的类视图:



Namer对象是FirstFirst和LastFirst的基类,用户调用Namer类的时候,不直接new出Namer类或者他的子类,而是使用NameFactory的getName方法得到具体的对象。这样在用户就不用关心自己正在使用的是哪一个Namer,正在调用哪一个方法。用户只针对Namer进行工作,而不用关心具体的类型。在实际工程中可以将Namer类的子类的构造函数只对NameFactory开放,进一步限制程序员。

C#代码如下:

1:Namer的实现

using System;

 

namespace NameFactory

{

    /// <summary>

    /// Summary description for Namer.

    /// </summary>

    //Base class for getting split names

    public class Namer {

        //parts stored here

        protected string frName, lName;

 

        //return first name

        public string getFrname(){

            return frName;

        }

        //return last name

        public string getLname() {

            return lName;

        }

    }

}

 

2:FirstFirst类的实现

using System;

namespace NameFactory

{

    /// <summary>

    /// Summary description for FirstFirst.

    /// </summary>

    public class FirstFirst : Namer

    {

        public FirstFirst(string name)

        {

            int i = name.IndexOf (" ");

            if(i > 0) {

                frName = name.Substring (0, i).Trim ();

                lName = name.Substring (i + 1).Trim ();

            }

            else {

                lName = name;

                frName = "";

            }

        }

    }

}

 

3:LastFirst类的实现

using System;

namespace NameFactory

{

    /// <summary>

    /// Summary description for LastFirst.

    /// </summary>

    public class LastFirst : Namer

    {

        public LastFirst(string name) {

            int i = name.IndexOf (",");

            if(i > 0) {

                lName = name.Substring (0, i);

                frName = name.Substring (i + 1).Trim ();

            }

            else {

                lName = name;

                frName = "";

            }

        }

    }

}

 

4:NameFactory,工厂的实现

using System;

namespace NameFactory

{

    /// <summary>

    /// Summary description for NameFactory.

    /// </summary>

    public class NameFactory {

        public NameFactory() {}

        public Namer getName(string name) {

            int i = name.IndexOf (",");

            if(i > 0)

                return new LastFirst (name);

            else

                return new FirstFirst (name);

        }

    }

}

 

5:调用者,一个窗体,如图:

按钮的响应事件代码如下:

private void btCompute_Click(object sender, System.EventArgs e) {

    Namer nm = nameFact.getName (txName.Text );

    txFirst.Text = nm.getFrname ();

    txLast.Text = nm.getLname ();

}

 

程序并不复杂,运行不运行无所谓,关键是要搞清楚Factory所起的作用:他隐藏了Namer类的创建细节,调用者始终不知道他创建的是哪一个类,也不需要关心他调用的方法是哪一个子类的。如果以后的程序的需求发生改动,比如:某国的人不仅有First Name和Last Name,还有Mid Name,就可以很方便的进行扩充,添加一个Namer的子类,修改一下Factory。调用者甚至不知道有新加了一种Namer的类型。

 

实际的应用

 

举一个简单的例子:工程中需要使用多种数据库,Oracle、SQL Server、Sybase,为这些数据库建立了各自的连接和查询操作的类,这些类有一个共同的基类BaseConn。可以建立一个ConnFactory类,用于根据不同情况产生具体的类。调用者不必关心自己调用的是谁。大大简化了业务代码。

 

实际的使用例子还有很多。


.NET中的设计模式二:单件模式 选择自 lane_cn 的 Blog
作者:      来源:http://www.qqhit.com/     时间:2006-4-27 11:52:21

 

 

 

单件模式(Singleton)是一个非常简单的模式,这是我第一个理解并且能运用的模式。有时候模式的复杂程度并不在于本身,而是由于他的应用目的。最初的时候面对一个模式经常充满了困惑,一个简单的调用为什么要搞的如此复杂?为什么要建立这么多类,只是为了打开一个文件。

通常说来学习一个模式是一个接受、认可、领会的过程。接受:了解模式的结构,了解实例的意义;认可:认可该模式在实际工程中的作用和可行性;领会:将模式应用到开发过程中。

而模式的应用目的说到底无非是为了降低模块之间在时间和空间上的耦合程度,以此提高模块的复用性,降低出错的概率。

单件模式

Singleton模式是一个较为简单的模式,下面的代码就可以建立一个Singlton模式的例子,这是一个写系统日志的类。

public class LogWriter

{

    //申明一个静态的变量,类型为类本身

    private static LogWriter _instance = null;

 

    //将类的构造函数私有化,使得这个类不可以被外界创建

    private LogWriter()

    {

    }

 

    //提供静态的方法,创建类的实例

    public static LogWriter GetInstance()

    {

        if (_instance == null)

        {

            _instance = new LogWriter();

        }

        return _instance;

    }

 

    //下面实现类的其他功能

    //....

}

调用者不可以通过new的形式得到Singleton的实例,调用的例子如下:

LogWriter log = LogWriter.GetInstance();

log.Debug("hello world");

 

实际应用

很容易想象,单件模式适用于下面的情况:整个系统中某个对象是唯一的,或者是有固定数量的。比如数据库连接,配置文件,等等……

模式是一种程序员间的约定术语,语言可以成为思维的基础。有了这样的语言,一些复杂的概念就变得容易交流了。比如设计人员只要说到,某个类是一个单件模式,是一个Singleton,程序员听见以后就至少应该明白这个类的调用方式了,而不用再详细的说明。


.NET中的设计模式三:组合模式
作者:      来源:http://www.qqhit.com/     时间:2006-4-27 12:11:19

 

 

 组合模式(Composite)是一种“结构型”模式(Structural)。结构型模式涉及的对象为两个或两个以上,表示对象之间的活动,与对象的结构有关。

先举一个组合模式的小小例子:

如图:系统中有两种Box:Game Box和Internet Box,客户需要了解者两个类的接口分别进行调用。为了简化客户的工作,创建了XBox类,程序代码如下:

GameBox的代码:

    public class GameBox

    {

        public void PlayGame()

        {

            Console.WriteLine("plaly game");

        }

     }

InternetBox的代码:

    public class InternetBox

    {

        public void ConnectToInternet()

        {          

            Console.WriteLine("connect to internet");

        }

        public void GetMail()

        {          

            Console.WriteLine("check email");

        }

    }

XBox的代码:

    public class XBox

    {

        private GameBox mGameBox=null;

        private InternetBox mInternetBox=null;

 

        public XBox()

        {

            mGameBox = new GameBox();

            mInternetBox = new InternetBox();

        }

        public void PlayGame()

        {

            mGameBox.PlayGame();

        }

        public void ConnectToInternet()

        {

            mInternetBox.ConnectToInternet();

        }

        public void GetMail()

        {

            mInternetBox.GetMail();

        }      

    }

XBox中封装了GameBox和InternetBox的方法,这样,用户面对的情况就大大的简化了,调用的代码如下:

    public class CSComposite

    {

        static void Main (string[] args)

        {

            XBox x = new XBox();

            Console.WriteLine("PlayGame!");

            x.PlayGame();

            Console.WriteLine();

 

            Console.WriteLine("Internet Play Game!");

            x.ConnectToInternet();

            x.PlayGame();

            Console.WriteLine();

 

            Console.WriteLine("E-Mail!");

            x.GetMail();

        }      

    }

可以看见,用户只需要了解XBox的接口就可以了。

组合模式的应用例子

组合模式适用于下面这样的情况:两个或者多个类有相似的形式,或者共同代表某个完整的概念,外界的用户也希望他们合而为一,就可以把这几个类“组合”起来,成为一个新的类,用户只需要调用这个新的类就可以了。

下面举一个例子说明Composite模式的一个实际应用。下面的Class视图:

Employee类是AbstractEmployee接口的一个实现,Boss类是Employee的一个子类,EmpNode是从树视图的TreeNode类继承而来的。我们先看看代码:

AbstractEmployee,这是一个接口,提供下列方法:

public interface AbstractEmployee {

    float getSalary(); //get current salary

    string getName(); //get name

    bool isLeaf(); //true if leaf

    void add(string nm, float salary); //add subordinate

    void add(AbstractEmployee emp); //add subordinate

    IEnumerator getSubordinates(); //get subordinates

    AbstractEmployee getChild(); //get child

    float getSalaries(); //get salaries of all

}

Employee类是AbstractEmployee接口的一个实现

public class Employee :AbstractEmployee {

    protected float salary;

    protected string name;

    protected ArrayList subordinates;

 

    //------

    public Employee(string nm, float salry) {

        subordinates = new ArrayList();

        name = nm;

        salary = salry;

    }

 

    //------

    public float getSalary() {

        return salary;

    }

 

    //------

    public string getName() {

        return name;

    }

 

    //------

    public bool isLeaf() {

        return subordinates.Count == 0;

    }

 

    //------

    public virtual void add(string nm, float salary) {

        throw new Exception("No subordinates in base employee class");

    }

 

    //------

    public virtual void add(AbstractEmployee emp) {

        throw new Exception("No subordinates in base employee class");

    }

 

    //------

    public IEnumerator getSubordinates() {

        return subordinates.GetEnumerator ();

    }

 

    public virtual AbstractEmployee getChild() {

        return null;

    }

 

    //------

    public float getSalaries() {

        float sum;

        AbstractEmployee esub;

        //get the salaries of the boss and subordinates

        sum = getSalary();

        IEnumerator enumSub = subordinates.GetEnumerator() ;

        while (enumSub.MoveNext()) {

            esub = (AbstractEmployee)enumSub.Current;

            sum += esub.getSalaries();

        }

        return sum;

    }

}

从Employee接口和他的一个实现来看,下面很可能要将这个类型的数据组合成一个树的结构。

Boss类是Employee类的派生,他重载了Employee类的add和getChild方法:

public class Boss:Employee

{

    public Boss(string name, float salary):base(name,salary) {

    }

 

    //------

    public Boss(AbstractEmployee emp):base(emp.getName() , emp.getSalary()) {

    }

 

    //------

    public override void add(string nm, float salary) {

        AbstractEmployee emp = new Employee(nm,salary);

        subordinates.Add (emp);

    }

 

    //------

    public override void add(AbstractEmployee emp){

        subordinates.Add(emp);

    }

 

    //------

    public override AbstractEmployee getChild() {

        bool found;

        AbstractEmployee tEmp = null;

        IEnumerator esub ;

 

        if (getName().Equals (getName()))

            return this;

        else {

            found = false;

            esub = subordinates.GetEnumerator ();

            while (! found && esub.MoveNext()) {

                tEmp = (AbstractEmployee)esub.Current;

                found = (tEmp.getName().Equals(name));

                if (! found) {

                    if (! tEmp.isLeaf()) {

                        tEmp = tEmp.getChild();

                        found = (tEmp.getName().Equals(name));

                    }

                }

            }

            if (found)

                return tEmp;

            else

                return new Employee("New person", 0);

        }

    }

}

getChild方法是一个递归调用,如果Child不是Leaf,就继续调用下去。上面几个类表达了一个树的结构,表示出了公司中的领导和雇员的级别关系。

现在我们看一下这个程序需要达到的目标,程序运行后显示下面的界面:

界面上有一个树图,树上显示某公司的人员组织结构,点击这些雇员,会在下面出现这个人的工资。现在程序中有两棵树:一棵是画面上实际的树,另一个是公司中雇员的虚拟的树。画面上的树节点是TreeNode类型,雇员的虚拟树节点是AbstractEmployee类型。我们可以采用组合模式,创造一种新的“节点”,组合这两种节点的特性,简化窗体类需要处理的情况,请看下面的代码:

public class EmpNode:TreeNode {

    private AbstractEmployee emp;

 

    public EmpNode(AbstractEmployee aemp ):base(aemp.getName ()) {

        emp = aemp;

    }

 

    //-----

    public AbstractEmployee getEmployee() {

        return emp;

    }

}

EmpNode类是TreeNode类的子类,他具有TreeNode类的所有特性,同时他也组合了AbstractEmployee类型的特点。这样以来调用者的工作就简化了。下面是Form类的代码片断,我把自动生成的代码省略了一部分:

public class Form1 : System.Windows.Forms.Form {

    private System.Windows.Forms.Label lbSalary;

 

    /// <summary>

    /// Required designer variable.

    /// </summary>

    private System.ComponentModel.Container components = null;

    AbstractEmployee prez, marketVP, salesMgr;

    TreeNode rootNode;

    AbstractEmployee advMgr, emp, prodVP, prodMgr, shipMgr;

    private System.Windows.Forms.TreeView EmpTree;

    private Random rand;

 

    private void init() {

        rand = new Random ();

        buildEmployeeList();

        buildTree();

    }

 

    //---------------

    private void buildEmployeeList() {

        prez = new Boss("CEO", 200000);

        marketVP = new Boss("Marketing VP", 100000);

        prez.add(marketVP);

        salesMgr = new Boss("Sales Mgr", 50000);

        advMgr = new Boss("Advt Mgr", 50000);

        marketVP.add(salesMgr);

        marketVP.add(advMgr);

        prodVP = new Boss("Production VP", 100000);

        prez.add(prodVP);

        advMgr.add("Secy", 20000);

 

        //add salesmen reporting to sales manager

        for (int i = 1; i<=5; i++){

            salesMgr.add("Sales" + i.ToString(), rand_sal(30000));

        }

        prodMgr = new Boss("Prod Mgr", 40000);

        shipMgr = new Boss("Ship Mgr", 35000);

        prodVP.add(prodMgr);

        prodVP.add(shipMgr);

 

        for (int i = 1; i<=3; i++){

            shipMgr.add("Ship" + i.ToString(), rand_sal(25000));

        }

        for (int i = 1; i<=4; i++){

            prodMgr.add("Manuf" + i.ToString(), rand_sal(20000));

        }

    }

 

    //-----

    private void buildTree() {

        EmpNode nod;

        nod = new EmpNode(prez);

        rootNode = nod;

        EmpTree.Nodes.Add(nod);

        addNodes(nod, prez);

    }

 

    //------

    private void getNodeSum(EmpNode node) {

        AbstractEmployee emp;

        float sum;

        emp = node.getEmployee();

        sum = emp.getSalaries();

        lbSalary.Text = sum.ToString ();

    }

 

    //------

    private void addNodes(EmpNode nod, AbstractEmployee emp) {

        AbstractEmployee newEmp;

        EmpNode newNode;

        IEnumerator empEnum;

        empEnum = emp.getSubordinates();

 

        while (empEnum.MoveNext()) {

            newEmp = (AbstractEmployee)empEnum.Current;

            newNode = new EmpNode(newEmp);

            nod.Nodes.Add(newNode);

            addNodes(newNode, newEmp);

        }

    }

 

    //------

    private float rand_sal(float sal) {

        float rnum = rand.Next ();

        rnum = rnum / Int32.MaxValue;

        return rnum * sal / 5 + sal;

    }

 

    //------

    public Form1() {

        //

        // Required for Windows Form Designer support

        //

        InitializeComponent();

        init();

        //

        // TODO: Add any constructor code after InitializeComponent call

        //

    }

 

    /// <summary>

    /// Clean up any resources being used.

    /// </summary>

    protected override void Dispose( bool disposing ) {

        if( disposing ) {

            if (components != null) {

                components.Dispose();

            }

        }

        base.Dispose( disposing );

    }

    /// <summary>

    /// The main entry point for the application.

    /// </summary>

    [STAThread]

    static void Main() {

        Application.Run(new Form1());

    }

 

    private void EmpTree_AfterSelect(object sender, TreeViewEventArgs e) {

        EmpNode node;

        node = (EmpNode)EmpTree.SelectedNode;

        getNodeSum(node);

    }

}

EmpTree_AfterSelect方法是树图点击节点事件的响应方法,用户点击节点后在文本栏里显示相应的工资。组合模式已经介绍完了,下面的东西和组合模式没有什么关系。

为什么用interface

为什么要在程序中创建雇员的interface呢?我们可以创建一个class Employee,再派生出Boss,一样可以实现上面的功能嘛。

使用interface是为了将画面上的显示程序与后台的业务数据程序分离开。画面的显示程序只需要关心“雇员”提供哪些接口就可以工作了,而不去过问具体的细节,比如工资的计算规则。如果需要对界面类和数据类分别进行单元测试,这样的做法也提供了可能(也就是说,这个程序是可测试的)。测试画面的时候可以在雇员接口上实现一些虚假的雇员类,其中的方法和属性都是为了测试而假造的,这样就可以测试界面的显示是否正确。一般说来程序如果要进行单元测试,应该从设计阶段就考虑程序的“可测试性”,其中重要的一点是:将界面表示与业务逻辑分离开。

关于如何提高程序的可测试性,以后有时间我会整理一些心得体会。


.NET中的设计模式四:命令模式
作者:      来源:http://www.qqhit.com/     时间:2006-4-27 13:15:05

 

 

 命令模式(Command)结构并不复杂,简单的说,就是将某些职责封装成对象,对象的激发者(Invoker)和接收者(Reciever)都不过问命令的执行过程。用这样的方式解除了激发者和接受者之间的耦合,为程序提供了更清晰的层次结构。实际运用中经常是将系统中的命令抽象成一个接口,所有的命令都是这个接口的实现,如下所示:

Command类是ICommand的实现,Command类实现了execute方法,其他命令类都是Command方法的子类。这样激发者接受者和接受者面临的环境就非常简单了:统一作为Command类型就可以处理了。

最简单的命令模式的实现代码如下:

///定义Command接口

public interface ICommand

{

    void execute();

}

 

///实现ICommand接口

///Command类是所有命令的基类,在这里实现一些命令的共同处理过程

public abstract class Command : ICommand

{

    public virtual void execute()

    {

    }

    //实现其他的方法....

}

 

///实现一个具体的Command1

public class Command1 : Command

{

    public void override execute()

    {

        //具体的处理过程

    }

}

 

///实现一个具体的Command2

public class Command2 : Command

{

    public void override execute()

    {

        //具体的处理过程

    }

}

 

///实现一个具体的Command3

public class Command3 : Command

{

    public void override execute()

    {

        //具体的处理过程

    }

}

命令模式的实际应用

下面从一个系统实例看一看命令模式的应用。ReportingService是一个报表系统的一部分,是一个运行在Windows系统上的Service程序,所需要完成的功能是:每天晚上11点到凌晨2点,将数据库中的大批量的数据生成数百张报表。报表的分布较为复杂,有的要求在Web服务器上发布报表,有的要求将报表数据输出成一个文本文件,还有的要将报表保存在数据库系统中。

系统的设计运用了命令模式,其中的一部分结构如下:

程序中定义了ICommand接口,实现了多个Command类(这里只画出3个表示示例),分别执行各个报表的生成任务。Command类提供了下列接口:

分类 名称 类型 说明
属性 finished bool 表示任务是否已经进行完毕
属性 context IApplicationContext 任务执行需要的环境和资源,包括数据库连接、文件系统等等
方法 execute void 执行任务

Service的主线程每天定时将系统运行环境进行初始化,建立一个ApplicationContext类的实例(Application是一个Singlton模式的类,图中没有体现),这个类中保存了Command运行需要使用的全部资源。然后初始化一个Command对象列表,逐个执行每一个Command。这样一来,复杂的业务逻辑就从程序的主框架中分离开了,Service的主线程所需要做的全部工作就是协调资源的分配和异常的处理,还可以不断检查命令执行的状态,看看finished属性是否为真。如果执行完以后发现某些命令执行不成功,要做相应的处理。

程序采用了单元测试的开发方式,在对程序主线程进行测试的时候,使用了一个虚拟的Command实现。测试每一个Command类的时候则为每个Command实现了虚拟的ApplicationContext。这样的结构为单元测试提供了很多便利。关于提高程序的可测试性可以参见我的另一篇文章:怎样测试代码中难测试的部分

命令模式和其他模式的结合

为了给Command的建立提供一个整洁简单的方式,Command模式经常和工厂模式(Factory)结合使用,采用一个工厂对Command对象的建立进行管理,减少命令激发者与命令对象之间的耦合程度。

在窗体程序中,Command模式还经常与组合模式(Composition)结合使用。可以在初始化窗体的时候,将画面上的菜单项、工具栏、按钮等控件与相应的Command进行组合,这样在控件点击时,调用对应的Command,即可实现对命令的处理。

在《程序员》杂志本年第10月期介绍了一个利用Command模式实现程序中Undo功能的文章。程序中为每个Command定义了“执行”和“撤销”两种操作,如下:

图中的Action就是Commamnd对象,只是名称不同,意义是一样的。Action对象存在do和undo方法,分别是“执行”和“撤销”方法。可以将执行完毕的Action对象的done属性设置为true,再将其保存在一个列表中——ActionList。在ActionList中找到最后一个done属性为true的Action,执行其undo方法,即可实现“撤销”的操作。

命令类的序列化

如果将Command基类及其每一个子类都进行序列化,可以实现更多的强大的功能。程序执行过程中,如果发生异常,比如网络发生中断、文件丢失等异常,可以将没有执行完毕的Command序列化保存下来。等待可以执行的时候再将这些对象重新加载,命令可以继续执行。甚至用户已经重新启动了计算机,命令也可以继续执行。Command对象的序列化也为分布式的操作提供了便利。在网络环境中,可以在某处建立一个Command对象,将其序列化后传输到网络上另一台计算机上进行执行。这种情况下不要忘记异常类的序列化,如果自己定义了Exception的子类,也要将其进行序列化,以便在网络环境中捕捉和处理Command执行时发生的异常。Microsoft网站上有一篇文章介绍.NET中对象的序列化,详细的介绍了对象序列化的概念。

参考文献

《实用J2EE设计模式编程指南》 (美)Graig A.Berry John Carnell Matjaz B.Juric 邱仲潘

posted @ 2006-08-18 17:51  阿东hd  阅读(555)  评论(0编辑  收藏  举报