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

threenewbee threenewbee 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要 + 加入社区
注:本文的目的不是谈论设计模式的实现,而是侧重讲如何把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()实现这一点。

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

最后,我们用到了动态对象。
...全文

相关推荐

评论 124
收藏 已收藏
分享
返回顶部
threenewbee
C# 111,129 成员 642,539 内容