何时使用接口而不是抽象类,反之亦然?

时间:2009-01-26 08:47:39

标签: oop inheritance interface abstract-class

这可能是一个通用的OOP问题。我想根据它们的用法在接口和抽象类之间进行一般性比较。

什么时候想要使用界面?何时想要使用抽象类

23 个答案:

答案 0 :(得分:401)

抽象类可以具有共享状态或功能。接口只是提供状态或功能的承诺。一个好的抽象类将减少必须重写的代码量,因为它的功能或状态可以共享。接口没有要共享的已定义信息

答案 1 :(得分:387)

我写了一篇关于此的文章:

Abstract classes and interfaces

总结:

当我们谈论抽象类时,我们定义了一个对象类型的特征;指定 对象是什么

当我们谈论接口并定义我们承诺提供的功能时,我们正在谈论建立一个关于 对象可以做什么的合同。

答案 2 :(得分:79)

就个人而言,我几乎从不需要编写抽象类。

大多数时候我看到抽象类被错误地使用了,这是因为抽象类的作者正在使用“模板方法”模式。

“模板方法”的问题在于它几乎总是有些重入 - “派生”类不仅知道它正在实现的基类的“抽象”方法,而且还知道公共方法。基类,即使大多数时候它不需要调用它们。

(过度简化)示例:

abstract class QuickSorter
{
    public void Sort(object[] items)
    {
        // implementation code that somewhere along the way calls:
        bool less = compare(x,y);
        // ... more implementation code
    }
    abstract bool compare(object lhs, object rhs);
}

所以在这里,这个类的作者编写了一个通用算法,并打算让人们通过提供他们自己的“钩子”来“专门化”它 - 在这种情况下,是一个“比较”方法。

所以预期的用法是这样的:

class NameSorter : QuickSorter
{
    public bool compare(object lhs, object rhs)
    {
        // etc.
    }
}

问题在于你将两个概念过度耦合在一起:

  1. 比较两个项目(首先应该使用哪个项目)的方法
  2. 排序项目的方法(即快速排序与合并排序等)
  3. 在上面的代码中,理论上,“compare”方法的作者可以重新回调到超类“Sort”方法......即使在实践中他们也不会想要或需要这样做。

    您为这种不必要的耦合付出的代价是,很难更改超类,并且在大多数OO语言中,无法在运行时更改它。

    替代方法是使用“策略”设计模式:

    interface IComparator
    {
        bool compare(object lhs, object rhs);
    }
    
    class QuickSorter
    {
        private readonly IComparator comparator;
        public QuickSorter(IComparator comparator)
        {
            this.comparator = comparator;
        }
    
        public void Sort(object[] items)
        {
            // usual code but call comparator.Compare();
        }
    }
    
    class NameComparator : IComparator
    {
        bool compare(object lhs, object rhs)
        {
            // same code as before;
        }
    }
    

    现在注意:我们所有的都是接口,以及这些接口的具体实现。在实践中,您并不需要任何其他任何东西来进行高级OO设计。

    为了“隐藏”我们通过使用“QuickSort”类和“NameComparator”实现“名称排序”这一事实,我们仍然可以在某处编写工厂方法:

    ISorter CreateNameSorter()
    {
        return new QuickSorter(new NameComparator());
    }
    

    任何时间你有一个抽象类,你可以这样做......即使在基类和派生类之间存在自然的重入关系时,通常也要付出代价才能使它们明确。< / p>

    最后一个想法:我们上面所做的就是通过使用“QuickSort”函数和“NameComparison”函数“编写”“NameSorting”函数......在函数式编程语言中,这种编程风格变得均匀更自然,代码更少。

答案 3 :(得分:39)

好吧,我自己也只是“弄清楚” - 这里是外行人的说法(如果我错了,请随时纠正我) - 我知道这个话题太过分了,但有一天其他人可能会偶然发现它......

抽象类允许您创建蓝图,并允许您另外构造(实现)您希望其所有后代拥有的属性和方法。

