谈谈WPF和.net的开发模式

谈谈WPF和.net的开发模式

一、概述

谈到WPF的开发,就不能不说到MVVM,一说到MVVM,就会提及MVC、MVP等概念,那么这样一关联下来就会产生很多概念,到最后就很容易变成以概念来阐述概念,最终的结果可想而知,大家可能会一头雾水、不知所云,所以我用“漫谈WPF开发”这个小标题来阐述一下我对WPF开发的理解,当然只是自己对这些技术的总结和经验,错误之处在所难免,也希望大家能够谅解!

从2007年接触WPF和Silverlight以来,也做过一些项目了,对他们也有一些自己的理解,当然在开发这些项目的过程中也在使用其他的一些技术做项目,比如WinForm、ASP.NET(ASP.NET MVC一个项目没做完就被终止)等等,感觉不论是采用什么技术,最基本的东西都不会变,比如对数据库和文件的访问、对日志和异常的处理、对报表的展现、对打印的实现、对性能的提升、对用户的友好等等。

那么这些项目也为我们积累了不少经验,有技术上的也有其他方面的:

  • 为了应付项目需求的不断变化和项目的可扩展性,我们也会引入OO和设计模式;
  • 为了解除各模块和组件的耦合,我们也会利用IOC的思想解耦;
  • 为了让逻辑代码清晰且没有其他代码的干扰,我们也会采用AOP的方式进行代码重组;
  • 为了使项目的开发速度更快且更方便,我们也会引入ORM思想来加快项目的开发速度和可维护性;
  • 为了更好组织各层开发,隔开耦合,我们也会采用MVC、MVP、MVVM模式;
  • 为了提升用户的响应速度,我们会采用AJAX的方式来实现;
  • 为了降低系统的负载同时提高用户的响应能力,我们也会采用MSMQ或者SSB来组织消息队列;
  • 为了规范各系统的接口,提供一个统一的交互平台,我们也会采用SOA;
  • 为了降低服务器的负担和提高速度,我们也会自己写一套缓存;
  • 为了把产品做好,我们也会不断优化技术;
  • 为了能做好外包项目,我们会不需要任何高深技术;
  • 为了能得到客户满意老板好评,我们也会学会如何交流;

其实归根到底就是要分清关系,理清思绪,既要处理好与机器的关系,也要处理好与人的关系,只有这样才能把产品或者项目做成功,我也在不断学习当中,所以如果大家有一些这方面的问题和建议,我们也可以互相讨论。

前面不知所云的漫谈了一通,那么我们到底该怎么认识WPF项目的开发呢?

我个人的观点是和其他技术一样,假如这是一个比较小的而且需求改动很小的项目,那么我不建议用一些高深的技术,因为它要的是马上看到效果和时间上的优势,所以应该抛弃我们的技术思想。

当遇到一个比较大型的项目而且需求可能变动很大,那我们得慎重考虑系统的构架了,因为很多时候我们都会发现我们的系统无法再扩展了,这就是一个很大的“杯具”了。

那么作为一个项目,我们怎样才能在事前做好呢?我觉得有以下几个方面:

  • **项目情况把握:**首先我们要分析项目的背景、项目的目的、项目的前景、项目的需求、项目的客户、项目的实现难度、项目的规模、项目所使用的技术、项目的最终效果等因素,只有把握好了这些方面以后,我们才能做到对项目知根知底且游刃有余。
  • **项目团队把握:**首先分析一下自己的团队成员组成结构,有没有领域分析人员?有几个架构师?有没有Team Leader?
  • 有几个Senor Developer?有几个Developer?有没有测试人员? 有没有项目配置管理员?有没有QA以及有没有类似的项目经验等。
    知道这些可以合理安排任务,这正是对自己团队的把握。
  • **开发模式把握:**不论你是开发产品还是做项目,我们都需要采取一种适合的模式,那么什么叫适合呢?这个没有准确的答案,只有根据具体情况具体分析了,
    如果需求比较明确且系统较大,那我们就可以用传统的瀑布模型进行开发,只要客户能接受同时自己做好各方面的监控,应该问题不大;
    如果需求不是很明确且周期很长,我们可以用迭代的方式进行开发,这样客户也能更加明确自己的需求同时也能看到自己想要的效果。
    如果需求不明确而且有很多不确定因素,我们也可以采用TDD的方式进行开发,如果把握得好,这样慢慢也会形成一个比较好的项目。
  • **开发规范把握:**作为一个多人开发的团队,没有一些规范是不行的,团队管理规范、项目管理规范、代码书写规范、开发流程规范、测试规范等等,这些都是要在开发之前定好,否则我们将会看到项目到处一盘散沙,无从管理。
  • **其他方面规范:**其他方面的规范就很多了,比如开发环境的规范、测试的规范、文档的规范、部署的规范等等,这个可以根据具体项目进行裁剪。

