Bonus Answer

Created:12/4/2013 10:58 AM
Updated:12/4/2013 11:50 AM

Table Of Contents  |  <<Previous 

The improvement I have in mind - in case you haven't guessed it - is to split out the two or three responsibilities remaining in WizbangComponent - Evaluating a Thing's conformance to industry standards (i.e. the Secret Sauce), (arguably Determining the noise a thing makes when something is done to it), and Reporting on that to a Recorder.

One of the things the Bonus Answer highlights is that there is a degree of subjectivity in the SOLID Principles.  Very often it is unambiguously clear whether or not code adheres to them.  Sometimes there is room for disagreement.  WizbangComponent relies on injected components to carry out the tasks of Determining  and Reporting/Recording.  Does this mean it doesn't have those responsibilities?  This is where pragmatism can come to the rescue: if we considered WizbangComponent to have the responsibility of Determining and Reporting/Recording (and thus split these responsibilities out of the component), would it solve any problems?

In this case, we can clearly see that if the Secret Sauce algorithm (Evaluating Conformance) were in a class with no other responsibilities, it would make for a much more integrable component.  So rather than sitting around arguing the academic merits of one side or the other, let's just make our code better!

In the solution 'MuchBetter', in the project 'WizbangComponentv5', take a look at the class 'SecretSauce'.  Notice that it implements interface IThingEvaluator, which is the contract for the one responsibility - evaluating if a thing conforms to some set of standards - that SecretSauce has.  That code is below.  Also look at the tests for SecretSauce, and notice how much simpler it now is to test the 'Conforms' functionality (the tests are below also).

Also in the 'RuntimeCompositionApplication' solution, take a look at the adapters in the folder Implementations > Adapters > MuchBetterWizbangComponent.  Take a look at ReporterAndEvaluatorFactory.GetWizbangComponentEvaluatorAndReporter() (code below).  (Don't be thrown by the code using the Fluent Interface - it should be pretty self-explanatory).  Essentially the RuntimeCompositionApplication client can now use just ACME's 'Secret Sauce' - the code to determine a Thing's conformance to DoItAll industry standards, which is the reason AcquiCorp. wants to merge with ACME - and determine which algorithm to use for all the rest.

         internal static IThingEvaluatorAndReporter GetWizbangComponentEvaluatorAndReporter(
            ILogger logger, IThingNoiseDeterminer determiner, IThingNoiseRecorder recorder,
            IThingRepository repository) {
                return
                    Fluent
                    .UsingAsEvaluator
                    .This( new IEvaluatorToACMESecretSauce ())
                    .Setup
                    .WithLogger(logger)
                    .WithNoiseDeterminer(determiner)
                    .WithNoiseRecorder(recorder)
                    .WithThingRepository(repository)
                    .GetEvaluatorAndReporter();
        }

And remember, all this was made possible by bringing ACME's codebase into adherence with SOLID Principles and the related effort of making it more testable!

Here's the code for SecretSauce:
    public class SecretSauce : IThingEvaluator {
        public bool ThingConformsToDoItAllIndustryStandards( Thing thing) {
            return ! thing.Name.ToLower().Contains( "noconform");
        }

        bool IThingEvaluator.Conforms(IThing thing) {
            return ThingConformsToDoItAllIndustryStandards(
                new IThingMapper().ToThing(thing));
        }

        public IThingEvaluator AsIThingEvaluator {
            get { return this as IThingEvaluator; }
        }
    }
}

And here's the (now much simpler) testing code:
    [TestClass]
    public class SecretSauceTests {
        [ TestMethod]
        public void WhenThingConformsToDoItAllIndustryStandardsIsCalledWithAThingThatConformsToIndustryStandardsThenItMustReturnTrue() {
            //Arrange
            var thing = new Thing { Name = "Conforms" };

            //Act
            var conforms = new SecretSauce().ThingConformsToDoItAllIndustryStandards(thing);

            //Assert
            Assert.IsTrue(
                conforms,
                "SecretSauce.ThingConformsToDoItAllIndustryStandards() reported as nonconforming a conforming Thing.");
        }

        [ TestMethod]
        public void WhenThingConformsToDoItAllIndustryStandardsIsCalledWithAThingThatDoesNotConformToIndustryStandardsThenItMustReturnFalse() {
            //Arrange
            var thing = new Thing { Name = "noconform" };

            //Act
            var conforms = new SecretSauce().ThingConformsToDoItAllIndustryStandards(thing);

            //Assert
            Assert.IsFalse(
                conforms,
                "SecretSauce.ThingConformsToDoItAllIndustryStandards() reported as conforming a nonconforming Thing.");
        }

    }


<< Previous: Bonus