另一方面,接口只允许您声明您希望具有给定名称的属性和/或方法存在于实现它的所有类中 - 但是没有指定您应该如何实现它。此外,一个类可以实现MANY接口,但只能扩展一个Abstract类。界面更像是一种高级建筑工具(如果你开始掌握设计模式,它会变得更加清晰) - 一个抽象在两个阵营都有一个脚,也可以执行一些肮脏的工作。

为什么要使用一个而不是另一个?前者允许对后代进行更多具体定义 - 后者允许更大的多态。最后一点对最终用户/编码员很重要,他们可以利用这些信息以各种组合/形状实现A.P. I(接口)以满足他们的需要。

我认为这对我来说是一个“灯泡”时刻 - 考虑一下与作者相关的界面,以及更多来自链接后期的任何编码人员,他们正在为项目添加实现,或扩展一个API。

答案 4 :(得分:37)

我的两分钱:

接口基本上定义了一个契约,任何实现类都必须遵守(实现接口成员)。它不包含任何代码。

另一方面,抽象类可以包含代码,并且可能有一些标记为抽象的方法,继承类必须实现这些方法。

我使用抽象类的罕见情况是,当我有一些默认功能时,继承类可能在覆盖一些特殊类继承的抽象基类时可能不会感兴趣。

示例(一个非常基本的!):考虑一个名为Customer的基类,它具有抽象方法,如CalculatePayment()CalculateRewardPoints()和一些非抽象方法,如GetName(),{{1 }}。

SavePaymentDetails()RegularCustomer等专门类将继承自GoldCustomer基类并实现自己的CustomerCalculatePayment()方法逻辑,但可重复使用CalculateRewardPoints()GetName()方法。

您可以向抽象类添加更多功能(非抽象方法),而不会影响使用旧版本的子类。而向接口添加方法会影响实现它的所有类,因为它们现在需要实现新添加的接口成员。

包含所有抽象成员的抽象类与接口类似。

答案 5 :(得分:36)

如果您将java视为OOP语言,

接口不提供方法实现 ”在Java 8启动时不再有效。现在java在默认方法的接口中提供了实现。

简单来说,我想使用

interface: 通过多个不相关的对象实现合同。它提供“ HAS A ”功能。

抽象类: 在多个相关对象中实现相同或不同的行为。它建立了“ IS A ”关系。

Oracle website提供了interfaceabstract类之间的主要区别。

如果符合以下情况,请考虑使用抽象类

  1. 您希望在几个密切相关的类之间共享代码。
  2. 您希望扩展抽象类的类具有许多常用方法或字段,或者需要除公共之外的访问修饰符(例如protected和private)。
  3. 您想要声明非静态或非最终字段。
  4. 在以下情况下考虑使用接口

    1. 您希望不相关的类会实现您的界面。例如,许多不相关的对象可以实现Serializable接口。
    2. 您希望指定特定数据类型的行为,但不关心谁实现其行为。
    3. 您希望利用类型的多重继承。
    4. 示例:

      抽象类( IS A 关系)

      Reader是一个抽象类。

      BufferedReaderReader

      FileReaderReader

      FileReaderBufferedReader用于共同目的:阅读数据,它们通过Reader类相关联。

      界面(有一个能力)

      Serializable是一个界面。

      假设您的应用程序中有两个类,它们正在实现Serializable接口

      Employee implements Serializable

      Game implements Serializable

      此处,您无法通过SerializableEmployee之间的Game界面建立任何关系,这些界面用于不同目的。两者都能够序列化国家,并且比较在那里结束。

      看一下这些帖子:

      How should I have explained the difference between an Interface and an Abstract class?

答案 6 :(得分:29)

如果你的想法清楚明白,那么什么时候做一件非常简单的事情。

抽象类可以派生,而接口可以实现。两者之间存在一些差异。派生Abstract类时,派生类和基类之间的关系是'是'关系。例如,Dog是Animal,Sheep是Animal,这意味着Derived类从基类继承了一些属性。

然而,对于接口的实现,关系是“可以”。例如,狗可以是间谍犬。狗可以是马戏团的狗。狗可以是赛狗。这意味着您实现某些方法来获取某些东西。

我希望我很清楚。

答案 7 :(得分:10)