前面谈到了一些项目管理整体把握,总结就是大道至简、适可而止!那么我们如何才能在具体项目中引用一些其他技术呢?感觉思绪有点乱了,还是就此打住,等到了讲WPF具体项目或者具体技术的时候再讲,不然就真的一发不可收拾了。
在这里插入图片描述

以上转自:https://www.cnblogs.com/KnightsWarrior/archive/2010/07/09/1774059.html

三、设计模式七大原则(SOLID)

为什么要提倡 “Design Pattern” 呢?根本原因是为了代码复用,增加可维护性。那么怎么才能实现代码复用呢?

开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。其他几条,则可以看做是开闭原则的实现方法。设计模式就是实现了这些原则,从而达到了代码复用、增加可维护性的目的。
在这里插入图片描述
高内聚、低耦合、可扩展、可复用。

3.1 单一职责原则(Single Responsibility Principle)

-== 一个类只负责一个职责。==

  • 代码的粒度降低了,类的复杂度降低了。
  • 可读性提高了,每个类的职责都很明确,可读性自然更好。
  • 可维护性提高了,可读性提高了,一旦出现 bug ,自然更容易找到他问题所在。
  • 改动代码所消耗的资源降低了,更改的风险也降低了。

3.2 开闭原则(Open Closed Principle)

  • 对扩展开放,对修改关闭。
  • 在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。
  • 实现“开-闭”原则的关键步骤就是抽象化。
    开闭原则是面向对象的可复用设计的第一块基石,它是最重要的面向对象设计原则。开闭原则由 Bertrand Meyer 于1988年提出,其定义如下:
  • 开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
  • 在开闭原则的定义中,软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类。
  • 任何软件都需要面临一个很重要的问题,即它们的需求会随时间的推移而发生变化。当软件系统需要面对新的需求时,我们应该尽量保证系统的设计框架是稳定的。如果一个软件设计符合开闭原则,那么可以非常方便地对系统进行扩展,而且在扩展时无须修改现有代码,使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。随着软件规模越来越大,软件寿命越来越长,软件维护成本越来越高,设计满足开闭原则的软件系统也变得越来越重要。
  • 为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。在Java、C#等编程语言中,可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。在很多面向对象编程语言中都提供了接口、抽象类等机制,可以通过它们定义系统的抽象层,再通过具体类来进行扩展。如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。

3.3 里氏代换原则(Liskov Substitution Principle)

  • 面向对象设计的基本原则之一。
  • 任何基类可以出现的地方,子类一定可以出现。
  • LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
  • 里氏代换原则是对“开-闭”原则的补充。
  • 基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。其严格表述如下:

如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。这个定义比较拗口且难以理解,因此我们一般使用它的另一个通俗版定义:

里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。

里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠。

例如有两个类,一个类为BaseClass,另一个是SubClass类,并且SubClass类是BaseClass类的子类,那么一个方法如果可以接受一个BaseClass类型的基类对象base的话,如:method1(base),那么它必然可以接受一个BaseClass类型的子类对象sub,method1(sub)能够正常运行。反过来的代换不成立,如一个方法method2接受BaseClass类型的子类对象sub为参数:method2(sub),那么一般而言不可以有method2(base),除非是重载方法。

里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

在使用里氏代换原则时需要注意如下几个问题:

  1. 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
  2. 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。
  3. Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。

3.4 迪米特法则(最少知道原则)(Law of Demeter)

最小知识原则(Principle of Least Knowledge,PLK,也叫迪米特法则)
迪米特法则来自于1987年美国东北大学(Northeastern University)一个名为“Demeter”的研究项目。迪米特法则又称为最少知识原则(LeastKnowledge Principle, LKP),其定义如下:

迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

  • ==一个实体应当尽量少的与其他实体之间发生相互作用,==使得系统功能模块相对独立。

如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。

迪米特法则还有几种定义形式,包括:不要和“陌生人”说话、只与你的直接朋友通信等,在迪米特法则中,对于一个对象,其朋友包括以下几类:

  1. 当前对象本身(this);
  2. 以参数形式传入到当前对象方法中的对象;
  3. 当前对象的成员对象;
  4. 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
  5. 当前对象所创建的对象。
    任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。在应用迪米特法则时,一个对象只能与直接朋友发生交互,不要与“陌生人”发生直接交互,这样做可以降低系统的耦合度,一个对象的改变不会给太多其他对象带来影响。

迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。

在将迪米特法则运用到系统设计中时,要注意下面的几点:在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。

3.5 接口隔离原则(Interface Segregation Principle)

  • == 使用多个隔离的接口,比使用单个接口要好。==
  • 接口隔离原则定义如下:

接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。这里的“接口”往往有两种不同的含义:一种是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象;另外一种是指某种语言具体的“接口”定义,有严格的定义和结构,比如Java语言中的interface。对于这两种不同的含义,ISP的表达方式以及含义都有所不同:

当把“接口”理解成一个类型所提供的所有方法特征的集合的时候,这就是一种逻辑上的概念,接口的划分将直接带来类型的划分。可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的一个接口,此时,这个原则可以叫做“角色隔离原则”。

如果把“接口”理解成狭义的特定语言的接口,那么ISP表达的意思是指接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。在面向对象编程语言中,实现一个接口就需要实现该接口中定义的所有方法,因此大的总接口使用起来不一定很方便,为了使接口的职责单一,需要将大接口中的方法根据其职责不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便,并都承担某一单一角色。接口应该尽量细化,同时接口中的方法应该尽量少,每个接口中只包含一个客户端(如子模块或业务逻辑类)所需的方法即可,这种机制也称为“定制服务”,即为不同的客户端提供宽窄不同的接口。

  • 降低依赖,降低耦合。

3.6 依赖倒置原则(Dependence Inversion Principle)

  • 开闭原则的基础。
  • 针对接口编程,依赖于抽象而不依赖于具体。
    如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要实现机制之一,它是系统抽象化的具体实现。依赖倒转原则是Robert C. Martin在1996年为“C++Reporter”所写的专栏Engineering Notebook的第三篇,后来加入到他在2002年出版的经典著作“Agile Software Development, Principles, Patterns, and Practices”一书中。依赖倒转原则定义如下:

** 依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。**

依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。

在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能,满足开闭原则的要求。

在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象

3.7 合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)

合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),其定义如下:

合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合,而不是继承来达到复用的目的。

合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能的目的。简言之:复用时要尽量使用组合/聚合关系(关联关系),少用继承。

在面向对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承,但首先应该考虑使用组合/聚合,组合/聚合可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。

通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;而且继承只能在有限的环境中使用(如类没有声明为不能被继承)。

二、 WPF中使用的设计模式

以下是模式的简要说明:

1)MVVM - 用作模型转换器并替代代码隐藏.提高了可测试性,为ViewModel编写单元测试要容易得多.

2)依赖注入 - 用于提高类的可测试性(您可以将特定类的单元测试与其他类别分开编写)以及以更简单的方式更改实现的可能性(更改记录器,缓存提供程序,Web服务等)

3)命令 - 默认情况下可以应用于Button和MenuItem控件,如果无法执行操作,则禁用控件.也用于MVVM模式,作为代码隐藏事件的替代.

WPF中已经使用的经典书中的其他模式:

  • 辛格尔顿.WPF中的Application类和Web表单中的HttpContext类.
  • 适配器.数据绑定引擎,它使用IValueConverter接口转换UI的绑定值.
  • 装饰.Border类,它使用可变厚度和颜色的边框来装饰任何UIElement类.
  • 正面.PrintDialog类,它提供了一个简单的界面,使您可以使用WPF提供的整个打印和文档子系统.
  • 命令.ICommand接口,由RoutedCommand和RoutedUICommand类实现.
  • 迭代器.IEnumerator接口,.NET Framework中的许多集合和列表都可以实现.
  • 观察员.INotifyPropertyChanged接口和事件.

三、设计模式分类

