设计模式(五)行为型模式

设计模式(五)行为型模式,第1张

在上一篇结构型模式中,我们以功能为基本单位,研究了一些设计模式,用于实现功能转换、功能组合、功能封装等目的。

我们知道,面向对象编程有两个核心元素:对象、对象间通信协作。从面向对象的角度看,任何系统和功能,都是由一个个对象,相互分工合作实现的。推而广之,很多系统也都是这样组织和运行的。

本章的设计模式,列举了通用场景下常用功能机制的经典实现方法,讲解了经典实现中是如何高效组织对象、控制对象协作交互的,具有很好的参考价值。

示例:https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html

责任链模式,就是把程序处理对象前后排列起来,形成一条处理线。处理线上需要被处理的信息,在处理线上向下传递,任何一个节点都可以随时中断传递。

GUI系统中的事件传递机制(在Javascript中叫做事件冒泡),是责任链模式最典型的应用之一。

当某一事件发生时,最顶层GUI对象会首先收到事件,但是它先不处理,而是依次交给命中的子GUI对象处理。当子GUI对象返回为False时,表示事件未被接收,此时父GUI对象才真正对发生的事件进行业务处理。

可以看出,事件传递机制,是一种增强版的责任链模式,它的节点处理权,经历了向下和向上的双向传递过程。

总结:当项目中一个数据对象,需要被多个处理对象进行处理时,可以将处理对象链接起来,然后把数据对象传递给头节点,随着处理的进行,数据对象的处理权会在处理链中流动,从而完成整个处理过程。

责任链模式结构适用于需求固定的场景,用于实现简单高效的处理机制。假如需求不断变化,而且功能很复杂,那么用责任链模式很可能就无法胜任了,需要采用新的高复杂度的设计。例如,如果想要数据对象在所有处理对象中根据状态来实现跳转,可以选择使用状态机等其他方案来实现。

示例:https://www.runoob.com/design-pattern/command-pattern.html

想要实现撤销、重做、事务等功能,可以使用此设计模式。通常在编辑器、数据库中有此类功能需求。

命令也就是请求,或者叫调用。命令模式要求将请求参数和请求相关的方法封装在一起。

请求对象中封装了实现“撤销”、“重做”、“事务”功能所需要的所有信息,实现了关联信息的高内聚,所以可以实现我们想要的功能。

例如,可以在请求对象中保存修改之前的值、修改之后的值。利用修改之前的值,可以实现“撤销”功能;利用修改之后的值,可以实现“重做”功能。如果将所有请求对象都记录下来,并按照先后顺序排列起来,形成“撤销重做”堆栈,这样就可以实现连续的“撤销”、“重做”。“事务”则是“撤销”与“重做”的结合体,正常执行流程等同于“重做”,发生错误需要回滚,等同于“撤销”。

如果不采用这种方式,会导致实现这些功能的信息,分散在源码中多个地方,或者已经丢失,没有保存,就无法实现“撤销”、“重做”、“事务”功能。

同时,实现请求参数高内聚,也可以很方便地将它们保存到磁盘上,保存到文件的过程叫做“序列化”,从文件中读取的过程叫“反序列化”。这里的序列指的就是二进制流。

Qt中与命令模式相关的部分是:Undo Framework,里面有示例项目,不熟悉的同学可以抽点时间看一看。

示例:https://www.runoob.com/design-pattern/interpreter-pattern.html

顾名思义,解释器模式是用来实现解释器的。 解释器是这样一个程序:解释器以符合语法的文本为输入,解释输入内容,完成一定的计算功能。文本可以在程序运行时动态加载,动态解释、动态执行。

实现简单的解释器:命令行程序,如ping命令、cd命令等; 实现复杂的解释器:脚本语言解释器,如python,lua,javascript;计算器。

我们知道,在GUI图形用户界面被发明之前,人类和程序之间的交互是通过敲命令行实现的,缺点是使用难度较大,门槛较高。 在GUI发明以后,交互更加友好,电脑更加易于使用了,所以也更加普及了。

但是GUI交互的缺点在于,不够灵活,对参数的控制粒度不够细致。例如,现在大多数开发者都使用集成开发环境来开发软件,一般情况下都使用默认参数,比较方便。但是如果你想要更改某些编译选项,可能还是需要直接修改底层的编译命令。命令相对于GUI元素更加灵活,过于灵活的地方用GUI比较难于实现,例如组合、递归、跳转等等。在这些场景下,使用解释器是非常合适的。但是通常情况下,这个模式并不常用。

示例:https://www.runoob.com/design-pattern/iterator-pattern.html

在需要多次遍历同一个数据集合的时候,为了少些一些for,或者想要把遍历过程封装起来,降低耦合,就可以使用迭代器模式。这个模式非常常用。

迭代器就是一个专门用来遍历数组的类。它只需要实现两个接口:hasNext()、next()。 hasNext()接口用于控制循环何时停止;next()接口用于取出当前位置的数据元素,并将遍历指针指向下一个元素。 当然,构造迭代器对象的时候,需要将数据集合传递给迭代器,让迭代器知道要遍历哪些数据。

原本需要用for循环来遍历的代码,现在通过封装,提取出了“遍历”这一功能所需要的必要信息,定义了两个接口,把不必要暴露的信息封装在了迭代器中,妥妥的实现了解耦。

示例:https://www.runoob.com/design-pattern/mediator-pattern.html

中介者模式是指,在原本直接通信的对象之间,添加一个通信中间层,使对象间通信变为间接通信,降低对象间的耦合。

此模式和代理模式基本思想上是一致的。二者的区别是:代理模式是通过加一个中间层,来实现两个原本很难交互的功能主体,实现顺畅交互;中介者模式是为了降低对象间通信时的耦合而提出的,为的是提高代码的可维护性。

比较大的项目中会用到,一般存在于某些框架中。因为大的项目中对象繁多,通信也比较复杂,适合使用中介者模式。

在大的项目中,一般会有一个全局的通信管理器,任何对象都可以使用通信管理器提供的接口,将自己注册为某一个具有唯一ID消息的发送者和接收者。这样发送者只需要发送消息,不需要管谁来接收,不需要拥有发送者的实例指针,发出消息后,已注册的接收者都会收到消息。接收者不需要管信号是谁发的,即不需要拥有发送者的实例指针。

所以,中介者模式也可以叫“通信中介模式”。

示例:https://www.runoob.com/design-pattern/memento-pattern.html

这个模式和状态存档功能是绑定在一起的。为了在程序中实现状态存档功能,可以使用备忘录模式。

原例子中有三个类,个人觉得没有必要,这里我们简化成两个类,即备忘录模式中有两个类:状态对象类和状态对象管理类。 状态对象类是状态字段是集合,并提供了存取接口;状态对象管理类负责组织和保存状态对象。当然实际实现中可以根据需求增加类,配合使用,完成状态保存恢复。

当一个对象会影响到其他多个对象时,即当对象间存在一对多关系时,使用观察者模式。 一般应用于单向通知的场景,如GUI中鼠标事件、按键事件、窗口事件通知。使用Qt中的信号槽机制可以实现此模式。

“一”是指发生变化的那个对象,“多”是指需要获取此变化通知的对象组。其中,变化消息是单向地由“一”到“多”传递的。如果不是单向的或者对象间不是一对多的关系,更加复杂,就需要重新思考其他对象间通信模型。

如果不使用此模式,可能会导致观察者不能动态增加或删除;可能会造成发送者的业务代码和接收者的响应代码混在一起,耦合严重。

使用此模式,需要为观察者设计一个基类,并设计一个接收通知的接口,所有观察者需要实现通知接口;所有观察者指针可以保存在队列中,实现动态增删。

状态模式用于实现状态机。 如果一个程序功能中存在某些状态,在一定情况下,这些状态可以互相转换,并且在转换前后需要作出对应的 *** 作,这种情况下使用状态机来实现就非常合适。

如果不使用状态机(状态模式),一般的实现方法是使用一连串的if-else,或者使用长长的switch-case来实现。这样做的缺点,一方面状态判断不够高效,另一方面是业务代码集中在一块,不好维护。

使用状态机,每个状态都是一个类,相关的业务代码分布到各自的状态类中,能够实现不同的状态及与状态相关的业务代码解耦。同时某个状态和下一个状态是关联好的,在状态切换时,效率更高,不需要执行长长的判断。

Qt中已实现状态机框架,The State Machine Framework,在此框架下,我们可以更加专注于业务实现,而不是状态机本身的技术细节。

示例:https://www.runoob.com/design-pattern/null-object-pattern.html

使用基类保存子类对象通常有两种做法:

第一种方法用指针是基本方法,但是指针用起来要非常小心,要考虑内存释放的问题。此时空对象就可以用空指针表示。 第二种方法用基类对象保存子类对象,这种方法使用起来相对省心,不用与指针打交道,使用者不用直接管理内存。例如Qt中的Qt XML C++ Classes类的设计就是采用这种方式设计的。这种情况下,因为不使用指针,就需要使用空对象来代替空指针。

可以仿造Qt XML中的类进行设计。一般需要提供isNull()接口,对象类型转换接口等。

策略模式和桥接模式类似,用于实现功能切换与组合。二者区别在于,策略模式专注于一个功能的不同实现方式;桥接模式专注于多个功能之间的组合。