我写了一篇关于何时使用抽象类以及何时使用接口的文章。除了“一个IS-A ......和一个CAN-DO ......”之外,它们之间还有很多不同之处。对我来说,这些都是罐头答案。我提到了使用其中任何一个的几个原因。希望它有所帮助。

http://codeofdoom.com/wordpress/2009/02/12/learn-this-when-to-use-an-abstract-class-and-an-interface/

答案 8 :(得分:10)

1.如果您正在创建为不相关的类提供通用功能的东西,请使用接口。

2.如果要为层次结构中密切相关的对象创建内容,请使用抽象类。

答案 9 :(得分:6)

我认为最简洁的方法如下:

共享属性=&gt;抽象类。
共享功能=&gt;接口

并且不那么简洁......

抽象类示例:

public abstract class BaseAnimal
{
    public int NumberOfLegs { get; set; }

    protected BaseAnimal(int numberOfLegs)
    {
        NumberOfLegs = numberOfLegs;
    }
}

public class Dog : BaseAnimal
{
    public Dog() : base(4) { }
}

public class Human : BaseAnimal 
{
    public Human() : base(2) { }
}

由于动物具有共享属性 - 在这种情况下为腿数 - 因此创建包含此共享属性的抽象类是有意义的。这也允许我们编写对该属性进行操作的公共代码。例如:

public static int CountAllLegs(List<BaseAnimal> animals)
{
    int legCount = 0;
    foreach (BaseAnimal animal in animals)
    {
        legCount += animal.NumberOfLegs;
    }
    return legCount;
}

界面示例:

public interface IMakeSound
{
    void MakeSound();
}

public class Car : IMakeSound
{
    public void MakeSound() => Console.WriteLine("Vroom!");
}

public class Vuvuzela : IMakeSound
{
    public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");        
}

请注意,Vuvuzelas和Cars完全不同,但它们共享功能:发出声音。因此,界面在这里是有意义的。此外,它允许程序员在一个公共接口下将声音组合在一起 - 在这种情况下IMakeSound。使用此设计,您可以编写以下代码:

List<IMakeSound> soundMakers = new List<ImakeSound>();
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Vuvuzela());

foreach (IMakeSound soundMaker in soundMakers)
{
    soundMaker.MakeSound();
}

你能说出会输出什么吗?

最后,你可以将两者结合起来。

合并示例:

public interface IMakeSound
{
    void MakeSound();
}

public abstract class BaseAnimal : IMakeSound
{
    public int NumberOfLegs { get; set; }

    protected BaseAnimal(int numberOfLegs)
    {
        NumberOfLegs = numberOfLegs;
    }

    public abstract void MakeSound();
}

public class Cat : BaseAnimal
{
    public Cat() : base(4) { }

    public override void MakeSound() => Console.WriteLine("Meow!");
}

public class Human : BaseAnimal 
{
    public Human() : base(2) { }

    public override void MakeSound() => Console.WriteLine("Hello, world!");
}

在这里,我们要求所有BaseAnimal发出声音,但我们还不知道它的实现。在这种情况下,我们可以抽象接口实现并将其实现委托给它的子类。

最后一点,请记住在抽象类示例中我们如何能够对不同对象的共享属性进行操作,并且在接口示例中我们能够调用不同对象的共享功能?在最后一个例子中,我们可以做到这两点。

答案 10 :(得分:4)

类可以只从一个基类继承,因此如果要使用抽象类为一组类提供多态性,它们必须都从该类继承。抽象类也可以提供已经实现的成员。因此,您可以使用抽象类确保一定数量的相同功能,但不能使用接口。

以下是一些建议,可帮助您决定是使用接口还是抽象类来为组件提供多态性。

  • 如果您预计要创建组件的多个版本,请创建一个抽象类。抽象类提供了一种简单易用的组件版本。通过更新基类,所有继承类都会随更改自动更新。另一方面,一旦以这种方式创建,接口就无法更改。如果需要新版本的接口,则必须创建一个全新的接口。   
  • 如果您创建的功能在各种不同的对象中都有用,请使用界面。抽象类应主要用于密切相关的对象,而接口最适合为不相关的类提供通用功能。     
  •     如果您正在设计小巧,简洁的功能,请使用接口。如果要设计大型功能单元,请使用抽象类。    
  • 如果要在组件的所有实现中提供通用的实现功能,请使用抽象类。抽象类允许您部分实现您的类,而接口不包含任何成员的实现。