3.1 设计模式关系图

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.2 设计模式使用频次

创建型模式(6个)
在这里插入图片描述
结构型模式(7个)
在这里插入图片描述
行为型模式(11个)
在这里插入图片描述

3.3 创建型模式(5种)

  • 处理对象创建,试图根据实际情况使用合适的方式创建对象。
  • 将系统使用的具体类封装。
  • 隐藏具体类实例创建和组合方式。

包含:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

3.4 结构型模式(7种)

  • 借由一以贯之的方式来了解元件间的关系,以简化设计。

包含:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
在这里插入图片描述

3.5 行为型模式(11种)

  • 用来识别对象之间的常用交流模式并加以实现。

包含:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
在这里插入图片描述

3.6 其它(2种)

并发型模式、线程池模式。

观察者模式

常使用事件(event)进行解耦,外部代码通过订阅事件来解耦,实现对内部状态的观察

在 Process 类中有很多事件,可以用来捕获另一个进程中的输出,错误等

public event DataReceivedEventHandler OutputDataReceived;

public event DataReceivedEventHandler ErrorDataReceived;

通常这两个事件我们就可以获取到另外一个进程中的输出信息,除此之外还有很多的类在使用事件,相信你也用过很多

迭代器模式

迭代器模式将一个聚合对象保存数据的职责,和它遍历数据的职责分离,在引入迭代器后,聚合对象只负责保存数据,而遍历数据交给迭代器来完成。迭代器模式的介绍将参考.NET中的迭代器来进行说明。

模式产生的原因
在软件开发中,为了可以更加方便的操作聚合对象,同时可以很灵活的为聚合对象增加不同的遍历方法,我们需要一个解决方案可以让我们访问一个聚合对象但又不需要暴露它的内部结构。迭代器模式为我们解决了这个问题。它提供一种可以顺序访问一个聚合对象各个元素的方法且不会暴露该对象的内部表示。

模式的灵感来源
在现实生活中,人们有两种方法来操作电视机实现开机,关机,换台等操作。

一种方法是使用电视机本身提供的操作面板上的按钮来说直接实现,
另一种则是通过遥控器来间接控制电视机。

遥控器的出现为电视机的操作带带来了极大的方便,用户并不需要知道电视机中是怎么存储频道的就可以实现换台。

在这里插入图片描述
由图可知,迭代器模式由4个对象构成:

  • Aggregate(抽象聚合对象):

抽象聚合对象是所有的聚合对象的父类,它提供了一个获得迭代器的方法,在.NET中这个方法被封装在了另一个接口中,其实为了更加彻底的解耦,我们也可以采用.NET的方法,将GetIterator单独封装在一个接口中。

  • ConcreteAggregate(具体聚合对象):

具体的聚合对象,负责存储数据和获得迭代器,以方便遍历。

  • AbstractIterator(抽象迭代器):

抽象迭代器中有如何遍历聚合对象的基础方法,在具体迭代器中我们需要实现这些方法。

  • ConcreteIterator(具体迭代器):

具体迭代器负责实现抽象迭代器中的方法,并且要实现自己独有的遍历方法,一般我们可以在具体迭代器中书写当前迭代器的迭代逻辑

// 聚集抽象
public interface IEnumerable
{
    /// <summary>Returns an enumerator that iterates through a collection.</summary>
    /// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
    IEnumerator GetEnumerator();
}

// 迭代器抽象
public interface IEnumerator
{
    /// <summary>Advances the enumerator to the next element of the collection.
    /// </summary>
    /// <returns>
    /// <see langword="true" /> if the enumerator was successfully advanced to the next element; <see langword="false" /> if the enumerator has passed the end of the collection.</returns>
    /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created.</exception>
    bool MoveNext();

    /// <summary>Gets the element in the collection at the current position of the enumerator.</summary>
    /// <returns>The element in the collection at the current position of the enumerator.</returns>
    object Current { get; }

    /// <summary>Sets the enumerator to its initial position, which is before the first element in the collection.</summary>
    /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created.</exception>
    void Reset();
}

Array 和 List 各自实现了自己的迭代器,感兴趣可以去看下源码

  • 抽象迭代器类:
using System.Collections.Generic;

namespace Iterator.Iterator.Question3
{
    public abstract class MyIterator
    {
        protected List<int> MyList;
        protected int Index;
        public MyIterator(string name, List<int> list)
        {
            MyList = list;
        }

