流畅接口设计与代码异味

5
public class StepClause
{
    public NamedStepClause Action1() {}

    public NamedStepClause Action2() {}
}

public class NamedStepClause : StepClause
{
    public StepClause Step(string name) {}
}

基本上,我希望能够做到像这样的事情:
var workflow = new Workflow().Configure()
    .Action1()
    .Step("abc").Action2()
    .Action2()
    .Step("def").Action1();

因此,一些“步骤”被命名,而另一些则没有。
我不喜欢的是StepClause知道它的派生类NamedStepClause。
我试过几种方法来让自己感觉更好。 我试图将事情移到接口上,但问题只是从具体类移动到接口——INamedStepClause仍然需要从IStepClause派生,并且IStepClause需要返回INamedStepClause才能调用Step()。 我还可以使Step()成为完全独立类型的一部分。 然后我们就不会有这个问题了,我们会有:
var workflow = new Workflow().Configure()
    .Step().Action1()
    .Step("abc").Action2()
    .Step().Action2()
    .Step("def").Action1();

这样做是可以的,但如果可能的话,我希望使步骤命名成为可选项。

我在SO上找到了另一篇文章 (链接在此),看起来很有趣,很有前途。你有什么看法?我认为原始解决方案完全不可接受,或者说它是可以接受的吗?

顺便说一下,那些操作方法将使用谓词和函数对象,我认为我不想在那里再添加一个参数来命名步骤。

所有这一切的目的,对我来说,就是只在一个地方定义这些操作方法。因此,通过使用泛型和扩展方法的解决方案似乎是迄今为止最好的方法。


我就是不喜欢我得到的大多数答案。另一个原因是我会忘记 ;P - Jiho Han
1个回答

8
我可以为您提供两个选项。
选项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; }
    }
}

选项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; }
    }
}

两个选项都可以编译并为你提供你所需的流畅接口。我更倾向于选择方案A,因为它使你能够访问类的内部工作原理。使用扩展方法意味着你可能需要给予某种外部访问你的类的权限,从而破坏了封装性。

祝你好运!


1
我会选择选项A,并更改NamedStepClause中的Step方法 - 它应该返回StepClause<NamedStepClause>而不是NamedStepClause; 否则,我会陷入这样一种情况:可以执行Step("1").Step("2").Step("3")... 或许那个要求没有表达清楚 - 对不起。 - Jiho Han
选项B实际上由于我在上面的评论中所述的原因而不起作用。 - Jiho Han
1
自从我学习C++以来,我就没有见过这样的模板/泛型魔法。不错。=] - strager

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接