使用地道的 C# 语言来表现设计模式

threenewbee 2011-04-19 10:32:50
加精
注:本文的目的不是谈论设计模式的实现,而是侧重讲如何把C#语法实际运用到设计模式中去。我想C#5 C#6时代再来看这些设计,恐怕又觉得笨拙了。所以,我想通过这篇文章,给大家一些更好地使用C#语言的启示,而不是给出设计模式实现的固定套路。限于水平有限,本文只能是抛砖引玉,欢迎楼下改进、补充。

注意,一切没有意义的发言,比如顶、支持、学习、mark、收藏、看看各类灌水等请不要留,以便大家更好地参与交流讨论。

首先展示的是迭代器模式和装饰模式:

遍历TreeView是很多人问的问题,用它来展示迭代器模式非常合适,事实上,下面的代码本身就可以拿来用:

我们给TreeView增加一个GetAllNodes()的方法:
    static class TreeViewNodeIterator
{
private static IEnumerable<TreeNode> GetChild(TreeNode tnparent)
{
foreach (TreeNode tn in tnparent.Nodes)
{
if (tn.Nodes.Count > 0)
{
foreach (TreeNode tnchild in GetChild(tn))
{
yield return tnchild;
}
}
yield return tn;
}
}

public static IEnumerable<TreeNode> GetAllNodes(this TreeView treeview)
{
if (treeview == null) throw new NullReferenceException();
foreach (TreeNode tn in treeview.Nodes)
{
if (tn.Nodes.Count > 0)
{
foreach (TreeNode tnchild in GetChild(tn))
{
yield return tnchild;
}
}
yield return tn;
}
}
}


使用:
        private void button1_Click(object sender, EventArgs e)
{
foreach (TreeNode tn in treeView1.GetAllNodes())
Debug.WriteLine(tn.Text);
}


在这个代码中,使用了C#的yield return,它在内部构造了一个状态机,从而实现了枚举接口。

内部,使用了递归来遍历结点。

同时我们用扩展方法来封装这个操作。为原本不支持遍历TreeNode的TreeView增加了功能,这就是装饰模式。


然后我们展示下代理模式。

这个例子是从 http://topic.csdn.net/u/20110415/16/112401aa-2fd7-4f96-8f99-ed33d7739e91.html 受到的启发。

很明显,C#没有办法向某些动态语言那样获得参数,那么怎么做呢?

看代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Test.foo(3, 5);
}

}

static class Test
{
private static void _foo(params int[] args)
{
for (int i = 0; i < args.GetLength(0); i++)
{
Console.WriteLine(args[i]);
}

}

public static void foo(int x, int y)
{
_foo(x, y);
}
}

}


在这里,所有到 foo 的调用请求被转发到了 _foo 上面,而 _foo 因为使用了参数数组,所以可以按照参数传递的顺序获得参数。

模板方法:

模板方法在事件处理中被大量使用,下面给出一个简化的模型:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
MyWindow win = new MyWindow();
win.Close();
}

}

class MyWindow : Window
{
protected sealed override void OnBeforeClose(out bool Cancel)
{
Console.WriteLine("Are you sure to close this window? (yes/no)");
string s = Console.ReadLine();
if (s.ToLower() != "yes")
Cancel = true;
else
Cancel = false;
}

protected sealed override void OnClosing()
{
Console.WriteLine("Please wait while the window is closing...");
}

}

class Window
{
protected virtual void OnBeforeClose(out bool Cancel) { Cancel = false; }
protected virtual void OnClosing() { }
protected virtual void OnClosed() { }

public void Close()
{
bool Cancel = false;
OnBeforeClose(out Cancel);
if (Cancel != true)
{
OnClosing();
OnClosed();
Console.WriteLine("Close() Finished!");
}
else
{
Console.WriteLine("Close() Canceled!");
}
}

}
}


