开发者

Adding a bifurcation to a fluent interface builder

I have a small framework that allows me to create a pipe&filter system. I had the idea of using fluent interfaces to build the pipe&filter system:开发者_StackOverflow中文版

        PipeFilter pipeFilter = PipeFilter.StartBuild()
            .AddFilter(new SomeFilter1())
            .AddFilter(new SomeFilter2())
            .AddFilter(new SomeFilter3())
            .AddFilter(new SomeFilter4())
            .Build();

The shown code works as expected. Here is a "picture" of the system:

SomeFilter1 -> SomeFilter2 -> SomeFilter3 -> SomeFilter4

Now, there is a kind of Filter that instead of one output has two, instead. I call it bifurcation. Here is an example of a system with a bifurcation:

              |-> SomeFilter2 -> SomeFilter3
SomeFilter1 --|
              |-> SomeFilter4

I'd like to implement something like this:

        PipeFilter pipeFilter = PipeFilter.StartBuild()
            .AddFilter(new SomeFilter1())
            .AddBifurcation()
                .Output1()
                    .AddFilter(new SomeFilter2())
                    .AddFilter(new SomeFilter3())
                .Output2()
                    .AddFilter(new SomeFilter4())
            .Build();

But it seems I just can't get it right. Is this even possible, to do? In the first example, I just needed a PipeFilterBuilder (that is being returned by PipeFilter.StartBuild()). In this second example, I've tried creating other kinds of builders to bring into the mix but that seems to be of no avail.

Forgot to mention, the idea would be that I could nest bifurcations anywhere I want, that is, I could get "trees" full of branches!

Can anybody be of any help with this?


I would go the following way

PipeFilter pipeFilter = PipeFilter.StartBuild()
        .AddFilter(new SomeFilter1())
        .AddBifurcation(
              withOutput(1)
                 .AddFilter(new SomeFilter2())
                 .AddFilter(new SomeFilter3()), /* this separates first and second output */
              withOutput(2)
                .AddFilter(new SomeFilter4())
              )
        .Build();

In more format terms, I define the Bifurcation class to be an implementor of the Filter interface.

A bifurcation can have any number of Output filters, chained using Output object. To distinguish these output objects, they all have an index.

As a consequence, addBifurcation create a new Bifurcation object and adds it, while withOutput(int) is a static method creating an Output object, which has all the required method. Notice this point implies you get rid of the classical distinction between Builder and builded object, in favor of a code where the Builder methods are defined in the base interface for Filter.


I think you are being constrained by your notation. At the lowest level, your filtering system can be composed of primitive filters, and sequential and parallel compositions. Your first example could be written (in pseudocode):

pipeFilter = Seq(new SomeFilter1(), 
                 Seq(new SomeFilter2(), 
                     Seq(new SomeFilter3(), new SomeFilter4())));

With such an interface it is completely obvious how to add parallel -- or really any other kind of combinator -- into the interface:

pipeFilter = Seq(new SomeFilter1(), 
                Parallel(Seq(new SomeFilter2(), new SomeFilter3()),
                         Seq(new SomeFitler4())));

Cumbersome though it may seem, I would suggest building your interface this way (called a "functional" as oppposed to "imperative" interface), and then writing convenience methods to reduce some of the structural burden, for example variants of Seq and Parallel that take an arbitrary numbers of arguments -- but it would probably be best to have those simply delegate down to folds of the binary variants.

To elaborate on the subtler design issue here, the class or interface with which you are working is a filter builder, not a filter itself. Parallel and Seq are methods on that class. This gives you the option to implement the combinators in multiple ways for multiple interpretations. I would write the interface like this:

interface FilterBuilder<Filter> {
    Filter Seq(Filter a, Filter b);
    Filter Parallel(Filter a, Filter b);
}

It may not be perfect for your needs, but it is a nice, flexible design pattern that doesn't seem to be widely known.


It is possible to implement the system as you've designed it.

You won't need any builder other than PipeFilterBuilder, but you will need a data structure capable of representing your tree of Filters. Your PipeFilterBuilder then holds a reference to this structure and keeps track of the current insertion point.

Any operation you perform on PipeFilterBuilder needs to update the insertion point and return the builder itself (this). Calling AddBifurcation would add the current insertion point to, say, a stack. Conversely, Output2 would set the insertion point to the value popped off the stack. Other functions should be fairly trivial.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