如果我已经有一个抽象类,定义一个接口是否有意义?


12

我有一类带有一些默认/共享功能的类。我用abstract class它:

public interface ITypeNameMapper
{
    string Map(TypeDefinition typeDefinition);
}

public abstract class TypeNameMapper : ITypeNameMapper
{
    public virtual string Map(TypeDefinition typeDefinition)
    {
        if (typeDefinition is ClassDefinition classDefinition)
        {
            return Map(classDefinition);
        }
        ...

        throw new ArgumentOutOfRangeException(nameof(typeDefinition));
    }

    protected abstract string Map(ClassDefinition classDefinition);
}

如您所见,我也有接口ITypeNameMapper。如果我已经有一个抽象类,TypeNameMapper或者abstract class已经足够,定义这个接口是否有意义?

TypeDefinition 在这个最小的例子中也是抽象的。


4
仅供参考-在OOD中,检查代码中的类型表明您的类层次结构不正确-告诉您从要检查的类型创建派生类。因此,在这种情况下,您需要一个接口ITypeMapper,该接口指定一个Map()方法,然后在TypeDefinition和ClassDefinition类中都实现该接口。这样,您的ClassAssembler可以简单地遍历ITypeMappers的列表,在每种类型上调用Map(),并从两种类型中获取所需的字符串,因为它们各自都有自己的实现。
罗德尼·巴尔巴蒂

1
除非绝对必要,否则使用基类代替接口称为“刻录基类”。如果可以通过接口完成操作,请通过接口进行操作。然后,抽象类将成为一个有用的额外功能。artima.com/intv/dotnet.html 特别地,远程处理要求您从MarshalByRefObject派生,因此,如果要进行远程处理,则不能从其他任何派生。

如果我希望根据情况可以切换同一类型的许多映射器,则@ RodneyP.Barbati将无法正常工作
Konrad

1
@Ben燃烧了有趣的基础。谢谢你的宝石!
康拉德'18

@Konrad这是不正确的,没有什么可以阻止您通过调用接口的另一个实现来实现该接口,因此,每种类型都可以具有通过该类型中的实现公开的可插拔实现。
罗德尼·巴尔巴蒂

Answers:


31

是的,因为C#除接口外不允许多重继承。

因此,如果我有一个同时是TypeNameMapper和SomethingelseMapper的类,我可以这样做:

class MultiFunctionalClass : ITypeNameMapper, ISomethingelseMapper 
{
    private TypeNameMapper map1
    private SomethingelseMapper map2

    public string Map(TypeDefinition typeDefinition) { return map1.Map(typeDefintion);}

    public string Map(OtherDef otherDef) { return map2.Map(orderDef); }
}

4
@Liath:单一责任实际上是为什么需要继承的一个促成因素。考虑三种可能的特质:ICanFlyICanRunICanSwim。您需要创建8个生物类别,每一个特征类别(YYY,YYN,YNY,...,NNY,NNN)组合一个。SRP指示您一次执行每种行为(飞行,奔跑,游泳),然后在8个生物上重复使用它们。这里的组合甚至会更好,但是一秒钟忽略组合,您应该已经看到由于SRP而需要继承/实现多个单独的可重用行为。
扁平的

4
@Konrad,这就是我的意思。如果您编写该接口,而不再需要它,则成本为3 LoC。如果您不编写接口,但确实需要它,那么代价可能是重构整个应用程序。
伊万

3
(1)实现接口是继承吗?(2)问答均应合格。“这取决于。” (3)多重继承需要警告标签。(4)我可以向您展示K的形式繁琐的形式interface不当行为..应该在基础上进行冗余的实现-它们正在独立发展!,条件逻辑的变化导致该垃圾邮件缺乏模板方法,封装破裂,不存在抽象。我最喜欢的:1方法类各自实现自己的1方法interface,每个仅具有一个实例。...等等等等,等等
–radarbob

3
代码中有看不见的摩擦系数。当您被迫读取多余的接口时,您会感觉到。然后,当接口实现将抽象类推向高潮时,它就像航天飞机猛烈撞击大气一样显示出自身的发热。这是暮光区,这是挑战者号穿梭巴士。接受我的命运,我以无所畏惧,超脱的奇迹看着汹涌的血浆。无情的摩擦撕裂了完美的外表,使粉碎的绿巨人带着雷鸣般的震颤进入了生产。
–radarbob

4
@radarbob诗意的。^ _ ^我同意;尽管接口的成本很低,但它并非不存在。编写它们并不需要太多,但是在调试的情况下,要达到实际的行为还需要1-2个步骤,当您深入了解六个抽象级别时,这些步骤可能会迅速加起来。在图书馆,通常是值得的。但是在应用程序中,重构比过早的泛化要好。
Errorsatz

