减少类中通过组合实现接口的样板


11

我有一个类:A这是一个综合了一些更小的类,的BCD

BCD实现接口IBICID分别。

由于A支持的所有功能BC并且DA器具IBICID为好,但不幸的是这导致了大量的重路由在执行A

像这样:

interface IB
{
    int Foo {get;}
}

public class B : IB
{
    public int Foo {get {return 5;}}
}

interface IC
{
    void Bar();
}

public class C : IC
{
    public void Bar()
    {
    }
}

interface ID
{
    string Bash{get; set;}
}

public class D: ID
{
    public string Bash {get;set;}
}

class A : IB, IC, ID
{
    private B b = new B();
    private C c = new C();
    private D d = new D();

    public int Foo {get {return b.Foo}}

    public A(string bash)
    {
        Bash = bash;
    }

    public void Bar()
    {
        c.Bar();
    }

    public string Bash
    {
         get {return d.Bash;}
         set {d.Bash = value;}
    }
}

有没有办法摆脱任何样板重定向ABCD都实现了不同,但共同的功能,我这样的A工具IBIC而且ID因为这意味着我可以通过A自己的匹配这些接口的依赖,而不必暴露内部的辅助方法。


您能否说一下为什么需要A类来实现IB,IC,ID?为什么不只是公开公开BCD?
Esben Skov Pedersen

1
我觉得我的喜欢主要原因A落实IBIC并且ID是感觉比较明智的写入Person.Walk(),而不是Person.WalkHelper.Walk(),例如。
尼克·乌德尔2015年

Answers:


7

您正在寻找的通常称为mixins。可悲的是,C#本身不支持这些功能。

解决方法很少:一种两种三种以及更多种。

我真的很喜欢最后一个。使用自动生成的局部类来生成样板的想法可能是最接近实际好的解决方案的想法:

[pMixins]是一个Visual Studio插件,它扫描解决方案以查找装饰有pMixin属性的部分类。通过将您的班级标记为部分,[pMixins]可以创建一个代码隐藏文件,并将其他成员添加到您的班级


2

虽然它不会减少代码本身的样板,但Visual Studio 2015现在带有一个重构选项,可以自动为您生成样板。

要使用此功能,请首先创建您的接口IExample和实现Example。然后创建您的新类Composite并使其继承IExample,但不要实现该接口。

类型的属性或字段添加Example到您的Composite类,打开令牌上快速操作菜单IExample中的Composite类文件,并选择“通过‘样本’实现接口”,其中“样本”在这种情况下是字段或属性的名称。

您应该注意,尽管Visual Studio会为您生成并将接口中定义的所有方法和属性重定向到该帮助器类,但在发布此答案时,它不会重定向事件。


1

您的课程做得太多,这就是为什么您必须实现这么多样板的原因。您在评论中说:

与Person.WalkHelper.Walk()相比,编写Person.Walk()感觉更明智

我不同意。步行行为可能涉及许多规则,并且不属于Person该类-它属于WalkingService使用walk(IWalkable)Person实现的方法的类IWalkable

编写代码时,请始终牢记SOLID原则。这里最适用的两个是关注点分离(将步行代码提取到单独的类中)和接口隔离(将接口拆分为特定的任务/功能/能力)。听起来您可能具有多个接口的I,但随后通过使一个类实现它们来撤消所有的出色工作。


在我的示例中,功能在单独的类中实现的,并且我有一个由几个这样的类组成的类,可以将所需的行为进行分组。在我的较大的类中没有任何实际的实现,而是由较小的组件处理的。不过,也许您是对的,并且公开那些帮助程序类,以便可以直接与他们进行交互是一种方法。
尼克·乌德尔2015年

4
至于方法的想法walk(IWalkable),我个人不喜欢它,因为步行服务成为呼叫者而非个人的责任,并且不能保证一致性。任何调用者都必须知道要使用哪个服务来保证一致性,而将其保留在Person类中意味着必须手动更改它才能使行走行为有所不同,同时仍然允许依赖项反转。
尼克·乌德尔2015年

0

您正在寻找的是多重继承。但是,C#和Java都没有。

您可以从A扩展B。这将消除对B和重定向胶水的私有字段的需求。但是您只能对B,C或D之一执行此操作。

现在,如果您可以使C继承自B,而D则继承自C,那么您只需要使A扩展D ...;)-尽管要明确,由于没有迹象表明,我实际上并不鼓励这样做为了它。

在C ++中,您将能够从B,C和D继承A。

但是多重继承使程序难以推理,并且有其自身的问题。此外,通常有一种干净的方法可以避免这种情况。尽管如此,有时甚至没有它,仍然需要在重复样板与对象模型的公开方式之间进行设计折衷。

感觉就像您正在尝试将合成和继承混合在一起。如果是C ++,则可以使用纯继承解决方案。但由于并非如此,因此我建议您接受构图。


2
就像op在他的示例代码中所做的那样,您可以使用java和c#中的接口进行多重继承。
罗伯特·哈维

1
有些人可能考虑实现与多重继承相同的接口(例如在C ++中)。其他人则不同意,因为接口无法提供可继承的实例方法,而如果您具有多个继承,则该方法将是可继承的。在C#中,您不能扩展多个基类,因此,您不能从多个类继承实例方法的实现,因此,您需要所有样板...
Erik Eidt 2015年

我同意多重继承是不好的,但是C#/ Java的具有多个接口实现的单继承的替代方法并不能解决所有问题(没有接口转发,这是人体工程学的噩梦)。在使用Golang一段时间之后,我认为Go有了一个正确的想法,即不具有任何继承,而是完全依赖于带有接口转发的组合-但是它们的特定实现也存在问题。我认为最好的解决方案(似乎还没有人实现)是具有转发功能的组合,并且可以使用任何类型的接口。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.