I've started to learn very interesting concept of DI, IoC and related stuff.

I have decided to learn it using simple application, very tiny calculator (only basic functions: add, subtract, multiply & divide), because it's very conveniently to learn any new stuff with elementary application.

My question relates to the design workflow. Right now, I've prepared the next sources:

Main app:

namespace CalculatorApp
{
    public interface ICalculator
    {
        int Add(int firstValue, int secondValue);
        int Subtract(int firstValue, int secondValue);
        int Multiply(int firstValue, int secondValue);
        int Divide(int firstValue, int secondValue);
    }

    public abstract class BaseCalculator : ICalculator
    {
        public virtual int Add(int firstValue, int secondValue)
        {
            return firstValue + secondValue;
        }

        public virtual int Subtract(int firstValue, int secondValue)
        {
            return firstValue - secondValue;
        }

        public virtual int Multiply(int firstValue, int secondValue)
        {
            return firstValue * secondValue;
        }

        public virtual int Divide(int firstValue, int secondValue)
        {
            return firstValue / secondValue;
        }
    }

    public class CalculatorAgent : BaseCalculator
    {
        public override int Add(int firstValue, int secondValue)
        {
            return base.Add(firstValue, secondValue);
        }

        public override int Subtract(int firstValue, int secondValue)
        {
            return base.Subtract(firstValue, secondValue);
        }

        public override int Multiply(int firstValue, int secondValue)
        {
            return base.Multiply(firstValue, secondValue);
        }

        public override int Divide(int firstValue, int secondValue)
        {
            return base.Divide(firstValue, secondValue);
        }
    }
}

Tests, which is using Ninject:

using System;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Ninject;
using Ninject.Modules;

namespace CalculatorApp.Tests
{
    class CalculatorConfig : NinjectModule
    {
        public override void Load()
        {
            Bind<BaseCalculator>().To<CalculatorAgent>();
        }
    }

    class TestHelper<T> : IDisposable
    {
        private Random random;
        private IKernel kernel;
        private T agent;

        public T Agent
        {
            get
            {
                return agent;
            }
        }

        public TestHelper()
        {
            random = new Random();
            kernel = new StandardKernel(new CalculatorConfig());
            agent = kernel.Get<T>();
        }

        internal Tuple<int, int> GenerateValues()
        {
            return new Tuple<int, int>
            (
                item1: GenerateRandomInteger(),
                item2: GenerateRandomInteger()
            );
        }

        internal int GenerateRandomInteger()
        {
            return random.Next(0, Byte.MaxValue);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                random = null;
                kernel = null;

                var disposable = agent as IDisposable;

                if (disposable != null)
                    disposable.Dispose();
            }
        }
    }

    [TestClass()]
    public class CalculatorTests : IDisposable
    {
        private TestHelper<CalculatorAgent> helper = new TestHelper<CalculatorAgent>();

        [TestMethod()]
        public void AddRandomValues()
        {
            // Arrange
            var values = helper.GenerateValues();

            // Act
            var testResult = values.Item1 + values.Item2;
            Debug.WriteLine(String.Format("Addition result: {0}", testResult));

            // Assert
            Assert.AreEqual(testResult, helper.Agent.Add(values.Item1, values.Item2));
        }

        [TestMethod()]
        public void SubtractRandomValues()
        {
            // Arrange
            var values = helper.GenerateValues();

            // Act
            var testResult = values.Item1 - values.Item2;
            Debug.WriteLine(String.Format("Subtraction result: {0}", testResult));

            // Assert
            Assert.AreEqual(testResult, helper.Agent.Subtract(values.Item1, values.Item2));
        }

        [TestMethod()]
        public void MultiplyRandomValues()
        {
            // Arrange
            var values = helper.GenerateValues();

            // Act
            var testResult = values.Item1 * values.Item2;
            Debug.WriteLine(String.Format("Multiplication result: {0}", testResult));

            // Assert
            Assert.AreEqual(testResult, helper.Agent.Multiply(values.Item1, values.Item2));
        }

        [TestMethod()]
        public void DivideRandomValues()
        {
            // Arrange
            var values = helper.GenerateValues();

            // Act
            var testResult = values.Item1 / values.Item2;
            Debug.WriteLine(String.Format("Division result: {0}", testResult));

            // Assert
            Assert.AreEqual(testResult, helper.Agent.Divide(values.Item1, values.Item2));
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                helper.Dispose();
            }
        }
    }
}

If to view the code, there is a part, where I suggest to register different implementations of calculator (it may be a calc with real numbers or the calc, which counts complex numbers, etc...):

class CalculatorConfig : NinjectModule
{
    public override void Load()
    {
        Bind<BaseCalculator>().To<CalculatorAgent>();
        // here will go other implementations
    }
}

I wonder:

  • Is the whole design of current app the correct one?
  • Have I used AAA (Arrange-Act-Assert) concept correctly?
  • Have I worked with the DI correctly?
  • Does it matter, if abstract class is used instead of interface?

If you are surprised with two IDisposable implementations, which are used in tests sources, so I may comment this place of code. Previously, there weren't those manual implementations until VS has argued at me, when I've run the code analysis, which has fired warnings about the absence of them.

  • 1
    Q1 - sure? I mean it is not a great example. conceptually a function calculator.Add(a,b) is not very prone to needing overloads. a more common example is some endpoint, a ResultsView or even an ICalculation.Evaluate()etc. DI is about extracting what is likely to change. dependencies. Q2 - yup. Q3 - not really. currently you seem to be using the DI container as a service locator, i am not seeing any actual injection. you should have a constructor with a dependent argument that gets injected for you. not just grab it from the kernel. Q4 - yes.you are forcing inheritance. don't. use composition – apieceoffruit Mar 23 '16 at 10:11
  • Is there something I am missing ? The entire CalculatorAgent class is redundant it's simply calling the base methods you are not overriding anything. – Denis Dec 24 '16 at 23:37

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.