http://www.codinghelmet.com/  

Wear a helmet. Even when coding.

howto > reduce-number-of-generic-parameters

Design Technique to Reduce Number of Generic Parameters
by Zoran Horvat @zoranh75

Introduction

In this article we will tackle one problem which is intrinsic to extending capabilities of existing classes. The problem is manifested when we desire to extend functionality used by a client. Let’s start with the example and it will become clear where the problem is.

The Printing Example

In the picture below, we can see a client which collaborates with some other object. The client wants to print some data and the printer object is there to perform the operation.

The detail which complicates matters a bit is that client is generic type. Printer must be paired with the client to support the same data type as that produced by the client.

Concrete printer

We can implement this requirement directly in code, without need to introduce any abstraction:

public class Printer<T>
{
    public void Print(T data)
    {
        Console.WriteLine("[{0}]", data);
    }
}

public class Client<T>
{

    private Printer<T> printer;

    public Client(Printer<T> printer)
    {
        this.printer = printer;
    }

    public void DoBusiness()
    {
        T data = this.Produce();
        this.printer.Print(data);
    }

    private T Produce()
    {
        return default(T);
    }
}

This implementation is straight-forward. Client expects concrete printer as its constructor dependency. Internally, Client manages a complex domain operation, which is represented by the DoBusiness method. Part of this operation is sending certain data to the printer.

On the other hand, Printer is just responsible to print the data. Nothing more than that.

But, after this stage, things begin to complicate. What happens if we do not have appropriate printer available, but have some other? As you will see, this case will have undesired impact on the client design.

Alternate Scenario with Mismatched Printer

It is easy to complicate even such a small application like this one with two classes only. Here is an alternate scenario. We have a printer available, but its generic type argument does not match the generic type argument of the client that wishes to use it. In other words, business process produces data of one type and printer expects data of some other type. Such mismatch causes a lot of pain on the client side.

Concrete printer with conversion

In order to print its data, client now has to use some convenient converter. That would be an object which can convert data from one type to another. The Converter object must be passed on to the client along with the mismatched printer.

How do we deal with this problem and who is supposed to implement additional logic? According to this diagram, the client will pay the price.

Do you feel the injustice in this situation? Why would the client be responsible for inability of the environment to pass it the right printer? That is the good question, but it doesn’t hit the target. Central point in this diagram is that we cannot implement the client in the same way as before anymore. The client now expects two generic parameters. Previously, it was only one generic parameter.

The second generic parameter is totally alien to the client. That is the type expected by the printer. In order to satisfy this additional requirement, we might have to implement a completely separate client class. But keep on mind that client’s primary role is to implement the domain logic. With having two implementations of the client we are hurting the domain model design. It begins to complicate due to a totally unrelated requirement. Actually, this requirement about conversion before printing should be placed in infrastructure rather than the domain.

So we are not going to implement this complicated client. Instead, we will apply one design technique which saves the client from having to know about this whole complication with data conversion.

Hiding the Complexity Behind an Interface

The following class diagram depicts the basic idea of hiding an algorithm or a concept behind an interface.

Abstract printer

In this case, we are letting the client collaborate with abstract printer. Concrete printer, which is the Printer<T> generic class, remains hidden from the client and client doesn’t couple with it.

What the client doesn’t know from its perspective is that there is another concrete implementation of the IPrinter interface. RelayPrinter is a generic class with two generic parameters. One indicates data type sent by the client. Another indicates data type which is actually printed. To bridge the gap between these two types, RelayPrinter uses a converter. Once again, client doesn’t know that such concrete printer exists. Client only knows about abstract printer with one generic type parameter.

The first change that we will make in code to implement this design is to introduce abstract printer:

public interface IPrinter<in T>
{
    void Print(T data);
}

public class Printer<T>: IPrinter<T>
{
    public void Print(T data)
    {
        Console.WriteLine("[{0}]", data);
    }
}

public class Client<T>
{

    private IPrinter<T> printer;

    public Client(IPrinter<T> printer)
    {
        this.printer = printer;
    }
    ...
}

From now on, concrete printer will implement the abstract printer interface and the client will only talk to abstract printers.

Data conversion is the infrastructure task and it will also be represented by an abstract interface:

public interface IConverter<in TInput, out TOutput>
{
    TOutput Convert(TInput data);
}

Now that abstractions are in place, we can provide the RelayPrinter implementation.

public class RelayPrinter<TData, TOutput>: IPrinter<TData>
{

    private IConverter<TData, TOutput> converter;
    private IPrinter<TOutput> printer;

    public RelayPrinter(IConverter<TData, TOutput> converter, IPrinter<TOutput> printer)
    {
        this.converter = converter;
        this.printer = printer;
    }

    public void Print(TData data)
    {
        TOutput convertedData = this.converter.Convert(data);
        this.printer.Print(convertedData);
    }
}

Implementation of the RelayPrinter is quite simple. But the most fascinating part of it is that this concrete class maps two generic types into one. The whole idea is similar to partial application, which is characteristic to functional languages. In partial application, a function with two arguments would be reduced to a function expecting one parameter.

In case of relay printer, class with two generic type arguments is reduced to a class with one generic type parameter. The idea is basically the same: client observes the reduced version of the type and knows how to use it; knowing more would needlessly complicate the design of the client.

Conclusion

Classes written like relay printer in this example are often playing the role of helpers, decorators, proxies and similar objects. They rarely do any actual work, but rather glue together two or more objects that can perform certain operations on their own.

RelayPrinter cannot print anything. In order to have anything printed out, we must have actual implementation of the IPrinter generic interface which deals with ink. We also need an actual implementation of the IConverter interface. The role of the RelayPrinter is then not to print data, but rather to orchestrate operations offered by the converter and another strongly typed printer.

In that respect, RelayPrinter could be exposed together with the IPrinter generic interface. RelayPrinter really is just a library class. It doesn't have to be part of the domain model because its features are not related to any particular domain.

See also:

Published: Feb 2, 2015

ZORAN HORVAT

Zoran is software architect dedicated to clean design and CTO in a growing software company. Since 2014 Zoran is an author at Pluralsight where he is preparing a series of courses on object-oriented and functional design, design patterns, writing unit and integration tests and applying methods to improve code design and long-term maintainability.

Follow him on Twitter @zoranh75 to receive updates and links to new articles.

Watch Zoran's video courses at pluralsight.com (requires registration):

Making Your C# Code More Object-Oriented

This course will help leverage your conceptual understanding to produce proper object-oriented code, where objects will completely replace procedural code for the sake of flexibility and maintainability. More...

Advanced Defensive Programming Techniques

This course will lead you step by step through the process of developing defensive design practices, which can substitute common defensive coding, for the better of software design and implementation. More...

Tactical Design Patterns in .NET: Creating Objects

This course sheds light on issues that arise when implementing creational design patterns and then provides practical solutions that will make our code easier to write and more stable when running. More...

Tactical Design Patterns in .NET: Managing Responsibilities

Applying a design pattern to a real-world problem is not as straight-forward as literature implicitly tells us. It is a more engaged process. This course gives an insight to tactical decisions we need to make when applying design patterns that have to do with separating and implementing class responsibilities. More...

Tactical Design Patterns in .NET: Control Flow

Improve your skills in writing simpler and safer code by applying coding practices and design patterns that are affecting control flow. More...

Writing Highly Maintainable Unit Tests

This course will teach you how to develop maintainable and sustainable tests as your production code grows and develops. More...

Improving Testability Through Design

This course tackles the issues of designing a complex application so that it can be covered with high quality tests. More...

Share this article

webmasters