【www.bbyears.com--C语言】
这篇简单介绍下委托的使用。很多介绍委托的文章都会说道:委托和事件的概念就像一道坎,过了这个槛的人,觉得真是太容易了,而没有过去的人每次见到委托和事件就觉得心里发慌。确实这东西就像最开始学C语言的指针一样,令人有一种很纠结的感觉,总觉得要调用一个方法直接调用就行了,为啥非要定义一个委托时执行这个方法呢。其实在C#里面很多的技术都是为了重用和简化代码而生,委托也不例外,很多使用C#多态去实现的设计模式其实都可以使用委托的方式去改写,可以理解为一种轻量级的设计模式吧。博主打算抽一篇专门分享下多态和委托实现设计模式的异同。这篇就先介绍简单委托的使用。
一、什么是委托:C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。用博主的话说,委托就是一种允许将方法名称作为参数传递的引用类型。它定义的是方法的类型,可以说就是方法的抽象,那么反过来说,方法可以理解为委托的实例,如public delegate void TestDelegate(string str);这种委托定义的就是所有参数类型为string,没有返回值的方法的一种抽象。
二、什么要使用委托:记得博主刚开始做项目的时候看到委托的写法就头大,总觉得这是没事找事,唯一的好处貌似就是代码看上去很酷~~随着工作的累积,发现项目中某些小的需求使用这种轻量级的委托来实现的时候确实能减少很多代码量。
三、委托的使用:
1、.Net Framework 里面的委托类型:使用过委托的朋友可能注意到了C#里面定义了两种类型的委托变量,基本能满足我们的一般需求。
(1)Action类型的委托:C#里面定义Action委托用于抽象化那种没有返回值的方法。将Action变量转到定义可知它的最简单形式:
// 摘要:
// 封装一个方法,该方法不具有参数并且不返回值。
[TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
public delegate void Action();
它定义是就是一种没有返回值没有参数的委托。同时Action还提供了16个泛型的委托,用于定义方法的传入参数:
我们来看他们的使用方法,我们首先定义测试的方法:
private static void Test5(int a, int b, int c) { //...... } //无参数无返回值 private static void Test1() { Console.WriteLine("Func Test1, No Parameter"); } //有参数无返回值 private static void Test2(string str) { Console.WriteLine("Func Test2, Parameter is" + str); } //无参数有返回值 private static object Test3() { Console.WriteLine("Func Test3, Parameter"); return Guid.NewGuid().ToString(); } //有参数有返回值 private static object Test4(string strRes) { Console.WriteLine("Func Test4, Parameter and Return Value"); return strRes; }
调用:
static void Main(string[] args) { //1.无参无返回值方法 var oAction1 = new Action(Test1); oAction1.Invoke();//调用方式一 oAction1();//调用方式二 //2.有参无返回值 var oAction2 = new Action(Test5); oAction2.Invoke(1, 2, 3); oAction2(1, 2, 3); //匿名方法的调用 var oAction3 = new Action ((a,b,c) => { //...... }); oAction3.Invoke(1, 2, 3); }
(2)Func类型的委托:还记得Linq里面的扩展方法Where()、Select()等方法的参数吗。public static IEnumerable
static void Main(string[] args)
{
var oFunc1 = new Func
class Program { static void Main(string[] args) { Person strHelper = new Person(); string r1 = strHelper.ProcessFunc("中国人", "你好", new MyDelegate(strHelper.ChineseSayHello)); string r2 = strHelper.ProcessFunc("English", "Hello", new MyDelegate(strHelper.EnglishSayHello)); string r3 = strHelper.ProcessFunc("Japanese", "こんにちは", new MyDelegate(strHelper.JapaneseSayHello)); Console.WriteLine(r1); Console.WriteLine(r2); Console.WriteLine(r3); Console.ReadKey(); } } public delegate string MyDelegate(string s1, string s2); public class Person { public string ProcessFunc(string s1, string s2, MyDelegate process) { return process(s1, s2); } public string ChineseSayHello(string s1, string s2) { return s1 +","+ s2; } public string EnglishSayHello(string s1, string s2) { return s1 + "," + s2; } public string JapaneseSayHello(string s1, string s2) { return s1 +","+ s2; } }
得到结果:
public string ProcessFunc(string s1, string s2, MyDelegate process)里面定义了一个回调函数,可以将任意一个符合这个委托的方法传递进去,得到想对应的结果。细看这种设计是不是和工厂设计模式十分相似,我简单构造了个工厂:
public class Person { public virtual string SayHello(string s2) { return s2; } } public class Chinese : Person { public override string SayHello(string s2) { return "中国人," + s2; } } public class English : Person { public override string SayHello(string s2) { return "English," + s2; } } public class Japanese : Person { public override string SayHello(string s2) { return "Japanese," + s2; } } //Main函数里面调用 class Program { static void Main(string[] args) { var r1 = GetPerson("你好").SayHello("你好"); var r2 = GetPerson("Hello").SayHello("Hello"); var r3 = GetPerson("こんにちは").SayHello("こんにちは"); Console.WriteLine(r1); Console.WriteLine(r2); Console.WriteLine(r3); Console.ReadKey(); } public static Person GetPerson(string strType) { if (strType == "你好") return new Chinese(); else if (strType == "Hello") return new English(); else return new Japanese(); } }
得到结果和上面相同:
设计模式实例
本文简单抽取了几个设计模式分别按照多态和委托的方式去实现,当然这里的重点并不是讲设计模式,而是为了使读者更好地理解委托。所以设计模式的很多细节,本篇可能会略过。
一、简单工厂模式:本篇就借助计算器的例子加以说明。
1、多态实现简单工厂模式。
class Program2 { static void Main(string[] args) { //1.使用多态实现简单工厂模式 int x = 8, y = 2; var iRes1 = GetObject("+").Compute(x, y); var iRes2 = GetObject("-").Compute(x, y); var iRes3 = GetObject("*").Compute(x, y); var iRes4 = GetObject("/").Compute(x, y); Console.WriteLine(iRes1); Console.WriteLine(iRes2); Console.WriteLine(iRes3); Console.WriteLine(iRes4); Console.ReadKey(); } static Calculator GetObject(string type) { Calculator oRes = null; switch (type) { case "+": oRes = new Add(); break; case "-": oRes = new Subtract(); break; case "*": oRes = new Multiply(); break; case "/": oRes = new Divide(); break; } return oRes; } } public class Calculator { public virtual int Compute(int x, int y) { return 0; } } public class Add : Calculator { public override int Compute(int x, int y) { return x + y; } } public class Subtract : Calculator { public override int Compute(int x, int y) { return x - y; } } public class Multiply : Calculator { public override int Compute(int x, int y) { return x * y; } } public class Divide : Calculator { public override int Compute(int x, int y) { if (y == 0) { return 0; } return x / y; } }
代码应该很容易看懂,直接通过方法的重写去实现,在此就不过多讲解。
2、委托方式实现简单工厂模式。
class Program2 { static void Main(string[] args) { #region 2.委托实现简单工厂模式 int x = 8, y = 2; var oCalculator = new Calculator(); var iRes1 = oCalculator.Compute(x, y, oCalculator.Add);//将方法作为参数传下去 var iRes2 = oCalculator.Compute(x, y, oCalculator.Subtract); var iRes3 = oCalculator.Compute(x, y, oCalculator.Multiply); var iRes4 = oCalculator.Compute(x, y, oCalculator.Divide); Console.WriteLine(iRes1); Console.WriteLine(iRes2); Console.WriteLine(iRes3); Console.WriteLine(iRes4); #endregion Console.ReadKey(); } } public delegate int DelegateCalculator(int x, int y); public class Calculator { //将方法的实例传递进来,在Compute方法里面执行 public int Compute(int x, int y, DelegateCalculator calculator) { return calculator(x, y); } public int Add(int x, int y) { return x + y; } public int Subtract(int x, int y) { return x - y; } public int Multiply(int x, int y) { return x * y; } public int Divide(int x, int y) { if (y == 0) { return 0; } return x / y; } }
这里需要定义四个实现方法Add、Subtract、Multiply、Divide,而不用在意这四个方法在哪个类下面,只要这四个方法的的参数和返回值和委托的定义保持一致即可。这也验证了上面说的 “站在方法的层面,委托实例的一个非常有用的特性是它既不知道,也不关心其封装方法所属类的详细信息,对它来说最重要的是这些方法与该委托的参数和返回值的兼容性” 。两种方式得到的结果是相同的:
二、观察者模式:观察者模式最典型的场景就是订阅者和订阅号的场景
1、纯多态方式实现观察者模式:这种代码园子里面非常多。
class Program3 { static void Main(string[] args) { // 具体主题角色通常用具体自来来实现 ConcreteSubject subject = new ConcreteSubject(); subject.Attach(new ConcreteObserver(subject, "Observer A")); subject.Attach(new ConcreteObserver(subject, "Observer B")); subject.Attach(new ConcreteObserver(subject, "Observer C")); subject.SubjectState = "Ready"; subject.Notify(); Console.Read(); } } //抽象主题类 public abstract class Subject { private IListobservers = new List (); /// /// 增加观察者 /// /// public void Attach(Observer observer) { observers.Add(observer); } ////// 移除观察者 /// /// public void Detach(Observer observer) { observers.Remove(observer); } ////// 向观察者(们)发出通知 /// public void Notify() { foreach (Observer o in observers) { o.Update(); } } } //具体主题类 public class ConcreteSubject : Subject { private string subjectState; ////// 具体观察者的状态 /// public string SubjectState { get { return subjectState; } set { subjectState = value; } } } //抽象观察者类 public abstract class Observer { public abstract void Update(); } //具体观察者 public class ConcreteObserver : Observer { private string observerState; private string name; private ConcreteSubject subject; ////// 具体观察者用一个具体主题来实现 /// public ConcreteSubject Subject { get { return subject; } set { subject = value; } } public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } ////// 实现抽象观察者中的更新操作 /// public override void Update() { observerState = subject.SubjectState; Console.WriteLine("The observer's state of {0} is {1}", name, observerState); } } 可以看到虽然已经很好的实现了观察者Observer 和主题Subject之间的分离。但是Subject的内部还是有对观察者的调用: public void Notify() { foreach (Observer o in observers) { o.Update(); } }
2、多态和委托实现观察者模式。
class Program3 { static void Main(string[] args) { // 具体主题角色通常用具体自来来实现 ConcreteSubject subject = new ConcreteSubject(); //传入的只是观察者的通过方法。 subject.Attach(new ConcreteObserver(subject, "Observer A").Update); subject.Attach(new ConcreteObserver(subject, "Observer B").Update); subject.Attach(new ConcreteObserver(subject, "Observer C").Update); subject.SubjectState = "Ready"; subject.Notify(); Console.Read(); } } public delegate void ObserverDelegate(); //抽象主题类 public abstract class Subject { public ObserverDelegate observedelegate; ////// 增加观察者 /// /// public void Attach(ObserverDelegate observer) { observedelegate += observer; } ////// 移除观察者 /// /// public void Detach(ObserverDelegate observer) { observedelegate -= observer; } ////// 向观察者(们)发出通知 /// public void Notify() { if (observedelegate != null) { observedelegate(); } } } //具体主题类 public class ConcreteSubject : Subject { private string subjectState; ////// 具体观察者的状态 /// public string SubjectState { get { return subjectState; } set { subjectState = value; } } } //具体观察者 public class ConcreteObserver { private string observerState; private string name; private ConcreteSubject subject; ////// 具体观察者用一个具体主题来实现 /// public ConcreteSubject Subject { get { return subject; } set { subject = value; } } public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } ////// 实现抽象观察者中的更新操作 /// public void Update() { observerState = subject.SubjectState; Console.WriteLine("The observer's state of {0} is {1}", name, observerState); } }
得到结果:
这样设计的优势:
(1)将通知的方法Update通过委托的形式传入主题对象。这样主题对象Subject就完全和观察者隔离。更好地实现了低耦合。
(2)减少了观察者抽象类的定义。使整个设计更加精简。
(3)如果将设计更进一步,观察者这边自定义delegate void ObserverDelegate()这种类型的方法。比如需要执行Update()方法之后还要记录一个日志的操作。如:
//具体观察者 public class ConcreteObserver { private string observerState; private string name; private ConcreteSubject subject; ////// 具体观察者用一个具体主题来实现 /// public ConcreteSubject Subject { get { return subject; } set { subject = value; } } public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } ////// 实现抽象观察者中的更新操作 /// public void Update() { observerState = subject.SubjectState; Console.WriteLine("The observer's state of {0} is {1}", name, observerState); } public void Log() { Console.WriteLine("Log:Update方法执行完成"); } }
那么在客户端调用时只需要将Log方法以委托的形式传入即可:
static void Main(string[] args) { // 具体主题角色通常用具体自来来实现 ConcreteSubject subject = new ConcreteSubject(); //传入的只是观察者的通过方法。 var obj = new ConcreteObserver(subject, "Observer A"); subject.Attach(obj.Update); subject.Attach(obj.Log); subject.SubjectState = "Ready"; subject.Notify(); Console.Read(); }
是不是显得更灵活一点。如果是纯多态的方式,由于Subject里面指定了调用Update()方法,所以当需要增加Log方法的时候代码的改变量要大。
三、模板方法模式,这里就以设备采集为例来进行说明:
1、多态实现模板方法模式:
class Program4 { static void Main(string[] args) { var oTem1 = new DeviceMML(); oTem1.Spider(); Console.WriteLine(""); var oTem2 = new DeviceTL2(); oTem2.Spider(); Console.ReadKey(); } } public abstract class TempleteDevice { // 模板方法,不要把模版方法定义为Virtual或abstract方法,避免被子类重写,防止更改流程的执行顺序 public void Spider() { Console.WriteLine("设备采集开始"); this.Login(); this.Validation(); this.SpiderByType1(); this.SpiderByType2(); this.LoginOut(); Console.WriteLine("设备采集结束"); } // 登陆 public void Login() { Console.WriteLine("登陆"); } // 验证 public void Validation() { Console.WriteLine("验证"); } // 采集 public abstract void SpiderByType1(); public abstract void SpiderByType2(); // 注销 public void LoginOut() { Console.WriteLine("注销"); } } //MML类型的设备的采集 public class DeviceMML : TempleteDevice { public override void SpiderByType1() { Console.WriteLine("MML类型设备开始采集1"); //....... } public override void SpiderByType2() { Console.WriteLine("MML类型设备开始采集2"); } } //TL2类型设备的采集 public class DeviceTL2 : TempleteDevice { public override void SpiderByType1() { Console.WriteLine("TL2类型设备开始采集1"); //....... } public override void SpiderByType2() { Console.WriteLine("TL2类型设备开始采集2"); } }
父类里面的非abstract方法都是模板方法,也就是子类公用并且不可以重写的方法。SpiderType1和SpiderType2是需要子类重写的方法。模板方法模式在抽象类中定义了算法的实现步骤,将这些步骤的实现延迟到具体子类中去实现,从而使所有子类复用了父类的代码,所以模板方法模式是基于继承的一种实现代码复用的技术。
2、使用委托改写后:
class Program4 { static void Main(string[] args) { var oTem1 = new TempleteDevice(DeviceMML.SpiderByType1, DeviceMML.SpiderByType2); oTem1.Spider(); Console.WriteLine(""); var oTem2 = new TempleteDevice(DeviceTL2.SpiderByType1, DeviceTL2.SpiderByType2); oTem2.Spider(); Console.ReadLine(); } } public delegate void DeviceDelegate(); public class TempleteDevice { public DeviceDelegate oDelegate; public TempleteDevice(params DeviceDelegate[] lstFunc) { foreach (var oFunc in lstFunc) { oDelegate += oFunc; } } // 模板方法,不要把模版方法定义为Virtual或abstract方法,避免被子类重写,防止更改流程的执行顺序 public void Spider() { Console.WriteLine("设备采集开始"); this.Login(); this.Validation(); if (oDelegate != null) { oDelegate(); } this.LoginOut(); Console.WriteLine("设备采集结束"); } // 登陆 public void Login() { Console.WriteLine("登陆"); } // 验证 public void Validation() { Console.WriteLine("验证"); } // 注销 public void LoginOut() { Console.WriteLine("注销"); } } //MML类型的设备的采集 public class DeviceMML { public static void SpiderByType1() { Console.WriteLine("MML类型设备开始采集1"); //....... } public static void SpiderByType2() { Console.WriteLine("MML类型设备开始采集2"); } }
//TL2类型设备的采集
public class DeviceTL2
{
public static void SpiderByType1()
{
Console.WriteLine("TL2类型设备开始采集1");
//.......
}
public static void SpiderByType2()
{
Console.WriteLine("TL2类型设备开始采集2");
}
}
得到结果:
优化模板方法模式的意义:
(1)解除了子类和父类之间的继承关系,更好地实现了对象间的低耦合。
(2)采用委托可以动态实现方法的组合,这种方式更加灵活,子类可以更加灵活的设计不同部分的方法。然后方法的数量通过params来传递,方法的数量没有什么严格的限制。
当然其他设计模式也可以使用委托去优化设计,博主在这里就暂时只分享这三种模式的异同。总的来说,委托不可能代替多态去实现各种模式,但是它和多态联合起来使用可以实现更加灵活的设计。通过这两篇下来,不知道你是否对委托有点感觉了呢,委托这东西,重在实战,就像游泳一样,如果不用那么几次,你永远也不可能学会。以上只是博主个人的理解,可能很多方便没有考虑得那么全面,希望各位指正。