📜 ⬆️ ⬇️

EventAggregator - antipattern

Before reading it is necessary to read about the EventAggregator template. EventAggregator provides the interaction of components and services of a composite application through weak connectivity.

EventAggregator can be found in many WPF frameworks: Mvvm Light - class Messenger , Catel - class MessageMediator . I met EventAggregator with the WPF framework Prism . Using EventAggregator was simple and flexible. The components of the system become independent of each other - by changing one component, I am not afraid to break the other.

When examining individual components, everything is as it is, but having risen to the level of operation of components in the system, one can discern serious problems:
')

I share my view on too loosely connected and implicit interactions between parts of the system.

LED control via EventAggregator


To control the LEDs you will need: the power button - Power , the two-state switch - Switch and two LEDs - RedLed and BlueLed . On WPF, it looks like this:


The Power button lights one of the LEDs depending on the state of the Switch .

In the system based on EventAggregator, select two events: power on / off - PowerEvent and switch state change - SwitchEvent .

The PowerEvent event is published when you click on the Power button, the SwitchEvent event is published when you click on the Switch. LEDs subscribe to PowerEvent and SwitchEvent events.

The LED lights up if there is power and the switch is in the desired state.

Event ID
enum Power { On = 1, Off = 0, } class PowerEvent : PubSubEvent<Power> { } public enum SwitchConnection { Connection1, Connection2, } class SwitchEvent : PubSubEvent<SwitchConnection> { } 


Power Management Code
 public class PowerViewModel : BindableBase { readonly IEventAggregator _aggregator; bool _power; public PowerViewModel(IEventAggregator aggregator) { _aggregator = aggregator; } public bool Power { get { return _power; } set { if (SetProperty(ref _power, value)) _aggregator.GetEvent<PowerEvent>().Publish(_power ? Events.Power.On : Events.Power.Off); } } } 


Switch control code
 public class SwitchViewModel : BindableBase { readonly IEventAggregator _aggregator; bool _switch; public SwitchViewModel(IEventAggregator aggregator) { _aggregator = aggregator; Switch = true; } public bool Switch { get { return _switch; } set { if (SetProperty(ref _switch, value)) _aggregator.GetEvent<SwitchEvent>().Publish(_switch ? SwitchConnection.Connection1 : SwitchConnection.Connection2); } } } 


LED code
 /// <summary> /// ViewModel . /// </summary> public class LedViewModel : BindableBase { readonly SwitchConnection _activeConnection; readonly Brush _activeLight; Power _currentPower; SwitchConnection _currentConnection; Brush _currentlight; public LedViewModel(SwitchConnection connection, Brush light, IEventAggregator aggregator) { _activeConnection = connection; _activeLight = light; aggregator.GetEvent<PowerEvent>().Subscribe(OnPowerChanged); aggregator.GetEvent<SwitchEvent>().Subscribe(OnSwitch); Update(); } /// <summary> ///   . /// </summary> public Brush Light { get { return _currentlight; } private set { SetProperty(ref _currentlight, value); } } /// <summary> ///  . /// </summary> void OnSwitch(SwitchConnection connection) { if (SetProperty(ref _currentConnection, connection)) Update(); } /// <summary> ///  . /// </summary> void OnPowerChanged(Power power) { if (SetProperty(ref _currentPower, power)) Update(); } void Update() { Brush currentLight = Brushes.Transparent; switch (_currentPower) { case Power.On: if (_currentConnection == _activeConnection) currentLight = _activeLight; break; case Power.Off: break; default: throw new ArgumentOutOfRangeException(); } Light = currentLight; } } 


Xaml markup
 <Window x:Class="AggregatorAntiPattern.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:AggregatorAntiPattern" mc:Ignorable="d" Height="350" Width="525"> <Window.Resources> <Style TargetType="Path" x:Key="Light"> <Setter Property="Stroke" Value="Black" /> <Setter Property="StrokeThickness" Value="2" /> <Setter Property="Fill" Value="{Binding Light}" /> <Setter Property="Data"> <Setter.Value> <EllipseGeometry RadiusX="10" RadiusY="10" /> </Setter.Value> </Setter> </Style> <Style TargetType="Line" x:Key="Connection"> <Setter Property="Stroke" Value="Black" /> <Setter Property="StrokeThickness" Value="1" /> </Style> </Window.Resources> <Canvas Margin="20"> <ToggleButton Canvas.Top="120" Content=" Power " DataContext="{Binding PowerVM}" IsChecked="{Binding Power}" /> <Line Canvas.Top="130" Canvas.Left="40" X1="0" X2="90" Y1="0" Y2="0" Style="{StaticResource Connection}" /> <ToggleButton Canvas.Top="120" Canvas.Left="120" Content=" Switch " DataContext="{Binding SwitchVM}" IsChecked="{Binding Switch}" /> <Line Canvas.Top="130" Canvas.Left="165" X1="0" X2="77" Y1="0" Y2="-30" Style="{StaticResource Connection}" /> <Line Canvas.Top="130" Canvas.Left="165" X1="0" X2="77" Y1="0" Y2="30" Style="{StaticResource Connection}" /> <Path Canvas.Top="100" Canvas.Left="250" DataContext="{Binding Connection1Light}" Style="{StaticResource Light}" /> <Path Canvas.Top="160" Canvas.Left="250" DataContext="{Binding Connection2Light}" Style="{StaticResource Light}" /> </Canvas> </Window> 


Binding code
 var aggregator = new EventAggregator(); PowerVM = new PowerViewModel(aggregator); SwitchVM = new SwitchViewModel(aggregator); Connection1Light = new LedViewModel(SwitchConnection.Connection1, Brushes.Red, aggregator); Connection2Light = new LedViewModel(SwitchConnection.Connection2, Brushes.Blue, aggregator); 


Everything works perfectly!

Issues with EventAggregator


There are only 4 components in the circuit, but what should be done to replace the LED with another element? Copy subscription from LedViewModel - duplicate.

With a dynamic replacement of components, everything is still worse, everywhere you will need to duplicate the reply. EventAggregator defaults to a weakreference. With the Weakreference, the unsubscribe should go automatically, but when dynamically replacing components, it is not known when the subscription will be deleted - everywhere you will need to duplicate the explicit unsubscribe.

Having replaced the component, I don’t know in which state the system is: whether the power is on, in what position the Switch is, I just don’t get a clue. One solution is to enter into the system an auxiliary event. The auxiliary event will ask components to publish their events — PowerEvent and SwitchEvent. Now everywhere you need to take care of the publication and subscription to this event - the system disintegrates and turns into a web.

System components only know about EventAggregator, but does this mean weak connectivity? No Despite the isolation of the components from each other, there is a very strong implicit connection in the system. The strong link is expressed in the set of events that need to be processed. I can not replace the Switch to another component without finishing Led. As a result, the connection between the parts of the system turns into a node: strong, not obvious and confusing.

What needs to be done so that there are several switches in the circuit?


Think before you get an answer.


About the use of EventAggregator inside services that implement some interface and are replaced depending on the configuration ... It is better not to remember.

Where the problems grow


Using EventAggregator violates 3 of the 5 SOLID principles. Uniqueness of responsibility - subscription / unsubscribe is not a concern of the components of the scheme. Openness closedness - when changing the scheme of interaction of components, you need to edit the subscription / unsubscribe. Inversion of dependence - the component itself decides which events to subscribe / unsubscribe from.
3 out of 5, and the problems ...

PS use EventAggregator with caution. For me, EventAggregator is an anti-pattern and the troubles from it are much more than good.

Source: https://habr.com/ru/post/322652/


All Articles