Tuesday, June 9, 2015

Builder Pattern - a Real Life Example

Here is a real world example of using a pattern to set up dependencies that need user input in order to function.


Given an application that contacts a server for state information. Lets use a console interface for this example.


The app consumes an interface that defines a method to get server state. The user interface layer should know nothing about the implementation details, but needs to pass the server name to the implementation. We want to use the same instance per each server we interface with. If we connect to five servers, we should have five objects.


Those objects can have different methods and for efficiency, they should be reusable. Each instance has internal state about the server name, which is an implementation detail, and can only be used for that server (think multi-threading).


We should avoid constructing the object in the user interface code because it would know too much detail about the implementation and therefore would be too tightly coupled to the specific implementation details. Additionally, we would not be able to pass a mock object for testing purposes so we need some way to pass the information to the code that processes the user request.


Instead of passing that we can define an interface that build the dependency based on user provided data. The Build method accepts a server name, constructs the server object passing the name to the constructor, and returns the server instance to the caller.


The caller uses the returned object that implements the server state interface to retrieve information about the server it knows about.


In C# code form:



interface IServerInformationReader
{
    Services GetServices();
}
interface IServerInformationLogic
{
    bool IsServiceRunning(string serviceName);
}
interface IServerInformationLogicBuilder
{
    IServerInformationLogic BuildReaderForServer(string serverName);
}

namespace N5D.ServerManagement.Builders
{
    class ServerInformationBuilder : IServerInformationLogicBuilder
    {
        public IServerInformationLogic BuildReaderForServer(string serverName)
        {
            return new ServerInfoLogic(new ServerInfoReader(serverName));
        }
    }
}

namespace N5D.ServerManagement.Tests.Builders
{
    class ServerInformationFakeBuilder : IServerInformationLogicBuilder
    {
        public IServerInformationLogic Build(string serverName)
        {
            return new ServerInfoLogic(new ServerInfoReaderFake(serverName));

        }
    }
}


This allows us to create the object in multiple ways depending on our use and the implementation details. Those will not be known to the consuming code but will be abstracted by the builder.

class ApplicationCode
{
    IServerInformationLogicBuilder _builder;
    IServerInformationLogic _serverInfo;

    ApplicationLogic(IServerInformationLogicBuilder builder)
    {
        _builder = builder;
    }

    void DefineServer()
    {
        string serverName = GetServerNameFromUserInput();
        serverInfo = _builder.Build(serverName);
        
    }

    void IsServiceRunning(string serviceName)
    {
        Console.WriteLine(_serverInfo.IsServiceRunning(serviceName));
    }

}




This enables us to pass a different builder to the app code depending on whether we are testing or writing production code. The component that fetches the info from the server has that sole purpose while the component that computes IsServiceRunning is a logic detail. In this way we can return known data with a fake reader and test the logic.


The logic in this example is clearly simple, but illustrates a situation when a builder is appropriate. The builder can be injected with some IoC container in each context. The reader must be made at runtime. That's when you need a builder.

No comments:

Post a Comment