Fluent interface design and code smell
public class StepClause
{
public NamedStepClause Action1() {}
public NamedStepClause Action2() {}
}
public class NamedStepClause : StepClause
{
public StepClause Step(string name) {}
}
Basically, I want to be able to do something like this:
var workflow = new Workflow().Configure()
.Action1()
.Step("abc").Action2()
.Action2()
.Step("def").Action1();
So, some "steps" are named and some are not.
The thing I do not like is that the StepClause has knowledge of its derived class NamedStepClause.
I tried a couple of things to make this sit better with me. I tried to move things out to interfaces but then the problem just moved from the concrete to the interfaces - INamedStepClause still need to derive from IStepClause and IStepClause needs to return INamedStepClause to be able to call Step(). I could also make Step() part of a completely separate type. Then we do not have this problem and we开发者_Go百科'd have:
var workflow = new Workflow().Configure()
.Step().Action1()
.Step("abc").Action2()
.Step().Action2()
.Step("def").Action1();
Which is ok but I'd like to make the step-naming optional if possible.
I found this other post on SO here which looks interesting and promising. What are your opinions? I'd think the original solution is completely unacceptable or is it?
By the way, those action methods will take predicates and functors and I don't think I want to take an additional parameter for naming the step there.
The point of it all is, for me, is to only define these action methods in one place and one place only. So the solutions from the referenced link using generics and extension methods seem to be the best approaches so far.
I'll give you two options.
Option A
var a = new A.NamedStepClause();
a.Action1()
.Step("abc").Action2()
.Action2()
.Step("def").Action1();
namespace A
{
public class StepClause<SC> where SC : StepClause<SC>
{
public SC Action1() { return null; }
public SC Action2() { return null; }
}
public class NamedStepClause : StepClause<NamedStepClause>
{
public NamedStepClause Step(string name) { return null; }
}
}
Option B
var b = new B.StepClause();
b.Action1()
.Step("abc").Action2()
.Action2()
.Step("def").Action1();
namespace B
{
public class StepClause
{
public StepClause Action1() { return null; }
public StepClause Action2() { return null; }
}
public static class StepClauseExtensions
{
public static StepClause Step(this StepClause @this, string name)
{ return null; }
}
}
Both options compile and give you the fluent interface that you're looking for. I'm more inclined to go with option A as it gives you access to the inner workings of the class. Using extension methods means you may need to give some sort of external access to your class thus breaking encapsulation.
Good luck!
精彩评论