First Improvement To ACME's Monolithic Application

Created:11/27/2013 1:51 PM
Updated:12/3/2013 5:21 PM
Source:file:///

Table Of Contents  | <<Previous | Next>>

The first thing we decide to tackle is the coupling between the Wizbang Widget and the company's standard Event Listener.  If we can do that, we should then be able to write tests involving event broadcasts much more easily.  We will also have reduced coupling, and gotten the Wizbang Widget closer to adherence to the Single-Responsibility Principle.  And as an added benefit, we're hoping to be able to eliminate the defect where events show up more than once in our form if the user clicks the 'Go' button more than once.

The code snippets below show the improved Wizbang Widget, as well as a test that can now be written against Wizbang Widget's event broadcasting.  To see the entire improved product, open the solution Monolithv2.  

What we've done is removed any reference to the company's standard Event Listener.  Instead, a client can now call 'AddListener' passing in an Action delegate (a listener) which we will get added to the list of listeners.  This is a version of the Strategy Design Pattern, and helps Wizbang Widget implement the Open/Closed Principle (one of the SOLID Principles).  When an event needs to be broadcast, each listener will get called with that Event Description.  Although it's not shown, if you look in solution Monolithv2 at project WizbangWidgetClientv2, you will see that now the code in that project (in Form1.cs) passes in a listener that not only adds broadcast event descriptions to the Events textbox (doing that only once and thus eliminating the previous defect), it also re-announces each event to the company's standard Event Listener.  WizbangWidgetClientv2 does have a coupling to MonolithEventListener and MonolithFramework, but this is appropriate.

You'll notice that the test does not need to test that Wizbang Widget's events get broadcast to the standard company Event Listener; we've removed that responsibiity completely from Wizbang Widget.  In fact, Wizbang Widget now has no responsibility whatsoever for broadcasting events: if a client provides it a way to 'talk about' events, the widget will 'talk about' them (after all, only Wizbang Widget knows when these events occur and what their details are).  But Wizbang Widget doesn't know or care what if anything a client is doing with the information.  Thus, we've now brought Wizbang Widget closer to adherence to the Single-Responsibility Principle (another of the SOLID Principles).

Here's the new code for WizbangWidget:

 public class WizbangWidget {
        private MonolithLogger _logger = new MonolithLogger ();
        private List< Action< string>> _listeners = new List< Action< string>>();

        public void AddListener( Action< string> listener) {
            _listeners.Add(listener);
        }

        public void DoItAll( string someParameter) {
            _logger.LogEvent( "Beginning 'DoItAll'");

            var repository = new MonolithRepository();
            var coolComponent = new CoolComponent();

            if ( new List< string> { "1", "2" , "noconform" }.Contains(someParameter)) {
                NoteEvent( string.Format( "someParameter = '{0}'", someParameter));
            }
            else {
                NoteEvent( "someParameter was out of bounds." );
                _logger.LogEvent( "someParameter was out of bounds." );
            }

            var thing = repository.GetThing(someParameter);

            var doesNotConform = string.Empty;

            if (ThingDoesNotConformToTheRulesWeProvideToAddValue(thing)) {
                _logger.LogEvent( string.Format( "Thing '{0}' did not conform to our sophisticated rules,", thing.Name));
                doesNotConform = "  But it did not conform to our sophisticated rules.";
            }

            try {
                var theNoise = coolComponent.TheNoiseAThingMakesWhenYouPokeItWithAPointedStick(thing);

                if (theNoise.Contains( "Burp!!!")) {
                    _logger.LogEvent( string.Format( "Thing {0} made an inappropriate noise.", thing.Name));
                }

                var webService = new MonolithWebServiceHelper();
                webService.RecordSoundAThingMade(thing, theNoise);
                _logger.LogEvent( string.Format( "Thing '{0}' made noise '{1}', and that was recorded." + doesNotConform, thing.Name, theNoise));
                NoteEvent( string.Format( "Thing '{0}' was handled." + doesNotConform, thing.Name));
            }
            catch ( Exception ex) {
                _logger.LogEvent( string.Format( "ERROR: {0}", ex.Message));
                throw;
            }
        }

        private bool ThingDoesNotConformToTheRulesWeProvideToAddValue( Thing thing) {
            return thing.Name.ToLower().Contains( "noconform");
        }

        private void NoteEvent( string eventDescription) {
            foreach ( var listener in _listeners) {
                listener(eventDescription);
            }
        }

        private void NoteAndLogEvent( string eventDescription) {
            _logger.LogEvent(eventDescription);
            NoteEvent(eventDescription);
        }
    }


and here's the code for the test we're now able to write to test event broadcasting (this is just a start; there are still a few more tests to write.  Since this is a demo project, we're not going to write those right now).

         public void WhenDoItAllIsCalledThenTheAppropriateEventsMustBeAnnounced() {
            //Arrange
            var events = new List< string>();
            var wizbangWidget = new WizbangWidget();
            wizbangWidget.AddListener(ed => events.Add(ed));

            //Act
            wizbangWidget.DoItAll( "test");

            //Assert
            Assert.AreEqual(
                2, events.Count,
                "An inappropriate number of events was announced when 'WizbangWidget.DoItAll' was called.");
        }
    }

<< Previous: Monolithic Project  |   Next: Second Improvement >>