将功能抽象成单独的类,功能切换只需要切换不同的功能子类即可,同一个功能需要实现同一个功能接口。

示例:https://www.runoob.com/design-pattern/template-pattern.html

模板模式应该是我们最熟悉的。 这里的模板就是接口类,接口类定义了使用者和功能提供者之间交互的函数列表。子类负责功能的具体实现。

示例:https://www.runoob.com/design-pattern/visitor-pattern.html

访问者模式用于将数据结构与数据 *** 作相分离。

访问者模式和迭代器模式类似。迭代器模式一般用来遍历数组,所以没有把for封装起来。而访问者模式可以遍历一切类型的数据结构,具体的遍历过程被封装在接收者内部。同时,对每一个遍历得到的数组元素的 *** 作,被封装在访问者内部。每一种对元素不同的 *** 作,都需要新建一个访问者类。

接收者需要实现accept()接口,访问者需要实现visit()接口。

每种设计模式都有使用场景,都有优点和缺点。随着需求的改变,任何一种设计模式可能都将不再适用。

这个很简单 可以用DataView或者是DataTable

DataView表示用于排序、筛选、搜索、编辑和导航的 DataTable 的可绑定数据的自定义视图。可以将DataView同数据库的视图类比,不过有点不同,数据库的视图可以跨表建立视图,DataView则只能对某一个DataTable建立视图。DataView一般通过DataTable.DefaultView 属性来建立,再通过RowFilter 属性和RowStateFilter 属性建立这个DataTable的一个子集。

DataView类用来表示定制的DataTable的视图。DataTable和DataView的关系是遵循著名的设计模式--文档/视图模式,其中DataTable是文档,而Dataview是视图。

在任何时候,你都可以有多个基于相同数据的不同的视图。更重要的是,你可以对每一个具有自己一套属性、方法、事件的视图作为独立的对象进行处理。这也代表了相对ADO一个巨大的飞跃。

public DataView()

public DataView(DataTable)

DataView只有同已经存在的、很可能是非空的DataTable对象连接后才可用。通常,这个连接在构造时就指定了。

DataView dvdv = new DataView(theDataSet.Tables["Employees"])

但是,你也可以先创建一个新的视图,然后再用Table属性同表相关联。

DataView dv = new DataView()dv.Table = theDataSet.Tables["Employees"]DataView构造函数使你由DataTable中得到一个DataView对象。如果需要,反之亦可。事实上,DataTable对象的DefaultView属性返回一个该表的DataView对象。 DataView dv = dt.DefaultView

一旦你有了DataView对象, 你可以利用它的属性来建立你希望用户见到的数据行集。一般,你可以使用下列属性:

RowFilter

Sort

前者可以定制视图中可见数据应匹配的规则。而后者通过表达式来进行排序。当然你可以使用这两者的任意组合。

设置过滤RowFilter是一个可读写的属性,用来读取和设置表过滤的表达式。

public virtual string RowFilter {getset}

你可以用列名,逻辑和数字运算符和常量的任意合法组合组成表达式。以下是一些例子:

dv.RowFilter = "Country = 'USA'"dv.RowFilter = "EmployeeID >5 AND Birthdate <#1/31/82#" dv.RowFilter = "Description LIKE '*product*'"

让我们来看一下过滤器的基本规则和运算符。

过滤字符串是表达式的逻辑连接。可以用AND,OR,NOT来连接成一个较短的表达式,也可以使用圆括号来组成子句,指定优先的运算。

通常包含列名的子句同字母、数字、日期或另一个列名进行比较。这里,可以使用关系运算符和算术运算符,如>=, <, >, +, *, % (取模)等等。

如果要选取的行并不能方便地通过算术或逻辑运算符表达,你可以使用IN *** 作符。以下代码显示如何选取一个随机行:

dv.RowFilter = "employeeID IN (2,4,5)"

你也可以使用通配符*和%,它们同LIKE运算符一起使用时显得更有用。它们都表示任意数量的字符,可以相互替代使用。

请注意,如果在LIKE子句中已经有了*或%字符,你必须用方括号将其括起,以免歧义。如果很不幸,字符串中方括号本身也存在了,那么它也必须用将本身括起。这样,匹配语句会如下所示:

dv.RowFilter = "Description LIKE '[[]*[]]product[[]*[]]"

通配符只允许在过滤字符串的开头或结尾处使用,而不能在字符串中间出现。例如,下列语句会产生运行时错误:

dv.RowFilter = "Description LIKE 'prod*ct"

字符串必须以单引号括起,而日期型必须以#符号括起。字符型值可以使用小数点和科学计数法。

