图的遍历(C#)

讲的非常好的一篇文章。感谢abatei,直接收藏分享之。 图的存储结构 图的存储结构除了要存储图中各个顶点的本身的信息外,同时还要存储顶点与顶点之间的所有关系(边的信息),因此,图的结构比较复杂,很难以数据元素在存储区中的物理位置来表示元素之间的关系,但也正是由于其任意的特性,故物理表示方法很多。常用的图的存储结构有邻接矩阵、邻接表、十字链表和邻接多重表。 8.2.1 邻接矩阵表示法 对于一个具有n个顶点的图,可以使用n*n的矩阵(二维数组)来表示它们间的邻接关系。图8.10和图8.11中,矩阵A(i,j)=1表示图中存在一条边(Vi,Vj),而A(i,j)=0表示图中不存在边(Vi,Vj)。实际编程时,当图为不带权图时,可以在二维数组中存放bool值,A(i,j)=true表示存在边(Vi,Vj),A(i,j)=false表示不存在边(Vi,Vj);当图带权值时,则可以直接在二维数组中存放权值,A(i,j)=null表示不存在边(Vi,Vj)。 图8.10所示的是无向图的邻接矩阵表示法,可以观察到,矩阵延对角线对称,即A(i,j)= A(j,i)。无向图邻接矩阵的第i行或第i列非零元素的个数其实就是第i个顶点的度。这表示无向图邻接矩阵存在一定的数据冗余。 图8.11所示的是有向图邻接矩阵表示法,矩阵并不延对角线对称,A(i,j)=1表示顶点Vi邻接到顶点Vj;A(j,i)=1则表示顶点Vi邻接自顶点Vj。两者并不象无向图邻接矩阵那样表示相同的意思。有向图邻接矩阵的第i行非零元素的个数其实就是第i个顶点的出度,而第i列非零元素的个数是第i个顶点的入度,即第i个顶点的度是第i行和第i列非零元素个数之和。 由于存在n个顶点的图需要n2个数组元素进行存储,当图为稀疏图时,使用邻接矩阵存储方法将出现大量零元素,照成极大地空间浪费,这时应该使用邻接表表示法存储图中的数据。 8.2.2 邻接表表示法 图的邻接矩阵存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。邻接表由表头结点和表结点两部分组成,其中图中每个顶点均对应一个存储在数组中的表头结点。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。如图8.12所示,表结点存放的是邻接顶点在数组中的索引。对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。 有向图的邻接表有出边表和入边表(又称逆邻接表)之分。出边表的表结点存放的是从表头结点出发的有向边所指的尾顶点;入边表的表结点存放的则是指向表头结点的某个头顶点。如图8.13所示,图(b)和(c)分别为有向图(a)的出边表和入边表。 以上所讨论的邻接表所表示的都是不带权的图,如果要表示带权图,可以在表结点中增加一个存放权的字段,其效果如图8.14所示。 【注意】:观察图8.14可以发现,当删除存储表头结点的数组中的某一元素,有可能使部分表头结点索引号的改变,从而导致大面积修改表结点的情况发生。可以在表结点中直接存放指向表头结点的指针以解决这个问题(在链表中存放类实例即是存放指针,但必须要保证表头结点是类而不是结构体)。在实际创建邻接表时,甚至可以使用链表代替数组存放表头结点或使用顺序表存代替链表存放表结点。对所学的数据结构知识应当根据实际情况及所使用语言的特点灵活应用,切不可生搬硬套。 【例8-1 AdjacencyList.cs】图的邻接表存储结构 using System; using System.Collections.Generic; public class AdjacencyList<T> { List<Vertex<T>> items; //图的顶点集合 public AdjacencyList() : this(10) { } //构造方法 public AdjacencyList(int capacity) //指定容量的构造方法 { items = new List<Vertex<T>>(capacity); } public void AddVertex(T item) //添加一个顶点 { //不允许插入重复值 if (Contains(item)) { throw new ArgumentException("插入了重复顶点!"); } items.Add(new Vertex<T>(item)); } public void AddEdge(T from, T to) //添加无向边 { Vertex<T> fromVer = Find(from); //找到起始顶点 if (fromVer == null) { throw new ArgumentException("头顶点并不存在!"); } Vertex<T> toVer = Find(to); //找到结束顶点 if (toVer == null) { throw new ArgumentException("尾顶点并不存在!"); } //无向边的两个顶点都需记录边信息 AddDirectedEdge(fromVer, toVer); AddDirectedEdge(toVer, fromVer); } public bool Contains(T item) //查找图中是否包含某项 { foreach (Vertex<T> v in items) { if (v.data.Equals(item)) { return true; } } return false; } private Vertex<T> Find(T item) //查找指定项并返回 { foreach (Vertex<T> v in items) { if (v.data.Equals(item)) { return v; } } return null; } //添加有向边 private void AddDirectedEdge(Vertex<T> fromVer, Vertex<T> toVer) { if (fromVer.firstEdge == null) //无邻接点时 { fromVer.firstEdge = new Node(toVer); } else { Node tmp, node = fromVer.firstEdge; do { //检查是否添加了重复边 if (node.adjvex.data.Equals(toVer.data)) { throw new ArgumentException("添加了重复的边!"); } tmp = node; node = node.next; } while (node != null); tmp.next = new Node(toVer); //添加到链表未尾 } } public override string ToString() //仅用于测试 { //打印每个节点和它的邻接点 string s = string.Empty; foreach (Vertex<T> v in items) { s += v.data.ToString() + ":"; if (v.firstEdge != null) { Node tmp = v.firstEdge; while (tmp != null) { s += tmp.adjvex.data.ToString(); tmp = tmp.next; } } s += "\r\n"; } return s; } //嵌套类,表示链表中的表结点 public class Node { public Vertex<T> adjvex; //邻接点域 public Node next; //下一个邻接点指针域 public Node(Vertex<T> value) { adjvex = value; } } //嵌套类,表示存放于数组中的表头结点 public class Vertex<TValue> { public TValue data; //数据 public Node firstEdge; //邻接点链表头指针 public Boolean visited; //访问标志,遍历时使用 public Vertex(TValue value) //构造方法 { data = value; } } } AdjacencyList<T>类使用泛型实现了图的邻接表存储结构。它包含两个内部类,Vertex<Tvalue>类(109~118行代码)用于表示一个表头结点,Node类(99~107)则用于表示表结点,其中存放着邻接点信息,用来表示表头结点的某条边。多个Node用next指针相连形成一个单链表,表头指针为Vertex类的firstEdge成员,表头结点所代表的顶点的所有边的信息均包含在链表内,其结构如图8.12所示。所不同之处在于: l Vertex类中包含了一个visited成员,它的作用是在图遍历时标识当前节点是否被访问过,这一点在稍后会讲到。 ...

2013-01-06 · 4 min · bystander

远程管理Demo(C#)

一个C#的通信的例子 1.服务端,服务端通过ip和端口生成客户端之后,点击开始监听后,便开启监听线程持续监听,同时注册断开连接和收到信息的事件。收到来自TcpClient 流中的信息后,解析之,如果是连接信息,就添加到连接列表,这样服务端就可以显示多个客户端了。如果是断开信息,就删掉。如果服务端想要给客户端发消息,就选中该客户,然后填写信息,就会调用连接类的发送方法。 2.客户端,也就是被控端,被控端通过tcp连接到远端ip,然后发送连接成功状态,随后异步读取。读取到信息后调用解析方式。然后处理。。 3.服务端如何生成客户端。其实也比较简单。就是写好客户端以后,保存为文本。然后通过CodeDomProvider的相关方法来编译即可。代码如下: public static bool Compile(string EXE_Name, string Source) { CodeDomProvider Compiler = CodeDomProvider.CreateProvider("CSharp"); CompilerParameters Parameters = new CompilerParameters(); CompilerResults cResults = default(CompilerResults); Parameters.GenerateExecutable = true; Parameters.OutputAssembly = EXE_Name; Parameters.ReferencedAssemblies.Add("System.dll"); Parameters.CompilerOptions = " /target:winexe"; Parameters.TreatWarningsAsErrors = false; cResults = Compiler.CompileAssemblyFromSource(Parameters, Source); if (cResults.Errors.Count > 0) { foreach (CompilerError CompilerError_loopVariable in cResults.Errors) { CompilerError error = CompilerError_loopVariable; MessageBox.Show("Error: " + error.ErrorText, "", MessageBoxButtons.OK, MessageBoxIcon.Error); } return false; } else if (cResults.Errors.Count == 0) { return true; } return true; } 源码下载:CSharp RAT Example.zip

2013-01-04 · 1 min · bystander

Lambda高手之路第六部分

今天武汉地铁通车了,今天介绍一些新的Lambda设计模式,应该是最后一部分了。 本节介绍一些核心有lambda表达式的模式,我不认为他们完全是新的模式,但是至少我还没有看到有人给他们起过名字,我于是决定尝试取个可能好,也可能不好的名字,这样我起码能很容易的给别人描述,有话在先,许多模式相当强大,但是可能会引入潜在的bug,所以小心为上 复杂的多态 Lambda表达式也可以被用来创建一些多态(override),而不用使用abstract或者virtual关键字(当然这并不意味着就不能用),考虑如下的代码片段 class MyBaseClass { public Action SomeAction { get; protected set; } public MyBaseClass() { SomeAction = () => { //Do something! }; } } 看起来没什么新的知识,我们创建一个类,里面有一个属性(一个lambda表达式),又一次JavaScript化了,有趣的地方是:属性暴露的这个部分不只是本类可以改变,子类也可以改变,看代码 class MyInheritedClass : MyBaseClass { public MyInheritedClass { SomeAction = () => { //Do something different! }; } } 看到了。我们可以改变这个方法。或者进行更精确的操作,这种方法的缺点是我们不能直接访问父类的实现,也就缺乏了基类的能力,因为,这个父类的属性会有同样的值,如果程序员真的需要这样写,我建议你遵循 pattern class MyBaseClass { public Action SomeAction { get; private set; } Stack<Action> previousActions; protected void AddSomeAction(Action newMethod) { previousActions.Push(SomeAction); SomeAction = newMethod; } protected void RemoveSomeAction() { if(previousActions.Count == 0) return; SomeAction = previousActions.Pop(); } public MyBaseClass() { previousActions = new Stack<Action>(); SomeAction = () => { //Do something! }; } } 这样的话,子类就不得不拥有了 AddSomeAction() 方法,而这个方法是吧当前的方法压入堆栈,那样我们可以恢复之前的状态。 这种模式我起了一个名字叫做Lambda属性多态模式(LP3),它简单的描述了可以在属性里捕获任何方法。之后可以被子类所设置,栈是这个模式的一个附加品,没有改变我们使用属性来完成的模式目标 为什么要用这种模式?有几个理由。地一个,因为我们可以用。但是等一等。如果你使用当中不同的属性,这个模式会变得相当棘手,突然,多态变成了一个完全的新方法。但是这也许是一个不同的模式,现在我想说这个模式完成了以前人们认为不可能的事情 举个例子,你想要(不建议,但是也许对该问题是最优雅的解决方法了。)重写一个静态方法,好吧。静态不可能被继承,原因很简单,师承是对实例对象来说的。而静态方法不属于任何一个实例,对于所有的实例都是一样的。这会引发一个警告,下面的例子也许并不如你所想的结果,因此,除非你非常清楚。否则不要乱用。 看代码 void Main() { var mother = HotDaughter.Activator().Message; //mother = "I am the mother" var create = new HotDaughter(); var daughter = HotDaughter.Activator().Message; //daughter = "I am the daughter" } class CoolMother { public static Func<CoolMother> Activator { get; protected set; } //此处是防止空引用 static CoolMother() { Activator = () => new CoolMother(); } public CoolMother() { //Message of every mother Message = "I am the mother"; } public string Message { get; protected set; } } class HotDaughter : CoolMother { public HotDaughter() { //一进入构造函数我们设置Activator ... Activator = () => new HotDaughter(); //Message of every daughter Message = "I am the daughter"; } } 这很简单,希望没有对你产生误导,这种模式有时候会让事情变得异常复杂,这也是为什么我总是避免使用它。不过他很有用。(可以通过该方法构造所有的静态属性和方法,并且可以使你总是获得你感兴趣的那个)只要你不感到头疼,这是解决静态多态性的一个好方法。是的。静态多态性是可能的。 ...

2012-12-28 · 3 min · bystander

3分钟理解Lambda表达式

1.什么是Lambda表达式 Lambda表达式是一个匿名方法,通常在LINQ中被用来创建委托 简单来说。它是一个没有声明,没有访问修饰符,没有返回值。甚至没有名字的方法。 2.为什么我们需要使用Lambda表达式?或者说为什么我们要写一个没有名字的函数? 为了方便,这种快捷方式允许你在调用的地方直接编写代码,尤其是你想调用的代码只会在这个地方使用一次。并且方法体本身很短。节省了单独写方法中写声明等等的麻烦。。 好处 1.代码量减少。不必写方法的名称。返回值和访问修饰符 2.当阅读代码的时候。直接就可以看到被调用函数的代码,不用去别的地方。 Lambda表示应该短些。太复杂了。可读性就下降了 如果编写Lambda表达式 Lambda基本的定义是:参数=>执行代码 举个例子 n = > n % 2 == 1 n是输入参数 n % 2 == 1 是函数体 你可以读作:给这个匿名方法传入一个参数n,如果n是奇数就返回true 使用该Lambda的例子 List<int> numbers = new List<int>{11,37,52}; List<int> oddNumbers = numbers.where(n => n % 2 == 1).ToList(); //现在oddNumbers 里面就是11和37了 ok.基本的Lambda表达式就是这样了。

2012-12-08 · 1 min · bystander

C#模拟手工洗牌(附测试)

洗牌大家都知道,代码实现最广泛的一种就是产生两个随机数,然后交换这两个随机数为下标的牌,但是这种的洗牌并不能保证同概率,你可以参考本文做一些测试,本文代码没啥可说的。我写出了非常详细的注释 ps:刚开始写那个随机数的时候,我随便给了个种子2012.。结果你懂的。。意外意外。这个全局的result数组让我很疼,代码有什么可以改进的,欢迎留言指出。不胜感激。 /*Author:Bystander *Blog:http://leaver.me *Date:2012/11/24*/ using System; class Program { static int[,] result; static void Main() { //初始牌的顺序,我只测试10张牌的情况 char[] _arr = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' }; result = new int[_arr.Length, _arr.Length]; //进行1000次,来统计结果的数字. for (int i = 0; i < 10000; i++) { ShuffleArray_Manual(_arr); SumCount(_arr); } int j = 0; foreach (int i in result) { if (j % result.GetLength(1) == 0) { Console.WriteLine(); } Console.Write(i + "\t"); j++; } } //模拟洗牌 static void ShuffleArray_Manual(char[] arr) { Random rand = new Random(Guid.NewGuid().GetHashCode()); int len = arr.Length; int mid = len / 2; /* * 洗牌的过程重复进行5次,为了洗的均匀.每次都是先平分扑克,然后完全交叉,然后从牌中拿出一部分,放在牌的最前面,也就是切牌,然后再进行下次平分扑克。 */ //双手洗牌5次,默认认为是平分扑克 for (int n = 0; n < 5; n++) { //两手洗牌 for (int i = 1; i < mid; i += 2) { char tmp = arr[i]; arr[i] = arr[mid + i]; arr[mid + i] = tmp; } //随机切牌 //注意切牌指的是从中抽出n张牌放到扑克牌的前面去 char[] buf = new char[len]; for (int j = 0; j < 5; j++) { //产生从大于等于1小于len的数 int start = rand.Next() % (len - 1) + 1; //产生大于等于0小于等于一半的数 int numCards = rand.Next() % (len / 2) + 1; if (start + numCards > len) { numCards = len - start; } //把扑克牌arr的前start张牌复制到buf里 Array.ConstrainedCopy(arr, 0, buf, 0, start); ///然后把切出来的numCards张牌,起始下标为start的移动到扑克牌arr的最前面 Array.ConstrainedCopy(arr, start, arr, 0, numCards); ///最后把切出去的buf牌(numCards张)复制回扑克牌arr的numCards之后的元素 Array.ConstrainedCopy(buf, 0, arr, numCards, start); } } } //统计一次结果的次数,存入结果数组 static void SumCount(char[] arr) { for (int i = 0; i < arr.Length; i++) { result[arr[i] - 'A', i] += 1; } } }

2012-11-25 · 2 min · bystander

获取操作系统版本信息

坊间流传的代码都有些问题,比如不能正常获取win7以上的版本信息,不能获取诸如专业版,旗舰版等的信息,不能正常获取操作系统位的信息。 使用代码,写了一个简单的库来实现效果。用法大概如下: StringBuilder sb = new StringBuilder(String.Empty); sb.AppendLine("Operation System Information"); sb.AppendLine("----------------------------"); sb.AppendLine(String.Format("Name = {0}", OSVersionInfo.Name)); sb.AppendLine(String.Format("Edition = {0}", OSVersionInfo.Edition)); if (OSVersionInfo.ServicePack!=string.Empty) sb.AppendLine(String.Format("Service Pack = {0}", OSVersionInfo.ServicePack)); else sb.AppendLine("Service Pack = None"); sb.AppendLine(String.Format("Version = {0}", OSVersionInfo.VersionString)); sb.AppendLine(String.Format("ProcessorBits = {0}", OSVersionInfo.ProcessorBits)); sb.AppendLine(String.Format("OSBits = {0}", OSVersionInfo.OSBits)); sb.AppendLine(String.Format("ProgramBits = {0}", OSVersionInfo.ProgramBits)); textBox1.Text = sb.ToString(); 对比一下坊间的几种不足: 总的来说。最大的问题就是不能正确检测你的操作系统到底是32位还是64位。几种方法大致如下: 1. 使用IntPtr指针的大小 最关键的一句代码是: return IntPtr.Size * 8; 但是事实上,这个返回的不是操作系统的位数,返回的是运行的程序的位数,如果在64位的windows上以32位的模式运行了这个程序,那么就会返回32. 2. 使用PROCESSOR_ARCHITECTURE 环境变量 string pa = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); return ((String.IsNullOrEmpty(pa) || String.Compare(pa, 0, "x86", 0, 3, true) == 0) ? 32 : 64); 这就是纯粹的误导了,因为和1的情况一样。不能返回处理器的位数而是返回了运行程序的位数,如果在64位的windows上以32位的模式运行了这个程序,那么就会返回32. 3. 使用PInvoke 和 GetSystemInfo 注意:为了保持文章不要太长。。我没有包括PInvoke API的声明,(译者注:C#的互操作性嘛),但你可能在我提供的源代码里找到。 ProcessorArchitecture pbits = ProcessorArchitecture.Unknown; try { SYSTEM_INFO l_System_Info = new SYSTEM_INFO(); GetSystemInfo(ref l_System_Info); switch (l_System_Info.uProcessorInfo.wProcessorArchitecture) { case 9: // PROCESSOR_ARCHITECTURE_AMD64 pbits = ProcessorArchitecture.Bit64; break; case 6: // PROCESSOR_ARCHITECTURE_IA64 pbits = ProcessorArchitecture.Itanium64; break; case 0: // PROCESSOR_ARCHITECTURE_INTEL pbits = ProcessorArchitecture.Bit32; break; default: // PROCESSOR_ARCHITECTURE_UNKNOWN pbits = ProcessorArchitecture.Unknown; break; } } catch { Ignore } return pbits; 老问题,还是会返回运行程序的位数,而不是操作系统/处理器的位数。 4. 使用PInvoke和GetNativeSystemInfo 我看到过有人说上面的都不可信。可以使用GetNativeSystemInfo代替,代码和上面一样,只是把GetSystemInfo换成GetNativeSystemInfo就好。 ...

2012-11-23 · 2 min · bystander

依赖倒置原则和依赖注入模式

昨天读完了程杰的《大话设计模式》。。收获颇丰。深刻感到了设计模式的伟大。。对面向接口的编程也理解了不少。刚好看到codeproject上一篇将依赖倒置的。讲到了依赖注入的方式。仔细读了一下。翻译一遍加深认识。 高耦合的代码随着项目复杂性的不断增加,最终会变成一碗碗的意大利面条啦。。二者通常是软件设计上的问题,如果一个类对另一个类的实现了解太多。当该类改变的时候会引起更多的改变。这违反了依赖倒置原则 而松耦合的代码设计优良。随着时间流逝,代码复杂两增大,松耦合的好处会变得更加清晰,依赖注入模式是实现松耦合的一个好的办法,本文介绍在没有依赖注入容器的情况下实现依赖注入 GoF说了,依赖倒置的原则: 高层模块不应依赖于低层模块,他们都应该依赖于抽象 抽象不依赖细节,细节依赖抽象 刚开始写依赖倒置比较难,随着经验增长会有所改善,通过使高层模块依赖于抽象,依赖倒置成功解耦,依赖注入模式是该原则的一个实现。 通常我们写出如下的代码: public class Email { public void SendEmail() { // code } } public class Notification { private Email _email; public Notification() { _email = new Email(); } public void PromotionalNotification() { _email.SendEmail(); } } Notification类依赖Email类,这违反了DIP,而且当我们要发送短信/保存到数据库的时候,我们还要改变Notification类。 我们使用抽象类/接口解耦 public interface IMessageService { void SendMessage(); } public class Email : IMessageService { public void SendMessage() { // code } } public class Notification { private IMessageService _iMessageService; public Notification() { _iMessageService = new Email(); } public void PromotionalNotification() { _iMessageService.SendMessage(); } } IMessageService 是一个接口,而Notification 类只要调用接口的方法/属性就可以了 同时,我们把Email对象的构造移到Notification 类外面去。 依赖注入模式可以实现。通常有三种方式 构造器注入 属性注入 方法注入 构造器注入 最普遍的方式,当一个类需要另一个类的依赖的时候,我们通过构造函数来提供,现在我们这样写 public class Notification { private IMessageService _iMessageService; public Notification(IMessageService _messageService) { this._iMessageService = _messageService; } public void PromotionalNotification() { _iMessageService.SendMessage(); } } 有几个好处:1.构造函数实现很简单,Notification类需要知道的很少。想要创建Notification实例的时候看构造函数就可以知道需要什么信息了。因此实现了松耦合。 属性注入 属性注入/setter注入比较不常见,当依赖可有可无的时候很有用。我们暴露一个可写的属性,允许客户提供不同的依赖实现,比如这样。 public class Notification { public IMessageService MessageService { get; set; } public void PromotionalNotification() { if (MessageService == null) { // some error message } else { MessageService.SendMessage(); } } } 没有了构造函数。而用属性来替换,在PromotionalNotifications 方法里我们需要检查MessageService的值或者提供相应的服务。 ...

2012-11-21 · 1 min · bystander

理解并实现模板模式

介绍 本文实现模板模式 背景 有时候我们需要做很多任务,而做这些任务的算法可能不同,这样可以设计成策略模式,这样。执行该任务的基本的一些代码就是一样的。但程序可可以动态的切换来执行任务的不同部分了。 现在,真实的情况是有些算法,从实现层面山看,有可能有一些步骤是不一样的,这种情况下。我们可以使用继承来完成。 当有个算法,而这个算法的一部分却多样的时候。使用模板模式就很好。GoF定义模板模式为: “Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.”. 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 在上面的类图中: AbstractClass:包含两种方法。第一种就是算法的每一步。另一种就是模板方法。模板方法就是那些可以被用在所有独立方法中。并且提供了算法执行的一个骨架 ConcreteClass:这个类重写了抽象类中每一步的方法,包含对这些步骤的个性化实现。 使用代码 看一个简单的例子。假想我们有一个类用来读取数据。并且能够为信息管理系统到处数据。 abstract class DataExporter { // 这个方法都是一致的 public void ReadData() { Console.WriteLine("Reading the data from SqlServer"); } // 当报表格式顶的时候这个也是定的。 public void FormatData() { Console.WriteLine("Formating the data as per requriements."); } // 目标文件类型的不同导致该方法不同 public abstract void ExportData(); // 这是客户端可能使用的模板方法 public void ExportFormatedData() { this.ReadData(); this.FormatData(); this.ExportData(); } } ReadData和FormatData 的实现不会变。唯一可变的部分就是ExportData方法。该方法对于不同的导出类型不同。如果我们要导出excel文件。我们要实现一个ConcreteClass的实现。 class ExcelExporter : DataExporter { public override void ExportData() { Console.WriteLine("Exporting the data to an Excel file."); } } 同样如果要导出PDF文件。重写这部分即可 class PDFExporter : DataExporter { public override void ExportData() { Console.WriteLine("Exporting the data to a PDF file."); } } 好处就是客户端可以使用DataExporter类,而具体的实现是在派生类中的 static void Main(string[] args) { DataExporter exporter = null; //导出 Excel文件 exporter = new ExcelExporter(); exporter.ExportFormatedData(); Console.WriteLine(); // 导出 PDF 文件 exporter = new PDFExporter(); exporter.ExportFormatedData(); } 运行时。对算法的调用将会执行真正请求的派生类的方法。 看一下我们的类图 ...

2012-10-25 · 1 min · bystander

理解并实现外观设计模式

介绍 本文介绍外观模式,并给出简单的实现示例 背景 写软件的时候,有时候需要处理一系列的对象来完成一个确定的任务.比如,我们给一个万能遥控器写代码,我们需要关掉所有的设备,那么,我们有这样几种选择.第一个就是手动选择每一个设备,然后一个接一个的关闭,这好傻.那我们为什么不再遥控器上放一个按钮,我们按一下就关掉了.按钮的命令会与设备控制器通信然后关掉他们. 如果我们又想在晚上12的时候自动关闭设备,那么我们就会有一个基于事件的计时器,与设备通信,然后关闭设备,问题是在两种情况下我们都需要与这些对象通信的函数. 有很多方法解决这个问题,为什么不能有一个对象,该对象的责任就是关闭设备,当我要关闭设备的时候,我调用该对象就行了.这也是外观模式的理念Gof大神定义外观模式 “Provide a unified interface to a set of interfaces in a subsystem. Façade defines a higher-level interface that makes the subsystem easier to use.” 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 看看模式图 注意外观对象仅仅是提供了对函数一起操作,.不能替换子系统的接口.子系统的类仍然可以被系统的其他部分访问.外观为子系统提供了一致的界面. 使用代码 为了模拟外观模式,我们模拟一个小例子.试着实现一个简单的外观对象,该外观对象操作一些WP手机的控制器对象,我们先定义问题 每天早上我跑步的时候,我都得对我的手机做出以下的事情.. 1. 关闭wifi 2. 切换到移动网络 3. 打开GPS 4. 打开音乐 5. 开始跑步追踪器 跑完以后.,我又蛋疼的做出以下几件事 1. 在twitter和facebook上分享我的跑步数据 2. 关闭跑步追踪器 3. 关闭音乐 4. 关闭GPS 5. 关闭移动数据 6. 打开wifi 目前我都是手工做的.,我们来实现这些假想的控制器类吧. class GPSController { bool isSwitchedOn = false; public bool IsSwitchedOn { get { return isSwitchedOn; } set { isSwitchedOn = value; DisplayStatus(); } } private void DisplayStatus() { string status = (isSwitchedOn == true) ? "ON" : "OFF"; Console.WriteLine("GPS Switched {0}", status); } } 其他的像MobileDataController, MusicController, WifiController 代码都是基本的一样的. 然后模拟一下跑步追踪器这个app class SportsTrackerApp { public void Start() { Console.WriteLine("Sports Tracker App STARTED"); } public void Stop() { Console.WriteLine("Sports Tracker App STOPPED"); } public void Share() { Console.WriteLine("Sports Tracker: Stats shared on twitter and facebook."); } } 下面模拟一下我的手工过程 ...

2012-10-23 · 3 min · bystander

理解并实现装饰器模式

背景 本文讨论装饰器模式,这个模式是因为很多情况下需要动态的给对象添加功能.比如我们创建了一个Stream类.后来需要对这个数据流类动态的添加一个加密功能.有人可能说把加密方法写到流类里面啊.然后使用一个bool变量来控制开关就行了.但是这样.这个加密方法只能写一种..如果用派生类来实现.那么..对于不同的加密方法.,都要创建一个子类,举个例子.比如有时候是一些函数的组合.我们最终的派生类的数目基本上就和排列组合的数目一样了. 我们使用装饰器模式来解决这个问题.GoF描述为 “Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.” 首先看一下图.理解一下这个模式中每一个类的作用 • Component:定义了可以动态添加功能的具体类ConcreteComponents的接口. • ConcreteComponent: 可以动态添加功能的具体类 • Decorator: 定义了动态添加到ConcreteComponent类中的功能的接口 • ConcreteDecorator: 可以添加到 ConcreteComponent.中的具体功能类. 使用代码 我们开一个面包店的例子.面包店卖蛋糕和甜点.客户可以买蛋糕和甜点,同时添加一些额外的东西.额外的东西包括奶油(Cream),樱桃(Cherry),香料(Scent)和会员(Name Card) 如果我们用派生类来实现..那么我们会有如下的类 • CakeOnly • CakeWithCreamAndCherry • CakeWithCreamAndCherryAndScent • CakeWithCreamAndCherryAndScentAndNameCard • CakeWithCherryOnly • PastryOnly • PastryWithCreamAndCherry • PastryWithCreamAndCherryAndScent • PastryWithCreamAndCherryAndScentAndNameCard • PastryWithCherryOnly • 等等等等 这简直就是噩梦..我们用装饰器模式来实现把. 首先定义Component 接口 public abstract class BakeryComponent { public abstract string GetName(); public abstract double GetPrice(); } 前面说过了.这个类定义了能够动态添加功能的具体类(ConcreteComponents)的接口,好吧.然后来创建具体类ConcreteComponents class CakeBase : BakeryComponent { // 真实世界里,这个值应该来自数据库等 private string m_Name = "Cake Base"; private double m_Price = 200.0; public override string GetName() { return m_Name; } public override double GetPrice() { return m_Price; } } class PastryBase : BakeryComponent { //真实世界里,这个值应该来自数据库等 private string m_Name = "Pastry Base"; private double m_Price = 20.0; public override string GetName() { return m_Name; } public override double GetPrice() { return m_Price; } } 现在基对象准备好了.看看那些可以被动态添加的功能.我们看看Decorator 类 ...

2012-10-22 · 2 min · bystander