复制自:
http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx

答案 11 :(得分:3)

什么时候比接口更喜欢抽象类?

  1. 如果一个人计划在程序/项目的整个生命周期中更新基类,那么最好让基类成为抽象类
  2. 如果人们试图为层次结构中密切相关的对象构建主干,则使用抽象类非常有益

什么时候比抽象类更喜欢接口?

  1. 如果不处理大规模的分层框架,接口将是一个不错的选择
  2. 因为抽象类不支持多重继承(钻石问题),所以接口可以节省一天时间

答案 12 :(得分:3)

如果这些陈述中的任何一个适用于您的情况,请考虑使用抽象类

  1. 您希望在几个密切相关的类之间共享代码。
  2. 您希望扩展抽象类的类具有许多常用方法或字段,或者需要除公共之外的访问修饰符(例如protected和private)。
  3. 您想要声明非静态或非最终字段。这使您可以定义可以访问和修改它们所属对象状态的方法。
  4. 如果这些陈述中的任何一个适用于您的情况,请考虑使用接口

    1. 您希望不相关的类会实现您的界面。例如,Comparable和Cloneable接口由许多不相关的类实现。
    2. 您希望指定特定数据类型的行为,但不关心谁实现其行为。
    3. 您希望利用多重继承。
    4. Source

答案 13 :(得分:2)

答案因语言而异。例如,在Java中,类可以实现(继承)多个接口,但只能从一个抽象类继承。因此接口为您提供更大的灵活性但在C ++中并非如此。

答案 14 :(得分:2)

对我来说,在许多情况下我会选择接口。但在某些情况下我更喜欢抽象类。

OO中的类通常指实现。当我想强制一些实现细节给孩子使用接口时,我使用抽象类。

当然,抽象类不仅可用于强制实现,还可用于在许多相关类之间共享某些特定细节。

答案 15 :(得分:1)

在java中,你可以从一个(抽象)类继承到“提供”功能,你可以实现许多接口来“确保”功能

答案 16 :(得分:1)

当您需要向一组(相关或不相关)对象添加额外功能时,接口比抽象类更好的一个有趣位置。如果你不能给它们一个基本抽象类(例如,它们是sealed或已经有父类),你可以给它们一个虚拟(空)接口,然后简单地为该接口编写扩展方法。

答案 17 :(得分:1)

纯粹在继承的基础上,你会使用一个抽象,你明确定义后代,抽象关系(即动物 - >猫)和/或需要继承虚拟或非公共属性,尤其是共享状态(Interfaces不支持)。

你应该尽可能地尝试使用组合(通过依赖注入)而不是继承,并注意作为契约的接口支持单元测试,关注点分离和(语言变化)多重继承,而不是Abstracts。

答案 18 :(得分:1)

如果要提供一些基本实现,请使用抽象类。

答案 19 :(得分:0)

抽象类可以有实现。

接口没有实现,它只是定义了一种契约。

还可能存在一些与语言相关的差异:例如,C#没有多重继承,但可以在类中实现多个接口。

答案 20 :(得分:0)

这可能是一个非常困难的号召......

我可以给出一个指针:一个对象可以实现许多接口,而一个对象只能继承一个基类(在现代的OO语言中,比如c#,我知道C ++有多重继承 - 但不是不赞同的吗?)

答案 21 :(得分:0)

简短的答案:抽象类使您可以创建子类可以实现或覆盖的功能。接口仅允许您定义功能,而不能实现它。尽管一个类只能扩展一个抽象类,但它可以利用多个接口。

答案 22 :(得分:-1)

基本的拇指规则是:对于&#34;名词&#34;使用Abstract 类和&#34; Verbs&#34;使用界面

例如:car是一个抽象类,drive,我们可以使它成为一个接口。