RowFilter也支持聚合函数,如SUM, COUNT, MIN,MAX, and AVG。如果表中没有数据行,那么函数将返回NULL。

在介绍RowFilter表达式的最后,让我们讨论三个很便利的函数:Len,IIF和Substring。

正如其名,Len()返回特定表达式的长度。该表达式可以是一个列名,也可以是其他合法的表达式。

Substring()返回指定的表达式自特定位置开始,特定长度的字符子串。

我最喜欢用的是IIF(),它按照逻辑表达式的值有一到两个值。IIF是IF-THEN-ELSE语句的紧凑表达。语法如下:

IIF(expression, if_true, if_false)

通 过该函数,可以建立非常复杂的过滤字符串。例如,假定你从SQL Server的Northwind数据库中取得Employees表,下列表达式可以选出那些employeeID小于6且lastname为偶数个字符 和employeeID大于6且lastname为奇数个字符的员工。

IIF(employeeID<6, Len(lastname) %2 =0, Len(lastname) %2 >0)

预排视图在上面的举例中,datagrid必须负责预排视图中的数据行,以便刷新用户界面。这个自动机制是.NET

数据绑定的产物。Datagrid是通过DataSource属性来获取数据的数据绑定控件。DataView是一个可数据绑定的类,可构建DataSource属性的内容。

如果你想使用datagrid之外的另一个控件,应该怎么办呢?又如果你不想使用自动数据绑定呢?应该怎样预排视图中所选的数据行呢?

DataView的Table属性指向相应的数据表,但DataTable并不保存过滤信息。所以,预排表中的数据注定是不可行的。虽然DataTable和DataView是紧密相联的,但它们各自保持独立,并执行独立的功能。

以下Visual Basic .NET代码段显示了如何遍历视图中所有的数据行,并加入到listbox中。

Dim dv As New DataView()

dv = ds.Tables("Employees").DefaultView

dv.RowFilter = "employeeid >5" ListBox1.Items.Clear() Dim buf As String Dim dr As DataRowView For Each dr In

dv buf = "" buf &= dr("lastname").ToString()&", " &dr("firstName").ToString() ListBox1.Items.Add(buf) Next

正如前面说提到的,DataView是可枚举的类,因此你可以安全的将它传给For..Each语句。Count属性存储了视图中数据行数,以便在For..Next循环中使用。

要访问视图中某一行,可以使用DataRowView类。DataRowView可表示DataRow的视图,就像DataView表达DataTable定制的视图一样。

总的来说,DataRow最多有四种状态:default,original,current和proposed。这些状态由DataRowVersion枚举类型设置,由RowVersion属性表达。

DataRow的视图只能是其中某一种状态。

数据行的默认(default)版本只有当其列在构造时设定了默认值时才有。而初始(original)版本是指在最后一次调用表的AcceptChanges后,从数剧源中得到数据行或快照。当前(Current)版本是指当前的数据行,包括所有当时发生的更新。Proposed状态只存在于调用BeginEdit和EndEdit的编辑过程中。

可以通过访问DataRow相同的语法访问DataRowView。这里最重要的属性叫Item。

排序和其他便捷的特性DataView支持Sort属性,可以用来对视图中的内容排序。Sort由用逗号分隔的列名表达式进行排序。通过在任何列名后加ASC或者DESC限定词,可以使得字段按照上升或者下降的顺序排列。如果没有方向限定词,默认顺序为ASC。

DataView是内存中的对象,所以排序在本地进行,无需调用数据库服务器。

RowStateFilter是DataView另一有趣的属性。它可以用任何预定义的标准来过滤DataTable中的内容。下表中是DataViewRowState枚举类型的所有取值:

CurrentRows 包括所有未更新的、新的和修改的数据行

Deleted 所有自上次调用AcceptChanges后删除的数据行

ModifiedCurrent 所有自上次调用AcceptChanges后修改过的数据行

ModifiedOriginal 所有自上次调用AcceptChanges后original版本的数据行

New 所有自上次调用AcceptChanges后新添加的行

OriginalRows 返回初始数据行,包含unchanged和deleted 的

Unchanged 所有未更新的数据行

如果要 *** 作非连接的数据,所有更新都在对DataTable调用AcceptChanges后生效。对单一行的更新在调用DataRow的AcceptChanges后生效。类似的,这些更新可以通过调用DataTable或DataRow对象的RejectChanges来取消。

DataView对象还有一些属性,如AllowEdit,AllowDelete和AllowNew,用来得到或设定是否允许更新的值。它们的默认值设为True,允许任何种类的更新。如果在标志设为False时,你想要完成相应的更新 *** 作,会有一个运行时错误发生


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/9885614.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-03
下一篇 2023-05-03

发表评论

登录后才能评论

评论列表(0条)

保存