日期:2023-01-24 阅读量:0次 所属栏目:软件技术
摘 要:摘要:委托是.NET中新增加的函数调用机制, 实现了函数调用的晚绑定,方便了程序员在程序开发时灵活的对函数进行调用,比起C++的函数指针,也有封装性好,安全性高的优点。委托的实现原理体现了设计模式的思想,委托比较适合框架开发,设计模式则是面向对象的思想,两者的侧重点有不同,但在有些应用场合,利用委托可以达到使用设计模式的效果。
关键词:关键词:.NET ; 委托;设计模式;函数指针
中图分类号:TP39 文献标识码:A 文章编号:
1. 委托探秘
在CLR中,委托是通过委托类来实现的, delegate编译过后会产生一个继承自MulticastDelegate的类。比如下面这段代码:
public delegate void DelegateMethod(int val)
编译器遇到这段代码时,会产生一个完整的类定义:
public class DelegateMethod:astDelegate
{
//构造器
public DelegateMethod(Object target, Int32 methodPtr);
//下面的方法和源代码中指定的原型一样
public void virtual Invoke(int val);
//下面两个方式是对委托的异步调用
public virtual IAsyncResult BeginInvoke(int val);
public virtual void EndInvoke(IAsyncResult result);
}
此外,父类MulticastDelegate中有三个重要的私有字段:
_target:指向回调函数被调用时应该被操作的对象
_methodPtr:clr用它来标识回调方法
_prev:指向另一个委托对象。当没有另一个委托对象时,该字段为null
下面从三个方面具体阐述:
1.1 委托的创建机制
委托的构造函数接收两个参数:一个对象引用和一个指向回调方法的整数。在编译时,编译器会通过分析源代码来确定引用的是哪个对象和方法。其中对象引用会被传递个target参数,一个特殊的标识方法的值会被传递个methodPtr参数,对于静态方法,null会被传递个target参数。构造器中,这两个参数会被保存在相应的私有字段中,并将_prev字段设置为null。
1.2委托的调用机制
当委托被调用时,实际调用的类中的Invoke方法。当Invoke被调用时,它使用_target和_methodPtr两个私有字段来在指定的对象上调用期望的方法。
BeginInvoke和EndInvoke是委托的异步调用。
1.3 委托链
C#编译器为委托类型实例提供了+=和-=操作符来简化委托链的构造。委托链的实现是依靠MulticastDelegate对象中的私有字段_prev,该字段指向另一个MulticastDelegate对象的引用,这使得多个委托对象可以组合成一个链表。
每当向委托链中使用“+”操作符添加新的委托时,新添加的委托对象的prev字段就会指向原有的委托对象。当调用委托链中的委托操作时,会先获取到最后添加的委托对象,然后根据其prev字段查找它之前的委托对象,只有前一个委托对象执行完操作后,才会执行当前的委托操作,所以遍历委托链上的操作是一个递归算法。
2. 委托中体现的设计模式思想
设计模式体现了设计思想,在软件开发中运用GOF面向对象设计模式,能实现软件模块的高内聚,低耦合,大大提高了软件的灵活性及可可扩展可维护性。
在软件开发中,运用设计模式是非常灵活的,由于设计模式体现的是设计思想,所以不要为想用设计模式而使用设计模式,先入为主的使用设计模式是不恰当的。在实际开发过程中,一方面往往通过已有项目经验采取适当的设计模式,另一方面,是循序渐进,不断改进的使用设计模式。 学好设计模式对掌握面向对象的软件开发思想大有裨益。
Delegate是.NET提供的委托机制,.NET将实现委托的细节封装起来,使得用户可以直接使用委托,而不需要知道内部实现原理。这样,委托的使用场景也就跳出了面向对象设计思想的范畴,从而变得更为灵活,
委托的内部实现折射出设计模式的思想,而且灵活运用设计模式,也能使我们编写出具有委托功能的软件模块。探讨委托与设计模式的关系,可以加强对设计模式的理解,并更好的理解委托的实现及用途。
2.1 Delegate与TemplateMethod(模板方法)模式
模板方法设计模式意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法设计模式结构:
在模板方法设计模式中,由父类(结构图中的AbstractClass)编写模板调用方法(结构图中的TemplateMethod),并声明模板调用中的每一个具体方法(结构图中的PrimtiveOperation),而这些具体的方法由子类(上图的ConcreteClass)实现。因此只要添加不同的子类,就可以用相同的调用方法执行不同的具体方法。
委托声明了一个函数签名标准,只要与该签名一致的函数,都可以由委托调用,与模板方法设计模式相对照,astDelegate类就是父类,被调用的函数所依附的委托类则是子类。MulticastDelegate中给出了Invoke,BeginInvoke,EndInvoke等委托基本操作的实现机制,而机制中具体运行的代码则有子类指向的函数代码段决定。
.NET中的Delegate在结构框架上运用了模板方法的设计思想。
2.2 Delegate与Command(命令)模式
命令设计模式意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
命令设计模式结构:
在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行“记录、撤销/重做(undo/redo)、事务”等处理,这将会产生大量的代码编写,从而导致代码的可维护性和可读性大大降低,因此这种无法抵御变化的紧耦合是不合适的。
命令模式将行为请求者与行为实现者解耦。它是将一组行为抽象为一个Command对象,由Command定义行为函数,由ConcreteCommand具体实现父类Command定义的函数。这样行为请求者要执行一组行为时,只需简单调用Command对象即可。
Command与Delegate有些类似,都解耦了“行为请求者”与“行为实现者”,都是采用迟绑定的思想调用“行为实现者”。Command设计模式中可以建立一个链表,由此存储要执行的命令对象,这与Delegate中的委托链有异曲同工之妙。
但两者定义行为接口的规范有所区别:Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,更符合抽象原则;Delegate以函数签名来定义行为接口规范,更灵活,但抽象能力较弱。
.NET的Delegate在处理“行
为请求者”与“行为实现者”的关系上运用了命令模式设计思想。
2.3 Delegate与Observer(观察者)模式
观察者设计模式的意图:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者设计模式的结构:
在调用委托方法时,会依次调用委托链中的委托对象方法。用户可以向委托链中添加或删除委托对象。
用委托机制实现的Event,就是典型的观察者模式的运用。事件触发时,可能会通知多个对象,但一开始设计时,事件不知道与多少对象有待改变,也不知道其它对象是谁,也就是,这些对象是松耦合的。用户可以自定义事件,以及相关的事件处理方法。当将方法添加到事件的委托链中后,事件一旦触发,则会遍历委托链中的所有事件处理函数。事件与处理事件的对象间是松耦合的。
Event运用了观察者模式设计思想。
2.4 Delegate与Strategy(策略)模式
策略设计模式的意图:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于是用它的客户而变化。
策略设计模式的结构:
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。策略设计模式将算法与对象本身解耦,能够在运行时根据需要透明地更改对象的算法。
策略设计模式要求这些能够进行更改的算法(结构图中的ConcreteStrategy)都继承自同一个父类(结构图中的Strategy),Delegate也可以实现在运行时根据需要更改算法的功能,但是要求这些包含算法算法的函数要有统一的函数签名。
在有些使用场合,委托和Strategy设计模式可以互换,但是Strategy的使用范围更广一些,体现的是面向对象设计的思想。
2.5 Delegate与State(状态)模式
状态设计模式的意图:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
状态设计模式的结构:
在软件构建过程中,某些对象的状态如果,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。状态设计模式将对象操作和状态转化之间解耦合,使得能够在运行时根据对象的状态来透明地更改对象的行为。
如果将每一个状态对应的操作都写在一个函数中,那么在状态切换时,只需委托调用不同的函数就能实现相应的操作。
在有些场合,委托可以实现State模式,方便用户编程。
3. C++实现委托的思想
使用委托大大方便了用户编程,但委托是.NET特有的,委托可以调用静态方法和实例方法,在 C++ 中,如果用户想要灵活调用具有同一签名的静态函数,使用的是函数指针方法;如果想要调用类中的成员函数,则要使用成员函数指针。
使用函数指针和成员函数指针提供给了程序员更大的灵活性及选择性,但是在使用过程中需要注意内存的安全。
在.NET委托中,可以用Delegate关键字创建具有不同函数签名的委托对象,若要在C++中实现这一功能,则需要对函数指针和成员函数指针运用模板进行封装。
此外,封装后的C++类不具有委托链的功能,还需要在类的内部添加一个链表,并实现递归的调用功能。
参考文献
[1]GOF设计模式[M] 机械工业出版社 作者:Erich Gamma, Richard Helm, Ralph Johnson
[2] 框架程序设计[M] 清华大学出版社 作者:Jeffrey Richter
下一篇:基层电大排课系统的设计与实现