        public abstract bool MoveNext();

        public abstract object Current();

        public abstract void Reset();
    }
}

  • 获得迭代器接口:
namespace Iterator.Iterator.Question3
{
    public interface INewGetIterator
    {
        MyIterator GetIterator();
    }
}

  • 迭代器代理类:
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Iterator.Iterator.Question3
{
    public class MCollectionAgent : MyIterator
    {
        private MyIterator _iterator;
        public MCollectionAgent(string name, List<int> list) : base(name, list)
        {
            switch (name)
            {
                case "AsOrder":
                    _iterator = new AsOrder(name, list);
                    break;
                case "DesOrder":
                    _iterator = new DesOrder(name, list);
                    break;
            }
        }

        public override bool MoveNext()
        {
            return _iterator.MoveNext();
        }

        public override object Current()
        {
            return _iterator.Current();
        }

        public override void Reset()
        {
            _iterator.Reset();
        }
    }
}

抽象聚合对象类:

using System.Collections.Generic;
using Iterator.Iterator.Example;

namespace Iterator.Iterator.Question3
{
    public abstract class CollectionObject : INewGetIterator
    {
        protected List<int> MyList;

        public abstract MyIterator GetIterator();
    }
}

具体聚合对象A:

using System.Collections.Generic;

namespace Iterator.Iterator.Question3
{
    public class CollectionA : CollectionObject
    {
        private string _name;

        public CollectionA(string name)
        {
            _name = name;
            MyList = new List<int>()
            {
                1,2,3,4,5,6,7,8,9
            };
        }
        
        public override MyIterator GetIterator()
        {
            return new MCollectionAgent(_name, MyList);
        }
    }
}

升序遍历迭代器:

using System.Collections.Generic;

namespace Iterator.Iterator.Question3
{
    public class AsOrder : MyIterator
    {

        public override bool MoveNext()
        {
            Index++;
            if (Index < MyList.Count)
            {
                return true;
            }
            return false;
        }

        public override object Current()
        {
            return MyList[Index];
        }

        public override void Reset()
        {
            Index = -1;
        }

        public AsOrder(string name, List<int> list) : base(name,list)
        {
            Index = -1;
        }
    }
}

降序遍历迭代器:

using System.Collections.Generic;

namespace Iterator.Iterator.Question3
{
    public class DesOrder : MyIterator
    {
        public DesOrder(string name, List<int> list) : base(name, list)
        {
            Index = MyList.Count;
        }

        public override bool MoveNext()
        {
            Index--;
            if (Index >= 0)
            {
                return true;
            }

            return false;
        }

        public override object Current()
        {
            return MyList[Index];
        }

        public override void Reset()
        {
            Index = MyList.Count; 
        }
    }
}

Program类:

using System;
using Iterator.Iterator.Question3;

namespace Iterator
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            //降序遍历
            CollectionObject collectionObject = new CollectionA("DesOrder");
            MyIterator iterator = collectionObject.GetIterator();
            while (iterator.MoveNext())
            {
                Console.WriteLine(iterator.Current());
            }
        }
    }
}

迭代器模式总结
迭代器模式的优点:
加粗样式迭代器模式支持以不同的方式遍历一个聚合对象,在同一个聚合对象中可以定义多种遍历方法。
迭代器简化了聚合对象,现在的聚合对象将不需要再考虑自己提供数据遍历的方法。
迭代器模式的缺点:
迭代器模式的使用会增加系统的复杂性。
迭代器模式的设计难度比较大,需要充分考虑系统将来的扩展。

命令模式#

命令模式是指:通过把一个请求封装成一个对象,从而可以使用不同的请求例如命令等将客户端参数化;可以将请求排队或者记录请求日志,可以支持请求撤销操作;命令模式是一个对象行为模式,别名为动作模式或者事务模式;
命令模式在将一个请求封装成一个对象后,无需了解请求激活的动作或者接收该请求后处理的细则;
这是一种两台机器之间通信性质的模式,命令模式解耦了发送者和接受者之间的联系,发送者调用一个操作,接收者执行该操作,发送者无需知道接收者对于该操作的任何接口。

