1

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?

bartek618
  • 11
  • 1
  • Did you mean `class End : TransformationStep`? – Sweeper Jul 14 '22 at 14:27
  • This looks hauntingly familiar: [TPL DataFlow](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library) ... [TransformBlock](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.dataflow.transformblock-2?view=net-6.0), [ActionBlock](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.dataflow.actionblock-1?view=net-6.0) - _"do you know any good materials on stuff like that?"_ - have a look into those links. – Fildor Jul 14 '22 at 14:39
  • @Sweeper I didn't know I can do that :) Thanks – bartek618 Jul 14 '22 at 14:57
  • Free example : https://dotnetfiddle.net/POnbk7 – Fildor Jul 14 '22 at 15:14
  • Async Example: https://dotnetfiddle.net/Uw360H – Fildor Jul 14 '22 at 15:35

1 Answers1

0

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" }
);
StepUp
  • 36,391
  • 15
  • 88
  • 148