1

接口和抽象类具有不同的用途:

  • 接口定义API,并且属于客户端而不是实现。
  • 如果类共享实现,那么您可以从抽象类中受益。

在您的示例中,interface ITypeNameMapper定义客户的需求并且abstract class TypeNameMapper没有增加任何价值。


2
抽象类也属于客户端。我不知道那是什么意思。一切都public属于客户端。TypeNameMapper之所以会增加很多价值,是因为它可以使实现者免于编写一些常见的逻辑。
康拉德

2
接口是否呈现为interface仅与C#禁止完全多重继承有关。
重复数据删除器

0

创建接口的整个概念是为了支持具有共享API的一类类。

明确地说,对接口的任何使用都意味着(或预期将有)接口规范的一个以上实现。

DI框架使这里陷入混乱,因为即使只有一个实现,它们中的许多框架也需要接口-对我来说,这是不合理的开销,因为在大多数情况下,这只是更复杂,更慢的调用new的方法,但是就是这样

答案在于问题本身。如果您有一个抽象类,则准备使用通用API创建多个派生类。因此,清楚地指示了接口的使用。


2
“如果您有一个抽象类,则……将明确指出使用接口。” -我的结论是相反的:如果您有一个抽象类,则将其用作接口(如果DI不使用它,请考虑使用其他实现)。仅当确实无法使用时,才创建界面-您以后可以随时进行操作。
maaartinus

1
同上,@ maaartinus。甚至没有“……好像是一个接口”。类的公共成员一个接口。同样是“您以后可以随时做”;这是设计基本原理101的核心。
radarbob

@maaartinus:如果从头开始创建一个接口,但是到处都使用抽象类类型的引用,那么该接口的存在将无济于事。但是,如果客户端代码在任何可能的地方使用接口类型而不是抽象类类型,则如果有必要产生一个与抽象类内部非常不同的实现,那么这将极大地简化事情。那时更改客户端代码以使用该接口将比设计客户端代码从一开始就痛苦得多。
超级猫

1
@supercat我可以同意假设您正在编写库。如果它是一个应用程序,那么一次替换所有实例将不会有任何问题。我的Eclipse可以做到(Java,而不是C#),但是如果做不到,那就像find ... -exec perl -pi -e ...并且有可能修复琐碎的编译错误。我的观点是:没有(当前)无用的东西的好处比将来可能节省的钱要大得多。
maaartinus

如果您将interface代码标记为代码(您在谈论C#- interface,而不是抽象接口,就像任何类的类/函数/等那样),并且以“ ...结尾”,但第一句是正确的。遗产。” interface如果您有MI,则无需。
重复数据删除器'18

0

如果我们想明确回答这个问题,那么作者说:“如果我已经有了一个抽象类TypeNameMapper或抽象类就足够了,那么定义这个接口是否有意义?”

答案是是和不是-是,即使您已经有抽象基类(因为您不应该在任何客户端代码中引用抽象基类),也应该创建接口,否则,因为不应该创建接口没有接口的抽象基类。

很明显,您没有对要构建的API进行足够的考虑。首先提供API,然后根据需要提供部分实现。您的抽象基类确实在代码中进行类型检查的事实足以告诉您这不是正确的抽象。

就像OOD中的大多数事情一样,当您陷入困境并且对象模型做得很好时,您的代码将通知您接下来的工作。从一个接口开始,在所需的类中实现该接口。如果您发现自己编写了类似的代码,请将其提取到一个抽象基类中-它是重要的接口,抽象基类只是一个助手,并且可能不止一个。


-1

如果不了解应用程序的其余部分,很难回答。

假设您使用的是某种DI模型,并且已将代码设计为尽可能可扩展(因此您可以TypeNameMapper轻松添加新代码),我会说是的。

的原因:

  • 您可以有效地免费获得它,因为基类实现了interface您,无需在任何子实现中都担心它
  • 大多数IoC框架和Mocking库都期望interfaces。尽管它们中的许多确实与抽象类一起工作,但它并非总是给定的,我坚信尽可能与其他99%的用户一起走同样的道路。

反对的理由:

  • 严格来说(如您所指出的),您并不需要
  • 如果class/ interface更改,则会有一些额外的开销

考虑到所有事情,我会说您应该创建interface。反对的理由可以忽略不计,但是尽管可以在模拟和IoC中使用抽象,但使用接口通常要容易得多。但最终,如果他们选择相反的方式,我不会批评同事的密码。

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.