单请求单接收
在这里插入图片描述
接口说明:

  • Invoker:命令调用者,它通过命令来发送请求;
  • Command:抽象命令对象,它通常是一个接口或者一个抽象类,其中声明了一个execute()方法来调用命令接受者;
  • ConcreteCommand:命令对象,抽象命令的实现者,其内通过会依赖一个命令接收者,该命令接收者执行具体的动作;
  • Receiver: 命令接受者,它通常是命令具体执行者,其内会有一个action()方法,该方法是对请求的业务处理;

单请求多接收

其中将多个命令封装到一个List中,从而实现一个请求将有多个接受者进行处理;

在这里插入图片描述
请求支持撤销动作
在这里插入图片描述
-其中在Command方法中加入撤销动作undo(),即可实现该命令的撤销;

策略模式#

策略模式定义了一系列的算法,并且将每个算法封装成一个对象,同时这些算法还可以相互替代。

asp.net core 中的认证和授权,我觉得就是策略模式的应用,在使用 [Authorize] 的时候会使用默认的 policy,也可以指定要使用的策略 [Authorize(“Policy1”)] 这样就会使用另外一种策略 Policy1,policy 还是比较简单的

policy 是用来根据用户的认证信息来控制授权访问的,而认证则是根据当前上下文(请求上下文、线程上下文、环境上下文等)的信息进行认证从而获取用户信息的过程

而不同的认证模式(Cookie/JWT/自定义Token等)其实是不同的处理方法,也就是策略模式中不同的算法实现,指定哪种认证模式,就是使用哪种算法实现来获取用户信息

在这里插入图片描述
说明:

  • Context:需要使用ConcreteStrategy提供的算法,并且其内部维护了一个Strategy的实例,同时负责动态设置运行时Strategy具体算法,还要负责Strategy之间的交互和数据传递;
  • Strategy:定义了一个公共接口,各种算法实现该接口,Context使用该接口来实现不同的算法,一般为一个抽象类或者公共接口;
  • ConcreteStrategy:算法具体实现类;
    在这里插入图片描述
namespace CLZReportGen.ReportUtility
{
    /// <summary>
    /// 环境类
    /// </summary>
    public class ReportContext
    {
        public DateTime dtFrom, dtTo;
        public CustomLogger logger;

        public CLZ_CELUE cELUE;
        //持有抽象策略角色的引用,用于客户端调用
        private IStrategy strategy;
        public IStrategy GetStrategy()
        {
            return strategy;
        }
        //设置所需策略
        public void SetStrategy(IStrategy strategy)
        {
            this.strategy = strategy;
        }
        //根据设置的策略类返回对应的结果
        public void ExecStrategyMethod(string reportType)
        {
            strategy.Exec(dtFrom, dtTo, cELUE, logger);
        }

    }
}


namespace CLZReportGen.ReportUtility
{
    public interface IStrategy
    {

         bool Exec(DateTime dtFrom, DateTime dtTo, CLZ_CELUE cELUE,CustomLogger logger);
      
    }

}

namespace CLZReportGen.ReportUtility
{

    public class MReport : IStrategy
    {
        public bool Exec(DateTime dtFrom, DateTime dtTo, CLZ_CELUE cELUE, CustomLogger logger)
        {
            MReportModel mReportModel = new MReportModel(logger, dtFrom, dtTo, cELUE);

            return mReportModel.templateMethod();
        }
    }
}
namespace CLZReportGen.ReportUtility
{
    public class QReport : IStrategy
    {
        public bool Exec(DateTime dtFrom, DateTime dtTo, CLZ_CELUE cELUE, CustomLogger logger)
        {
            throw new NotImplementedException();
        }
    }
}

namespace CLZReportGen.ReportUtility
{
    public class YReport : IStrategy
    {
        public bool Exec(DateTime dtFrom, DateTime dtTo, CLZ_CELUE cELUE,CustomLogger logger)
        {
            throw new NotImplementedException();
        }
    }
}

namespace CLZReportGen.ReportUtility
{
  public  class CallReport
    {
        /// <summary>
        /// 执行月报生产策略
        /// </summary>
        /// <param name="dtFrom"></param>
        /// <param name="dtTo"></param>
       public static void CallMReportTask(DateTime dtFrom, DateTime dtTo, CLZ_CELUE cELUE, CustomLogger logger)
        {
            ReportContext reportContext = new ReportContext();
            IStrategy strategy = new MReport();
            strategy.Exec(dtFrom, dtTo, cELUE,logger);
   ;
         
        }
       
    }
}

