首页 文章

接口与基类

提问于
浏览
723

我应该何时使用接口,何时应该使用基类?

如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?

如果我有狗和猫类 . 为什么我要实现IPet而不是PetBase?我可以理解有ISheds或IBarks(IMakesNoise?)的接口,因为那些可以按宠物放在宠物上,但我不明白哪个用于通用Pet .

30 回答

  • 3

    通过def,接口提供了与其他代码通信的层 . 默认情况下,类的所有公共属性和方法都实现隐式接口 . 我们还可以将接口定义为角色,当任何类需要扮演该角色时,它必须实现它,根据实现它的类,为其提供不同的实现形式 . 因此,当你谈论接口时,你谈论的是多态性,当你谈论基类时,你谈论的是继承 . oops的两个概念!!!

  • 2

    接口和基类代表两种不同形式的关系 .

    Inheritance (基类)表示"is-a"关系 . 例如 . 一只狗或一只猫"is-a"宠物 . 此关系始终表示类的(单个) purpose (与"single responsibility principle"一起) .

    另一方面, Interfaces 代表一个类的 additional features . 我称之为"is"关系,就像“ Foo 是一次性的”,因此C#中的 IDisposable 接口 .

  • 4

    关于C#,在某种意义上,接口和抽象类可以互换 . 但是,不同之处在于:i)接口无法实现代码; ii)因此,接口无法进一步调用堆栈到子类; iii)只有抽象类可以在类上继承,而多个接口可以在类上实现 .

  • 2

    另外请记住,不要在OO(see blog)中被扫除并始终根据所需行为建模对象,如果您正在设计一个应用程序,其中您需要的唯一行为是动物的通用名称和物种,那么您只需要一个class动物的名称属性,而不是世界上每种可能动物的数百万个类 .

  • 106

    Java World article解释得很好

    我个人倾向于使用接口来定义接口 - 即系统设计的一部分,用于指定应该如何访问某些内容 .

    我将有一个实现1个或更多接口的类并不罕见 .

    抽象类我用作其他东西的基础 .

    以下是上述文章JavaWorld.com article, author Tony Sintes, 04/20/01的摘录


    接口与抽象类选择接口和抽象类不是一个或两个命题 . 如果您需要更改设计,请将其设为界面 . 但是,您可能具有提供某些默认行为的抽象类 . 抽象类是应用程序内的优秀候选者构架 . 抽象类让你定义一些行为;他们强迫你的子类提供其他人 . 例如,如果您有应用程序框架,则抽象类可以提供默认服务,例如事件和消息处理 . 这些服务允许您的应用程序插入您的应用程序框架 . 但是,有一些特定于应用程序的功能,只有您的应用程序才能执行 . 此类功能可能包括启动和关闭任务,这些任务通常取决于应用程序 . 因此,抽象基类可以声明抽象关闭和启动方法,而不是尝试定义该行为本身 . 基类知道它需要那些方法,但是一个抽象类让你的类承认它不知道如何执行这些操作;它只知道它必须启动行动 . 在启动时,抽象类可以调用启动方法 . 当基类调用此方法时,Java会调用子类定义的方法 . 许多开发人员忘记了定义抽象方法的类也可以调用该方法 . 抽象类是创建计划继承层次结构的绝佳方法 . 对于类层次结构中的非叶类,它们也是一个很好的选择 . 类与接口有人说你应该用接口来定义所有类,但我认为推荐看起来有点极端 . 当我看到设计中的某些内容经常发生变化时,我会使用接口 . 例如,策略模式允许您将新算法和流程交换到您的程序中,而无需更改使用它们的对象 . 媒体播放器可能知道如何播放CD,MP3和wav文件 . 当然,您不希望将这些播放算法硬编码到播放器中;这将使添加像AVI这样的新格式变得困难 . 此外,您的代码将充斥着无用的case语句 . 并且为了增加侮辱伤害,每次添加新算法时都需要更新这些案例语句 . 总而言之,这不是一种非常面向对象的编程方式 . 使用策略模式,您可以简单地将算法封装在对象后面 . 如果这样做,您可以随时提供新的媒体插件 . 我们来调用插件类MediaStrategy . 该对象将有一个方法:playStream(Stream s) . 因此,为了添加新算法,我们只需扩展我们的算法类 . 现在,当程序遇到新的媒体类型时,它只是将流的播放委托给我们的媒体策略 . 当然,您需要一些管道才能正确实例化您需要的算法策略 . 这是使用界面的绝佳场所 . 我们使用了策略模式,它清楚地表明了设计中将会发生变化的地方 . 因此,您应该将策略定义为接口 . 当您希望对象具有某种类型时,通常应该优先选择接口而不是继承;在这种情况下,MediaStrategy . 依赖继承进行类型识别是危险的;它会将您锁定到特定的继承层次结构中 . Java不允许多重继承,因此您无法扩展为您提供有用实现或更多类型标识的内容 .

  • 9

    让我们以你的Dog和Cat类为例,让我们用C#来说明:

    狗和猫都是动物,特别是四足动物的哺乳动物(动物过于笼统) . 让我们假设你有两个抽象类Mammal:

    public abstract class Mammal
    

    这个基类可能有默认方法,例如:

    • Feed

    • 伙伴

    所有这些都是在两个物种之间具有或多或少相同实施的行为 . 要定义它,您将拥有:

    public class Dog : Mammal
    public class Cat : Mammal
    

    现在让我们假设还有其他哺乳动物,我们通常会在动物园看到它们:

    public class Giraffe : Mammal
    public class Rhinoceros : Mammal
    public class Hippopotamus : Mammal
    

    这仍然有效,因为功能的核心 Feed()Mate() 仍然是相同的 .

    然而,长颈鹿,犀牛和河马并不是你可以养宠物的动物 . 这就是界面有用的地方:

    public interface IPettable
    {
        IList<Trick> Tricks{get; set;}
        void Bathe();
        void Train(Trick t);
    }
    

    上述 Contract 的实施在猫与狗之间是不一样的;将他们的实现放在一个抽象类中继承将是一个坏主意 .

    您的狗和猫定义现在应该如下所示:

    public class Dog : Mammal, IPettable
    public class Cat : Mammal, IPettable
    

    从理论上讲,您可以从更高的基类覆盖它们,但实质上,接口允许您只需将所需的内容添加到类中而无需继承 .

    因此,因为您通常只能从一个抽象类继承(在大多数静态类型的OO语言中,例外包括C)但是能够实现多个接口,它允许您严格按照要求构造对象 .

  • 15

    这是接口和基类的基本和简单定义:

    • 基类=对象继承 .

    • Interface =功能继承 .

    干杯

  • 12

    这是非常特定于.NET的,但是框架设计指南书认为,通用类在不断发展的框架中提供了更大的灵活性 . 一旦接口出厂,您就没有机会在不破坏使用该接口的代码的情况下进行更改 . 但是,对于类,您可以修改它,而不是破坏链接到它的代码 . 只要您做出正确的修改(包括添加新功能),您就可以扩展和改进代码 .

    Krzysztof Cwalina在第81页说:

    在.NET Framework的三个版本的过程中,我与我们团队中的不少开发人员讨论了这个指南 . 他们中的许多人,包括那些最初不同意这些指南的人,都表示他们后悔将一些API作为接口发布 . 我甚至没有听说过有人后悔他们发了一堂课 .

    话虽这么说肯定有接口的地方 . 作为一般指导,如果没有其他任何内容作为实现接口的方式的示例,则始终提供接口的抽象基类实现 . 在最好的情况下,基类将节省大量的工作 .

  • 2

    Prefer interfaces over abstract classes

    基本原理,要考虑的要点[这里已经提到过两个]是:

    • 接口更灵活,因为类可以实现多个接口 . 由于Java没有多重继承,因此使用抽象类可以防止用户使用任何其他类层次结构 . In general, prefer interfaces when there are no default implementations or state. Java集合提供了很好的例子(Map,Set等) .

    • 抽象类具有允许更好的向前兼容性的优点 . 客户端使用界面后,您无法更改它;如果他们使用抽象类,您仍然可以在不破坏现有代码的情况下添加行为 . If compatibility is a concern, consider using abstract classes.

    • 即使您有默认实现或内部状态, consider offering an interface and an abstract implementation of it . 这将有助于客户,但如果需要,仍然允许他们更大的自由[1] .
      当然,这个问题已在其他地方详细讨论[2,3] .

    [1]当然,它增加了更多代码,但如果简洁是您最关心的问题,那么您可能应该首先避免使用Java!

    [2] Joshua Bloch,Effective Java,第16-18项 .

    [3] http://www.codeproject.com/KB/ar ......

  • 3

    我建议尽可能使用合成而不是继承 . 使用接口但使用成员对象进行基本实现 . 这样,您可以定义一个工厂,构造您的对象以某种方式运行 . 如果要更改行为,则创建一个新的工厂方法(或抽象工厂),以创建不同类型的子对象 .

    在某些情况下,如果在辅助对象中定义了所有可变行为,您可能会发现主要对象根本不需要接口 .

    因此,您最终可能会使用Pet,而不是IPet或PetBase它有一个IFurBehavior参数 . IFurBehavior参数由PetFactory的CreateDog()方法设置 . 这个参数是为shed()方法调用的 .

    如果你这样做,你会发现你的代码更灵活,你的大多数简单对象都处理非常基本的系统行为 .

    即使在多重继承语言中,我也推荐这种模式 .

  • 5

    这取决于您的要求 . 如果IPet足够简单,我宁愿实现它 . 否则,如果PetBase实现了大量功能,您不想复制,那么请使用它 .

    实现基类的缺点是需要 override (或 new )现有方法 . 这使得它们成为虚拟方法,这意味着您必须小心如何使用对象实例 .

    最后,.NET的单一继承杀了我 . 一个简单的例子:假设您正在创建用户控件,因此您继承了 UserControl . 但是,现在你被锁定也继承了 PetBase . 这会强制您重新组织,例如创建一个 PetBase 类成员 .

  • 6

    好吧,Josh Bloch在Effective Java 2d说:

    首选接口而不是抽象类

    一些要点:

    可以轻松改进现有类以实现新接口 . 您所要做的就是添加所需的方法(如果它们尚不存在)并将一个implements子句添加到类声明中 . 接口非常适合定义mixins . 松散地说,mixin是类可以实现的类型,除了它的“主类型”之外,它声明它提供了一些可选行为 . 例如,Comparable是一个mixin接口,允许类声明其实例相对于其他可相互比较的对象进行排序 . 接口允许构造非分层类型框架 . 类型层次结构非常适合组织某些事物,但其他事情并不完全属于严格的层次结构 . 接口通过包装类习惯用法实现安全,强大的功能增强 . 如果使用抽象类来定义类型,那么让想要添加功能的程序员除了使用继承之外别无选择 . 此外,您可以通过提供抽象骨架实现类来组合接口和抽象类的优点,以与您导出的每个非平凡接口一起使用 .

    另一方面,接口很难发展 . 如果向接口添加方法,它将破坏它的所有实现 .

    PS:买这本书 . 它更加详细 .

  • 56

    Submain .NET编码指南中详细解释了基于接口的基类的情况:

    基类与接口接口类型是值的部分描述,可能由许多对象类型支持 . 尽可能使用基类而不是接口 . 从版本控制的角度来看,类比接口更灵活 . 使用类,您可以发布版本1.0,然后在版本2.0中向类添加新方法 . 只要该方法不是抽象的,任何现有的派生类都将继续保持不变 . 由于接口不支持实现继承,因此适用于类的模式不适用于接口 . 向接口添加方法等同于向基类添加抽象方法;任何实现接口的类都会中断,因为该类没有实现新方法 . 接口适用于以下情况:几个不相关的类希望支持该协议 . 这些类已经 Build 了基类(例如,一些是用户界面(UI)控件,一些是XML Web服务) . 汇总不合适或不切实际 . 在所有其他情况下,类继承是更好的模型 .

  • 5

    当我第一次开始学习面向对象编程时,我犯了一个简单而且可能是使用继承来共享共同行为的常见错误 - 即使这种行为对于对象的本质并不重要 .

    为了进一步 Build 一个在这个特定问题中使用的例子,有许多可以用的东西 - 女朋友,汽车,模糊毛毯...... - 所以我可能有一个Petable类提供了这种常见的行为,并且各种类继承从中 .

    但是,容易接受不属于任何这些对象的性质 . 那里是非常重要的概念,对他们的本性至关重要 - 女朋友是一个人,汽车是陆地车辆,猫是哺乳动物......

    应首先将行为分配给接口(包括类的默认接口),并且只有当它们(a)对于作为较大类的子集的大类类是通用的时才提升为基类 - 与“猫”和“人”是“哺乳动物”的子集 .

    问题是,在您完全了解面向对象设计之后,您通常会自动执行此操作,而不必考虑它 . 所以“代码接口而不是抽象类”这一陈述的明显事实变得如此明显,你很难相信任何人都会费心去说 - 并开始尝试将其他含义读入其中 .

    我要添加的另一件事是,如果一个类纯粹是抽象的 - 没有非抽象的,非继承的成员或方法暴露给子,父或客户端 - 那么为什么它是一个类?它可以在某些情况下由接口替换,在其他情况下由Null替换 .

  • 2

    除非您知道它的含义,否则不要使用基类,并且它适用于这种情况 . 如果适用,请使用它,否则,使用接口 . 但请注意关于小接口的答案 .

    公共继承在OOD中被过度使用,并且比大多数开发人员意识到或愿意接受的表达要多得多 . 见Liskov Substitutablity Principle

    简而言之,如果A“是”B,那么对于它暴露的每种方法,A只需要不超过B并且不超过B .

  • 2

    接口应该很小 . 真的很小 . 如果你真的打破了你的对象,那么你的接口可能只包含一些非常具体的方法和属性 .

    抽象类是快捷方式 . 有没有PetBase的所有衍生品共享你可以编码一次并完成的东西?如果是,那么是抽象课的时间 .

    抽象类也是有限的 . 虽然它们为您提供了生成子对象的快捷方式,但任何给定对象都只能实现一个抽象类 . 很多时候,我发现这是Abstract类的限制,这就是我使用大量接口的原因 .

    抽象类可能包含多个接口 . 你的PetBase抽象类可以实现IPet(宠物拥有者)和IDigestion(宠物吃,或至少他们应该) . 然而,PetBase可能不会实施IMammal,因为并非所有宠物都是哺乳动物,并非所有哺乳动物都是宠物 . 您可以添加一个扩展PetBase并添加IMammal的MammalPetBase . FishBase可以拥有PetBase并添加IFish . IFish将ISwim和IUnderwaterBreather作为接口 .

    是的,我的例子对于这个简单的例子来说过于复杂,但这是关于接口和抽象类如何协同工作的一部分 .

  • 2

    我有一个粗略的经验法则

    Functionality: 可能在所有部分都有所不同:界面 .

    Data, and functionality, parts will be mostly the same, parts different: 抽象类 .

    Data, and functionality, actually working, if extended only with slight changes: 普通(具体)类

    具有最终修饰符的 Data and functionality, no changes planned: 普通(具体)类 .

    Data, and maybe functionality: read-only: 枚举成员 .

    这是非常粗略和准备好的,并没有严格定义,但是有一个来自接口的频谱,其中所有内容都要更改为枚举,其中所有内容都修复有点像只读文件 .

  • 136

    胡安,

    我喜欢将接口视为表征类的一种方式 . 一个特定的狗品种,比如YorkshireTerrier,可能是父狗类的后代,但它也实现了IFurry,IStubby和IYippieDog . 所以这个类定义了类是什么,但接口告诉我们关于它的事情 .

    这样做的好处是它允许我收集所有的IYippieDog并将它们扔进我的Ocean系列 . 因此,现在我可以跨越一组特定的对象,找到符合我正在查看的标准的对象,而无需过于仔细地检查课程 .

    我发现接口确实应该定义一个类的公共行为的子集 . 如果它定义了所有实现的类的所有公共行为,那么它通常不需要存在 . 他们没有告诉我任何有用的东西 .

    这个想法虽然与每个类都应该有一个接口并且你应该编写接口的想法背道而驰 . 这很好,但是你最终会遇到很多一对一的类接口,这让事情变得混乱 . 据我所知,这个想法并不需要花费任何费用,现在你可以轻松地进出东西 . 但是,我发现我很少这样做 . 大多数时候我只是修改现有的类,并且如果该类的公共接口需要更改,我总是会遇到完全相同的问题,除了我现在必须在两个地方更改它 .

    因此,如果你像我一样思考,你肯定会说Cat和Dog是IPettable . 这是一种与它们相匹配的特征 .

    另一部分是他们应该有相同的基类吗?问题是他们需要被广泛地视为同一件事 . 当然它们都是动物,但这适合我们将要一起使用它们 .

    假设我想收集所有Animal类并将它们放入我的Ark容器中 .

    还是他们需要成为哺乳动物?也许我们需要某种跨动物挤奶工厂?

    他们甚至需要联系在一起吗?只知道它们都是IPettable就足够了吗?

    当我真正需要一个 class 时,我常常觉得有必要得出一个完整的类层次结构 . 我希望总有一天我可能需要它,通常我永远不会这样做 . 即使我这样做,我也常常发现我必须做很多事来解决它 . 那是因为我创造的第一堂课不是狗,我不是那么幸运,而是鸭嘴兽 . 现在我的整个类层次结构基于奇怪的情况,我有很多浪费的代码 .

    您可能还会发现,并非所有Cats都是IPettable(就像那个无毛的那样) . 现在,您可以将该接口移动到适合的所有派生类 . 你会发现一个不那么突破的变化,突然之间的Cats不再来自PettableBase .

  • 7

    @Joel:某些语言(例如C)允许多重继承 .

  • 468

    我发现Interface> Abstract> Concrete的模式在以下用例中起作用:

    1.  You have a general interface (eg IPet)
    2.  You have a implementation that is less general (eg Mammal)
    3.  You have many concrete members (eg Cat, Dog, Ape)
    

    抽象类定义具体类的默认共享属性,但强制执行接口 . 例如:

    public interface IPet{
    
        public boolean hasHair();
    
        public boolean walksUprights();
    
        public boolean hasNipples();
    }
    

    现在,由于所有的哺乳动物都有头发和乳头(AFAIK,我不是动物学家),我们可以把它变成抽象基类

    public abstract class Mammal() implements IPet{
    
         @override
         public walksUpright(){
             throw new NotSupportedException("Walks Upright not implemented");
         }
    
         @override
         public hasNipples(){return true}
    
         @override
         public hasHair(){return true}
    

    然后具体的课程只是定义他们直立行走 .

    public class Ape extends Mammal(){
    
        @override
        public walksUpright(return true)
    }
    
    public class Catextends Mammal(){
    
        @override
        public walksUpright(return false)
    }
    

    当有许多具体类时,这种设计很好,并且您不希望仅仅为了编程接口而维护样板 . 如果在接口中添加了新方法,则会破坏所有生成的类,因此您仍然可以获得接口方法的优势 .

    In this case, the abstract could just as well be concrete; however, the abstract designation helps to emphasize that this pattern is being employed.

  • 57

    在我需要之前,我通常都不会实施 . 我更喜欢接口而不是抽象类,因为它提供了更多的灵活性 . 如果某些继承类中存在共同行为,我会将其移动并创建一个抽象基类 . 我没有看到两者的需要,因为它们本质上服务于相同的目的,并且两者都是一个糟糕的代码味道(imho),解决方案已被过度设计 .

  • 47

    一个重要的区别是您只能继承 one 基类,但您可以实现 many 接口 . 所以你只想使用一个基类,如果你绝对肯定你赢了't need to also inherit a different base class. Additionally, if you find your interface is getting large then you should start looking to break it up into a few logical pieces that define independent functionality, since there'没有规则你的类不能全部实现它们(或者你可以定义一个不同的接口,只是继承它们以对它们进行分组) .

  • 1

    以前关于使用抽象类进行常规实现的评论肯定是标记的 . 我还没有看到的一个好处是,使用接口可以更容易地实现模拟对象以进行单元测试 . 如Jason Cohen所描述的那样定义IPet和PetBase使您可以轻松地模拟不同的数据条件,而无需物理数据库的开销(直到您决定测试真实数据库的时间) .

  • 12

    从概念上讲,接口用于正式和半正式地定义对象将提供的一组方法 . 形式上是指一组方法名称和签名,半正式地表示与这些方法相关的人类可读文档 . 接口只是API的描述(毕竟,API代表Application Programmer Interface ),它们可以't contain any implementation, and it'不能使用或运行接口 . 他们只明确说明你应该如何与一个对象进行交互 .

    类提供了一个实现,他们可以声明它们实现了零个,一个或多个接口 . 如果要继承Class,则约定是使用“Base”作为Class名称的前缀 .

    基类和抽象基类(ABC)之间存在区别 . ABCs将接口和实现混合在一起 . 计算机编程之外的摘要意味着“摘要”,即“抽象==接口” . 然后,抽象基类可以描述接口,以及要继承的空,部分或完整实现 .

    关于何时使用接口与抽象基类和仅仅类的观点将根据您正在开发的内容以及您正在开发的语言而大相径庭 . 接口通常仅与静态类型语言(如Java或C#)相关联,但是动态类型语言也可以有接口和抽象基类 . 例如,在Python中,一个Class(声明它是一个接口)和一个对象(它是一个Class的实例)之间的区别是明确的,并且被称为 provide 这个接口 . 在动态语言中,两个对象都是同一个类的实例,可以声明它们提供完全 different 接口 . 在Python中,这仅适用于对象属性,而方法是共享状态在一个类的所有对象之间 . 但是在Ruby中,对象可以有每个实例的方法,所以它有任何明确的方式来声明接口) .

    在动态语言中,通常隐式地假定对象的接口,或者通过内省对象并询问它提供了什么方法(Look Before You Leap),或者最好通过简单地尝试在对象上使用所需的接口并捕获异常(如果对象)没有提供该接口(更容易请求宽恕而不是权限) . 这可能导致“误报”,其中两个接口具有相同的方法名称但在语义上不同,但是权衡是您的代码更灵活,因为您不需要预先指定预先考虑所有可能的用途你的代码

  • 19

    要记住的另一个选择是使用“has-a”关系,又称“以”或“组合”的形式实现 . 有时这是一种更简洁,更灵活的结构化方法,而不是使用“is-a”继承 .

    从逻辑上讲,狗和猫都“拥有”宠物可能没有多大意义,但它避免了常见的多重继承陷阱:

    public class Pet
    {
        void Bathe();
        void Train(Trick t);
    }
    
    public class Dog
    {
        private Pet pet;
    
        public void Bathe() { pet.Bathe(); }
        public void Train(Trick t) { pet.Train(t); }
    }
    
    public class Cat
    {
        private Pet pet;
    
        public void Bathe() { pet.Bathe(); }
        public void Train(Trick t) { pet.Train(t); }
    }
    

    是的,这个例子表明,这样做有很多代码重复和缺乏优雅 . 但是人们也应该意识到,这有助于让狗和猫脱离宠物类(因为狗和猫无法接触宠物的私人成员),并且它为狗和猫留下了继承其他东西的空间 - - 可能是哺乳动物班 .

    如果不需要私人访问,并且您不需要使用通用Pet引用/指针来引用Dog和Cat,则组合是首选 . 接口为您提供了通用的引用功能,可以帮助减少代码的冗长,但是当它们组织不良时,它们也可能会混淆 . 当您需要私有成员访问权限时,继承非常有用,并且在使用它时,您承诺将您的Dog和Cat类高度耦合到您的Pet类,这需要付出高昂的代价 .

    在继承,组合和接口之间,没有一种方法总是正确的,并且有助于考虑如何协调使用所有三个选项 . 在这三者中,继承通常是应该最少使用的选项 .

  • 3

    通常,您应该支持接口而不是抽象类 . 使用抽象类的一个原因是,如果您在具体类之间有共同的实现 . 当然,你仍然应该声明一个接口(IPet)并有一个抽象类(PetBase)实现该接口 . 使用小的,不同的接口,你可以使用倍数来进一步提高灵活性 . 接口允许跨越边界的类型的最大灵活性和可移植性 . 跨越边界传递引用时,始终传递接口而不是具体类型 . 这允许接收端确定具体实现并提供最大的灵活性 . 当以TDD / BDD方式编程时,这是绝对正确的 .

    “四人帮”在他们的书中指出“因为继承将子类暴露给其父类实现的细节,所以通常会说'继承会破坏封装” . 我相信这是真的 .

  • 3

    接口

    • 定义2个模块之间的 Contract . 不能有任何实施 .

    • 大多数语言允许您实现多个接口

    • 修改界面是一个重大变化 . 所有实现都需要重新编译/修改 .

    • 所有成员都是公开的 . 实现必须实现所有成员 .

    • 接口有助于去耦 . 您可以使用模拟框架来模拟接口后面的任何内容

    • 接口通常表示一种行为

    • 接口实现彼此分离/隔离

    基类

    • 允许您添加一些 default 实现,您可以通过派生免费获得

    • 除C外,您只能从一个类派生 . 即使可以来自多个 class ,通常也是一个坏主意 .

    • 更改基类相对容易 . 派生不需要做任何特别的事情

    • 基类可以声明可以通过派生访问的受保护和公共函数

    • Abstract基类不能像接口那样容易被模拟

    • 基类通常表示类型层次结构(IS A)

    • 类派生可能依赖于某些基本行为(具有对父实现的复杂知识) . 如果你改变一个人的基础实现并打破其他人,事情就会变得混乱 .

  • 10

    Sourcehttp://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

    C#是一种很好的语言,在过去的14年里已经成熟和发展 . 这对我们的开发人员来说非常棒,因为成熟的语言为我们提供了大量的语言功能 .

    但是,有很多权力变得很重要 . 其中一些功能可能被滥用,或者有时很难理解为什么你会选择使用一个功能而不是另一个功能 . 多年来,我看到很多开发人员都在努力解决的一个功能是何时选择使用接口或选择使用抽象类 . 两者都有优点和缺点以及正确的时间和地点 . 但是我们如何决定???

    两者都提供了类型之间的共同功能的重用 . 最明显的区别是接口不提供其功能的实现,而抽象类允许您实现一些“基本”或“默认”行为,然后能够在必要时使用类派生类型“覆盖”此默认行为 .

    这一切都很好,并提供了很好的代码重用,并坚持软件开发的DRY(不要重复自己)原则 . 当你有“是一种”关系时,抽象类很有用 .

    例如:金毛猎犬“是一种”狗 . 狮子狗也是如此 . 它们都可以像所有狗一样吠叫 . 但是,您可能想要声明贵宾犬公园与“默认”狗皮大不相同 . 因此,您可以按如下方式实现:

    public abstract class Dog
    {
          public virtual void Bark()
          {
            Console.WriteLine("Base Class implementation of Bark");
          }
    }
    
    public class GoldenRetriever : Dog
    {
       // the Bark method is inherited from the Dog class
    }
    
    public class Poodle : Dog
    {
      // here we are overriding the base functionality of Bark with our new implementation
      // specific to the Poodle class
      public override void Bark()
      {
         Console.WriteLine("Poodle's implementation of Bark");
      }
    }
    
    // Add a list of dogs to a collection and call the bark method.
    
    void Main()
    {
        var poodle = new Poodle();
        var goldenRetriever = new GoldenRetriever();
    
        var dogs = new List<Dog>();
        dogs.Add(poodle);
        dogs.Add(goldenRetriever);
    
        foreach (var dog in dogs)
        {
           dog.Bark();
        }
    }
    
    // Output will be:
    // Poodle's implementation of Bark
    // Base Class implementation of Bark
    
    //
    

    正如您所看到的,这将是保持代码DRY并允许在任何类型可以仅依赖于默认Bark而不是特殊情况实现时调用基类实现的好方法 . 像GoldenRetriever,Boxer,Lab这样的类都可以免费继承“默认”(低音类)Bark,因为它们实现了Dog抽象类 .

    但我相信你已经知道了 .

    你在这里是因为你想了解为什么你可能想要在抽象类上选择一个接口,反之亦然 . 您可能想要在抽象类上选择接口的一个原因是您没有或想要阻止默认实现 . 这通常是因为实现接口的类型与“是”关系无关 . 实际上,除了每种类型“能够”或“有能力”做某事或有某事之外,它们根本不需要相关 .

    那到底是什么意思呢?嗯,例如:人类不是鸭子......鸭子不是人类 . 很明显 . 然而,鸭子和人类都有“游泳能力”(考虑到人类通过了他在一年级的游泳课:)) . 此外,由于鸭子不是人类,反之亦然,这不是“是一种”关系,而是“能够”关系,我们可以使用界面来说明:

    // Create ISwimable interface
    public interface ISwimable
    {
          public void Swim();
    }
    
    // Have Human implement ISwimable Interface
    public class Human : ISwimable
    
         public void Swim()
         {
            //Human's implementation of Swim
            Console.WriteLine("I'm a human swimming!");
         }
    
    // Have Duck implement ISwimable interface
    public class Duck: ISwimable
    {
         public void Swim()
         {
              // Duck's implementation of Swim
              Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
         }
    }
    
    //Now they can both be used in places where you just need an object that has the ability "to swim"
    
    public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
    {
         somethingThatCanSwim.Swim();
    }
    
    public void Main()
    {
          var human = new Human();
          var duck = new Duck();
    
          var listOfThingsThatCanSwim = new List<ISwimable>();
    
          listOfThingsThatCanSwim.Add(duck);
          listOfThingsThatCanSwim.Add(human);
    
          foreach (var something in listOfThingsThatCanSwim)
          {
               ShowHowYouSwim(something);
          }
    }
    
     // So at runtime the correct implementation of something.Swim() will be called
     // Output:
     // Quack! Quack! I'm a Duck swimming!
     // I'm a human swimming!
    

    使用上面代码之类的接口将允许您将对象传递给“能够”执行某些操作的方法 . 代码并不关心它是如何做到的......它只知道它可以在该对象上调用Swim方法,并且该对象将根据其类型知道在运行时采取的行为 .

    再次,这可以帮助您的代码保持DRY,这样您就不必编写多个调用该对象的方法来执行相同的核心功能(ShowHowHumanSwims(human),ShowHowDuckSwims(duck)等)

    在这里使用接口允许调用方法不必担心行为的实现类型或方式 . 它只知道给定接口,每个对象都必须实现Swim方法,因此可以安全地在自己的代码中调用它,并允许在自己的类中处理Swim方法的行为 .

    摘要:

    因此,我的主要经验法则是,当您想要为类层次结构实现“默认”功能时使用抽象类或/和您正在使用的类或类型共享“是一种”关系(例如,poodle“是一个“狗的类型 .

    另一方面,当你没有“是一种”关系时,使用一个界面,但是有一些类型可以分享“能力”来做某事或有某些东西(例如鸭子“不是”人类 . 但是,鸭子和人类分享“游泳的能力” .

    抽象类和接口之间需要注意的另一个区别是,类可以实现一个到多个接口,但类只能从一个抽象类(或任何类)继承 . 是的,你可以嵌套类并拥有一个继承层次结构(许多程序都应该这样做),但你不能在一个派生类定义中继承两个类(这个规则适用于C# . 在其他一些语言中你可以这样做,通常只是因为这些语言缺乏接口) .

    还要记住使用接口遵守接口隔离原则(ISP) . ISP声明不应该强迫任何客户端依赖它不使用的方法 . 因此,接口应该专注于特定任务,并且通常非常小(例如IDisposable,IComparable) .

    另一个提示是,如果您正在开发小巧,简洁的功能,请使用接口 . 如果要设计大型功能单元,请使用抽象类 .

    希望这能为某些人解决问题!

    此外,如果你能想到任何更好的例子或想要指出一些东西请在下面的评论中这样做!

  • 95

    现代风格是定义IPet和PetBase .

    该接口的优点是其他代码可以使用它而与其他可执行代码无任何关系 . 完全“干净” . 接口也可以混合使用 .

    但是基类对于简单实现和常用实用程序很有用 . 因此,提供一个抽象基类以节省时间和代码 .

  • 3

    基类的继承者应该具有“是一种”关系 . 接口表示“实现”关系 . 因此,只有在继承者维护关系时才使用基类 .

相关问题