Chain of transformations based on generic types

264 Views Asked by At

I'm thinking about a chain, that will allow me to perform a set of transformations with data type changing from transformation to transformation. So far I've got something like this:

public abstract class TransformationStep<T, TY>
{
    public abstract TY Execute(T input);
}

public class Times2<TY> : TransformationStep<int, TY>
{
    public Times2(TransformationStep<int, TY> next)
    {
        Next = next;
    }

    public TransformationStep<int, TY> Next { get; set; }

    public override TY Execute(int input)
    {
        var res = input * 2;
        return Next.Execute(res);
    }
}

public class ToString<TY> : TransformationStep<int, TY>
{
    public ToString(TransformationStep<string, TY> next)
    {
        Next = next;
    }

    public TransformationStep<string, TY> Next { get; }

    public override TY Execute(int input)
    {
        var res = input + "!!!";
        return Next.Execute(res);
    }
}

The only problem I see is the end chain type, where I can't convert T to TY.

public class End<T, TY> : TransformationStep<T, TY>
{
    public override TY Execute(T input)
    {
        return input;
    }
}

Do you have any solution? Also what do you think about this design and do you know any good materials on stuff like that?

1

There are 1 best solutions below

0
StepUp On

It looks like you want transformations:

public abstract class TransformationStep<TIn, TOutput>
{
    public abstract TOutput Execute(TIn input);
}

And if you want to just return input type, then it is possible to create another method with return type:

public abstract class TransformationStep<TIn, TOutput>
{
    public abstract TOutput Execute(TIn input);

    public abstract TIn ExecuteWithoutTransformation(TIn input);
}

Dataflow

If you want to connect chains of data into pipeline or graph, then you can use TransformBlock.

What is about Chain of Responsibility pattern?

As wiki says about "Chain of Responsibility pattern":

In object-oriented design, the chain-of-responsibility pattern is a behavioral design pattern consisting of a source of command objects and a series of processing objects.2 Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.

Your code looks similar, however, code and goal of chain responsibility pattern is slightly different. It does not make transformations, it gives object to the next processing object in the chain.

So one of the variations of code of chain of the responsibility pattern can look like this:

An abstraction of desired behaviour of chain of the responsibility pattern:

public abstract class MyHandler<T>
{
    private MyHandler<T> Next { get; set; }

    public virtual void Handle(T request)
    {
        Next?.Handle(request);
    }

    public MyHandler<T> SetNext(MyHandler<T> next)
    {
        Next = next;
        return Next;
    }
}

And let us imagine that we are publishing house and we want that each property of article should be validated. So, concrete implemetations of handling article can look like this:

public class OnlyNewArticleValidationHandler : MyHandler<Document>
{
    public override void Handle(Document document)
    {
        if (document.DateCreated.Year < DateTime.Now.Year)
        {
            throw new Exception("Only new articles should be published.");
        }

        base.Handle(document);
    }
}


public class AuthorValidationHandler : MyHandler<Document>
{
    public override void Handle(Document document)
    {
        if (string.IsNullOrWhiteSpace(document.Author))
        {
            throw new Exception("Author is required.");
        }

        base.Handle(document);
    }
}


public class NameRequiredValidationHandler : MyHandler<Document>
{
    public override void Handle(Document document)
    {
        if (string.IsNullOrWhiteSpace(document.Name))
        {
            throw new Exception("Name is required.");
        }

        base.Handle(document);
    }
}

And ArticleProcessor would look like this:

public class MyChainAticleProcessor
{
    public void Validate(Document document)
    {
        var handler = new NameRequiredValidationHandler();
        handler.SetNext(new AuthorValidationHandler())
            .SetNext(new OnlyNewArticleValidationHandler());

        handler.Handle(document);
    }
}

And it can be run like this:

new MyChainAticleProcessor().Validate(
    new Document { Author = "Author 1", Name="Name 1" }
);