策略模式和命令模式区别

  • 目标不同: 策略模式主要针对同一动作,才用不同的实现方式,而命令模式是针对不同的命令,有不同的接收者来实现;
  • 主体不同:策略模式的主体是具体的应用对象,例如排序器,可以根据不同的场景才用不同的排序方式;而命令模式的主体是请求发送者和请求接受者,例如遥控器和空调,遥控器会有空凋相应的指令,来命令空调执行相应的方法;

责任链模式#

asp.net core 中间件的设计就是责任链模式的应用和变形,每个中间件根据需要处理请求,并且可以根据请求信息自己决定是否传递给下一个中间件,我也受此启发,封装了一个 PipelineBuilder 可以轻松构建中间件模式代码

app.UseStaticFiles();

app.UseResponseCaching();
app.UseResponseCompression();

app.UseRouting();

app.UseCors(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapControllerRoute(name: "areaRoute", "{area:exists}/{controller=Home}/{action=Index}");
    endpoints.MapDefaultControllerRoute();
});

PipelineBuilder 实际示例:

var requestContext = new RequestContext()
{
    RequesterName = "Kangkang",
    Hour = 12,
};

var builder = PipelineBuilder.Create<RequestContext>(context =>
        {
            Console.WriteLine($"{context.RequesterName} {context.Hour}h apply failed");
        })
        .Use((context, next) =>
        {
            if (context.Hour <= 2)
            {
                Console.WriteLine("pass 1");
            }
            else
            {
                next();
            }
        })
        .Use((context, next) =>
        {
            if (context.Hour <= 4)
            {
                Console.WriteLine("pass 2");
            }
            else
            {
                next();
            }
        })
        .Use((context, next) =>
        {
            if (context.Hour <= 6)
            {
                Console.WriteLine("pass 3");
            }
            else
            {
                next();
            }
        })
    ;
var requestPipeline = builder.Build();
foreach (var i in Enumerable.Range(1, 8))
{
    Console.WriteLine();
    Console.WriteLine($"--------- h:{i} apply Pipeline------------------");
    requestContext.Hour = i;
    requestPipeline.Invoke(requestContext);
    Console.WriteLine("----------------------------");
    Console.WriteLine();
}

建造者模式

asp.net core 中的各种 Builder, HostBuilder/ConfigurationBuilder 等,这些 Builder 大多既是 Builder 又是 Director,Builder 本身知道如何构建最终的 Product(Host/Configuration)

var host = new HostBuilder()
    .ConfigureAppConfiguration(builder =>
    {
        // 注册配置
        builder
            .AddInMemoryCollection(new Dictionary<string, string>()
            {
                {"UserName", "Alice"}
            })
            .AddJsonFile("appsettings.json")
            ;
    })
    .ConfigureServices((context, services) =>
    {
        // 注册自定义服务
        services.AddSingleton<IIdGenerator, GuidIdGenerator>();
        services.AddTransient<IService, Service>();
        if (context.Configuration.GetAppSetting<bool>("XxxEnabled"))
        {
            services.AddSingleton<IUserIdProvider, EnvironmentUserIdProvider>();
        }
    })
    .Build()
    ;

原型模式

dotnet 中有两个数据结构 Stack/Queue 这两个数据都实现了 ICloneable 接口,内部实现了深复制
来看 Stack 的 Clone 方法实现:

public virtual Object Clone()
{
    Contract.Ensures(Contract.Result<Object>() != null);
 
    Stack s = new Stack(_size);
    s._size = _size;
    Array.Copy(_array, 0, s._array, 0, _size);
    s._version = _version;
    return s;
}

组合模式#

WPF、WinForm 中都有控件的概念,这些控件的设计属于是组合模式的应用,所有的控件都会继承于某一个共同的基类, 使得单个对象和组合对象都可以看作是他们共同的基类对象

适配器模式#

之前介绍的适配器模式中有提到 asp.net core 3.x 里引入了 ServiceProviderFactory, 使得 .net core 可以更方便的集成第三方的依赖注入框架,这里使用了适配器模式通过 ServiceFactoryAdapter 来适配各种不同的第三方的依赖注入框架

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是刘彦宏吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值