下面是策略模式

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication2
{
class Program
{
static private Action<string> DebugWriter = s => Debug.WriteLine(s);
static private Action<string> ConsoleWriter = s => Console.WriteLine(s);
static void Main(string[] args)
{
Log log1 = new Log(DebugWriter);
log1.WriteLog("I am writing to outpot window!");
Log log2 = new Log(ConsoleWriter);
log2.WriteLog("I am writing to console!");
}
}

class Log
{
private Action<string> _writer = s => Console.WriteLine(s);
public Log(Action<string> writer)
{
if (writer != null) _writer = writer;
}

public void WriteLog(string s)
{
_writer(s);
}
}
}


这个我们讨论过了,在这里,我们使用了两种策略,一个是把信息输出到控制台,一个是把信息输出到 output window。

还是这个例子,下面来演示简单工厂和单例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication2
{
class Program
{

static void Main(string[] args)
{
Log log1 = Factory.GetLog(Factory.LogType.Debug);
log1.WriteLog("I am writing to outpot window!");
Log log2 = Factory.GetLog(Factory.LogType.Console);
log2.WriteLog("I am writing to console!");
}
}

static class Factory
{

public enum LogType
{
Debug,
Console
};

static private Action<string> DebugWriter = s => Debug.WriteLine(s);
static private Action<string> ConsoleWriter = s => Console.WriteLine(s);

public static Log GetLog(LogType Type)
{
if (Type == LogType.Console) return new Log(ConsoleWriter);
if (Type == LogType.Debug) return new Log(DebugWriter);
return new Log(null);
}
}

class Log
{
private Action<string> _writer = s => Console.WriteLine(s);
public Log(Action<string> writer)
{
if (writer != null) _writer = writer;
}

public void WriteLog(string s)
{
_writer(s);
}
}
}


我们把Log的生成交给一个工厂来做,而工厂只需要一个即可。这里使用了静态类的语法,实现了单例。

关于桥模式和享元模式,这里不提供例子,但是举实际的例子来说明问题。

桥模式的例子参考 http://topic.csdn.net/u/20110414/10/06526263-9e0d-491f-b963-7ae0afd1f7e4.html

这就是一个典型的桥模式的应用。作为客户端代码我们围绕LINQ编程而和数据库交互的实现是EF框架做的EF框架桥接了数据库查询和对象查询的具体实现。

下面的例子综合了策略模式、享元模式、工厂方法:

我们使用C#3的扩展方法给员工增加了工资计算的方法。

在内部,我们使用享元模式,从而为员工附加上对应的工资数据。

在使用扩展方法的时候,需要强调一点:目前只有C#支持扩展方法,所以其它语言,必须使用传统的方法调用,这就使得this对象可能为null,所以一定要先判断!
我们按照.NET的习惯,如果对象为null,丢出NullReferenceException。

另外,扩展方法只作用于定义的那个命名空间。这也为我们扩展不同的工资算法提供了可能。

因为扩展方法中保存了对象扩展的数据(这里的_data),所以对于持久化/序列化的时候要注意状态的保持和恢复。建议配合重写GetHashCode()实现这一点。

最后说下。如果扩展方法和类自身的成员重名,而且方法参数相同,执行哪一个呢?记住,类的成员方法优先级更高。

最后,我们用到了动态对象。
...全文
4726 124 打赏 收藏 转发到动态 举报
写回复
用AI写文章
124 条回复
切换为时间正序
请发表友善的回复…
发表回复
无常名 2011-04-27
  • 打赏
  • 举报
回复
说实话对设计模式至今还很模糊,貌似无形之中用的也不少。
xiaoxi_2011 2011-04-25
  • 打赏
  • 举报
回复
刚刚安装了VS2010,才学的.net,看不太明白哦
tellxp 2011-04-25
  • 打赏
  • 举报
回复
工厂和仲裁不错,其它没怎么刻意用过
devilss001 2011-04-24
  • 打赏
  • 举报
回复
支持楼主 楼主太犀利了
hzw417973780 2011-04-22
  • 打赏
  • 举报
