EventAggregator - antipattern

    Before reading, you should 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 Prism framework . Using EventAggregator has proven to be simple and flexible. System components become independent of each other - changing one component, I'm not afraid to break another.

    When considering the individual components, everything is so, but having risen to the level of the components in the system, you can see serious problems:


    I share my view of too weak a connection and an implicit interaction between parts of the system.

    LED control through EventAggregator


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


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

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

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

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

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


    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().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().Publish(_switch ? SwitchConnection.Connection1 : SwitchConnection.Connection2);
                }
            }
        }
    


    LED Code
    /// 
        /// ViewModel светодиода.
        /// 
        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().Subscribe(OnPowerChanged);
                aggregator.GetEvent().Subscribe(OnSwitch);
                Update();
            }
            /// 
            /// Свет от светодиода.
            /// 
            public Brush Light
            {
                get { return _currentlight; }
                private set
                {
                    SetProperty(ref _currentlight, value);
                }
            }
            /// 
            /// Обработчик переключателя.
            /// 
            void OnSwitch(SwitchConnection connection)
            {
                if (SetProperty(ref _currentConnection, connection))
                    Update();
            }
            /// 
            /// Обработчик питания.
            /// 
            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


    Middleware 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!

    Problems with EventAggregator


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

    With the dynamic replacement of components, it’s even worse, everywhere you will need to duplicate the unsubscribe. EventAggregator creates a weakreference by default. With Weakreference, the unsubscription should take place automatically, but when the components are dynamically replaced, it is not known when the subscription will be deleted - everywhere you will need to duplicate an explicit unsubscription.

    Having replaced the component, I don’t know in what condition the system is: whether the power is on, in what position the Switch is, I just have no where to get it from. One solution is to introduce an auxiliary event into the system. An auxiliary event will ask components to publish their events - PowerEvent and SwitchEvent. Now everywhere you need to take care of publishing and subscribing to this event - the system breaks up and turns into a web.

    System components only know about EventAggregator, but does this mean loose coupling? Not. Despite the isolation of the components from each other, a very strong implicit connection is present in the system. A strong connection is expressed in the set of events that need to be processed. I cannot replace Switch with another component without modifying Led. As a result, the connection between the parts of the system turns into a node: strong, implicit and confusing.

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


    Before you get an answer, think carefully.


    About using EventAggregator inside services that implement some interface and are replaced depending on the configuration ... It’s better not to recall.

    Where do the problems grow from?


    Using EventAggregator violates 3 out of 5 SOLID principles. Uniqueness of responsibility - subscription / unsubscription is not a concern of the components of the scheme. Openness closed - when changing the interaction scheme of components, you need to edit the subscription / unsubscribe. Dependency inversion - the component decides on which events to subscribe / unsubscribe.
    3 out of 5, and problems ...

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

    Also popular now: