一、多播委托实现观察者模式
多播委托就是一个委托上面绑定多个方法,当调用委托时,多个方法按照一定的顺序执行,本例子以温控器为例,当温度发生变化时,会引起冷却器和加热器的变化。
定义事件的订阅者
class Cooler
{
public Cooler(double temperature)
{
this.Temperature = temperature;
}
public double Temperature { get; set; }
public void TemperatureChanged(double temperature)
{
if (temperature > Temperature)
{
Console.WriteLine("当前温度过高,冷却器开启");
}
else
{
Console.WriteLine("当前温度过低,冷却器关闭");
}
}
}
class Heater
{
public Heater(double temperature)
{
this.Temperature = temperature;
}
public double Temperature { get; set; }
public void TemperatureChanged(double temperature)
{
if (temperature > Temperature)
{
Console.WriteLine("当前温度过高,加热器关闭");
}
else
{
Console.WriteLine("当前温度过低,加热器开启");
}
}
}
定义事件发布者
class Thermostat
{
public Action<double> temperatureChangeDelegate { get; set; }
private double _CurrentTemperature;
public double CurrentTemperature
{
get
{
return _CurrentTemperature;
}
set
{
if(value!=CurrentTemperature )
{
_CurrentTemperature = value;
Action<double> temperatureChangeDelegateNew = temperatureChangeDelegate;//防止在别的线程将Null赋值为null,从而引发异常
if (temperatureChangeDelegateNew!=null)
{
List<Exception> exceptions = new List<Exception>();//处理异常从而保证委托链不会因为异常导致有些委托链中的方法不执行
foreach (Action<double> handler in temperatureChangeDelegate.GetInvocationList() )
{
try
{
handler(value);
}
catch(Exception ex)
{
exceptions.Add(ex);
}
}
if(exceptions .Count >1)
{
throw new AggregateException("temperatureChangeDelegateNew有异常",exceptions );
}
}
}
}
}
}
调用委托
Thermostat thermostat = new Thermostat();
Cooler cooler = new Cooler(80);
Heater heater = new Heater(30);
thermostat.temperatureChangeDelegate += cooler.TemperatureChanged;
thermostat.temperatureChangeDelegate += heater.TemperatureChanged;
thermostat.CurrentTemperature = 100;
当前温度过高,冷却器开启
当前温度过高,加热器关闭
二、事件的由来,上述的委托有两个问题,如下:
1)在定义委托的类的外部直接可以调用委托,而我们想实现地目的是通过属性来触发委托的执行,而不能直接调用委托;
2)由于委托实例化时可以使用“=”号,有可能在写程序时,写成如下代码,这样就删除了一个方法,如果我们能实现禁止使用“=”号,这样就帮我们解决了问题
thermostat.temperatureChangeDelegate = cooler.TemperatureChanged;
thermostat.temperatureChangeDelegate = heater.TemperatureChanged;
由于事件可以解决如上的问题,所以在使用多播委托的时候,使用事件替代委托是比较好的选择,事件的代码如下:
class Cooler
{
public Cooler(TemperatureArgs temperatureArgs)
{
this.Temperature = temperatureArgs.NewTemperatere ;
}
public double Temperature { get; set; }
public void TemperatureChanged(object sender, TemperatureArgs e)
{
if (e.NewTemperatere > Temperature)
{
Console.WriteLine("当前温度过高,冷却器开启");
}
else
{
Console.WriteLine("当前温度过低,冷却器关闭");
}
}
}
class Heater
{
public Heater(TemperatureArgs temperatureArgs)
{
this.Temperature = temperatureArgs.NewTemperatere ;
}
public double Temperature { get; set; }
public void TemperatureChanged(object sender, TemperatureArgs e)
{
if (e.NewTemperatere > Temperature)
{
Console.WriteLine("当前温度过高,加热器关闭");
}
else
{
Console.WriteLine("当前温度过低,加热器开启");
}
}
}
public class TemperatureArgs : System.EventArgs
{
public TemperatureArgs(double newTemperature)
{
this.NewTemperatere = newTemperature;
}
public double NewTemperatere { get; set; }
}
class Thermostat
{
public event EventHandler<TemperatureArgs> temperatureChangeDelegate = delegate { };//空委托,这样我们就不用在调用事件时先进行null值判断了。
private double _CurrentTemperature;
public double CurrentTemperature
{
get
{
return _CurrentTemperature;
}
set
{
if (value != CurrentTemperature)
{
_CurrentTemperature = value;
temperatureChangeDelegate?.Invoke(this, new TemperatureArgs(CurrentTemperature));
}
}
}
}
调用:
Thermostat thermostat = new Thermostat();
Cooler cooler = new Cooler(new TemperatureArgs (80));
Heater heater = new Heater(new TemperatureArgs(30));
thermostat.temperatureChangeDelegate += cooler.TemperatureChanged;
thermostat.temperatureChangeDelegate += heater.TemperatureChanged;
thermostat.CurrentTemperature = 100;