回复
c#.net好些,还是asp.net,我是菜鸟···
xymao123 2011-04-22
  • 打赏
  • 举报
回复
c#的event比java实现的观察者模式,要好用和清晰得多
xrongzhen 2011-04-22
  • 打赏
  • 举报
回复
简单工厂解决的是实例化对象的问题

策略模式,银行家算法,至少有一个类用来维护算法类(继承同一个抽象类)对象的引用,一般用的时候都和简单工厂模式结合。

楼主可能为了使用一个例子,强调C#语法,导致模式不是那么明了,至少对初学者来说,很费劲,不易理解
halo_world 2011-04-22
  • 打赏
  • 举报
回复
这种技术能用在那些方面,做网站会遇到吗?
msw120 2011-04-22
  • 打赏
  • 举报
回复
搞清楚了,少加了个静态方法字段,可以了,3Q
msw120 2011-04-22
  • 打赏
  • 举报
回复
另外我照第一个例子写好了之后,编译报错“必须在非泛型静态类中定义扩展方法”是该怎么改呢
msw120 2011-04-22
  • 打赏
  • 举报
回复
代码错了,应该是ChildNodes


private static IEnumerable<TreeNode> GetChild(TreeNode tnparent)
{
foreach (TreeNode tn in tnparent.ChildNodes)
{
if (tn.ChildNodes.Count > 0)
{
foreach (TreeNode tnchild in GetChild(tn))
{
yield return tnchild;
}
}
yield return tn;
}
}
cdcjk 2011-04-21
  • 打赏
  • 举报
回复
工厂和单键常用,其他的本人用的不多
guyehanxinlei 2011-04-21
  • 打赏
  • 举报
回复
正在研究一些常用的Design Pattern
a12321321321312321 2011-04-21
  • 打赏
  • 举报
回复
看到楼主我有点自惭形秽了。。
threenewbee 2011-04-21
  • 打赏
  • 举报
回复
[Quote=引用 67 楼 feisheng512 的回复:]
如果我因代码需要而去调用已封装好的方法,但又觉得不能满足自己的要求,Override 重新写一个,这是否涉及到贴中所说的设计模式?
[/Quote]
直接继承就可以了。并不涉及到设计模式。
threenewbee 2011-04-21
  • 打赏
  • 举报
回复
[Quote=引用 92 楼 cxx1997 的回复:]
请教下 第一个例子里面
public static IEnumerable<TreeNode> GetAllNodes(this TreeView treeview)

能编译过吗?
TreeViewNodeIterator
是怎么扩展到TreeView 上去的?
foreach (TreeNode tn in treeView1.GetAllNodes())

是不是
for……
[/Quote]
这个例子需要C# 3.0(C# 2008)以上才能编译。参考MSDN的扩展方法。
cxx1997 2011-04-21
  • 打赏
  • 举报
回复
请教下 第一个例子里面
public static IEnumerable<TreeNode> GetAllNodes(this TreeView treeview)

能编译过吗?
TreeViewNodeIterator
是怎么扩展到TreeView 上去的?
foreach (TreeNode tn in treeView1.GetAllNodes())

是不是
foreach (TreeNode tn in TreeViewNodeIterator.GetAllNodes(treeView1))

??第一个例子的代码我真没搞明白,请教了
cxx1997 2011-04-21
  • 打赏
  • 举报
回复
//设计模式的很多动机就是将一部分实现推迟给调用者,实现扩展、重用

我的理解是:
设计模式的动机是让代码更贴近事实(逻辑)

很多代码实现,太注重语言层次,导致代码与逻辑脱钩,导致逻辑变化时,代码修改代价不成正比,最终导致项目失控.

这是我的理解,不知道你们怎么思考的.
快乐乔巴 2011-04-21
  • 打赏
  • 举报
回复
学习 不过初学者最好还是学下设计模式 了解下大师们是怎么去解决问题的
SQL77 2011-04-20
  • 打赏
  • 举报
回复
MARK STUDY
加载更多回复(24)

110,545

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