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

When to use an interface instead of an abstract class and vice versa?

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

什么时候使用接口,什么时候使用抽象类?


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


我写了一篇关于这个的文章:

抽象类和接口

总结:

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

当我们讨论一个接口并定义我们承诺提供的功能时,我们讨论的是建立一个关于对象可以做什么的契约。


就我个人而言,我几乎没有必要写抽象类。

大多数时候我看到抽象类被(mis)使用,这是因为抽象类的作者使用的是"模板方法"模式。

"template method"的问题是它几乎总是有些可重入性——"derived"类不仅知道它正在实现的基类的"abstract"方法,而且知道基类的公共方法,即使大多数时候它不需要调用它们。

(过于简化)示例:

1
2
3
4
5
6
7
8
9
10
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);
}

因此,在这里,这个类的作者编写了一个通用算法,打算让人们通过提供自己的"钩子"来"专门化"它——在本例中,是一个"比较"方法。

所以预期用途是这样的:

1
2
3
4
5
6
7
class NameSorter : QuickSorter
{
    public bool compare(object lhs, object rhs)
    {
        // etc.
    }
}

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

  • 比较两个项目的一种方法(应该先看哪个项目)
  • 排序项目的方法(即快速排序与合并排序等)
  • 在上面的代码中,理论上,"比较"方法的作者可以重新进入调用超类"排序"方法…即使在实践中,他们也不想或不需要这样做。

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

    另一种方法是使用"战略"设计模式,而不是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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"实现了"名称排序"这一事实,我们仍然可以在某处编写工厂方法:

    1
    2
    3
    4
    ISorter CreateNameSorter()
    {
        return new QuickSorter(new NameComparator());
    }

    只要你有一个抽象类,你就可以这样做…即使在基础类和派生类之间有一种自然的可重入关系,也通常需要将它们显式化。

    最后一个想法是:我们上面所做的就是使用"quicksort"函数和"namecomparison"函数"编写"一个"namesorting"函数…在函数式编程语言中,这种编程风格变得更加自然,代码更少。


    好吧,我自己"摸索"过这个话题——这是外行的说法(如果我错了,请随意纠正我)——我知道这个话题很难理解,但有一天可能会有人偶然发现……

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

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

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

    我认为这对我来说是一个"灯泡"的时刻——想想接口,不要太在意作者的困惑,而要更多地去考虑那些稍后将要加入项目实现或扩展API的任何编码人员的接口。


    我的两分钱:

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

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

    我使用抽象类的罕见情况是,当我有一些默认功能时,继承类可能对覆盖某些特定类继承的(例如抽象基类)不感兴趣。

    示例(非常简单的一个!):考虑一个名为CUSTOMER的基类,它具有抽象方法(如CalculatePayment()CalculateRewardPoints())和一些非抽象方法(如GetName()SavePaymentDetails())。

    RegularCustomerGoldCustomer这样的专门类将从Customer基类继承并实现它们自己的CalculatePayment()CalculateRewardPoints()方法逻辑,但重新使用GetName()SavePaymentDetails()方法。

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

    具有所有抽象成员的抽象类将类似于接口。


    如果您将Java视为面向对象编程语言,

    "接口不提供方法实现"在Java 8启动时不再有效。现在Java为缺省方法提供了接口的实现。

    简单来说,我想用

    接口:通过多个无关对象实现一个契约。它提供"有"能力。

    抽象类:在多个相关对象之间实现相同或不同的行为。它建立了"是"的关系。

    Oracle网站提供了interfaceabstract类之间的关键区别。

    如果出现以下情况,请考虑使用抽象类:

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

  • 您期望不相关的类实现您的接口。例如,许多无关对象可以实现Serializable接口。
  • 您希望指定特定数据类型的行为,但不关心谁实现了它的行为。
  • 您希望利用类型的多重继承。
  • 例子:

    抽象类(是关系)

    reader是一个抽象类。

    BufferedReader是一个Reader

    文件阅读器是一个Reader

    FileReaderBufferedReader用于读取数据,它们通过Reader类进行关联。

    接口(有能力)

    可序列化是一个接口。

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

    Employee implements Serializable

    Game implements Serializable

    在这里,你不能通过EmployeeGame之间的Serializable接口建立任何关系,这是为了不同的目的。两者都能够序列化状态,而比较就到此为止。

    看看这些帖子:

    我应该如何解释接口和抽象类之间的区别?


    什么时候做什么是一件非常简单的事情,如果你头脑中有清晰的概念。

    抽象类可以派生,而接口可以实现。这两者之间有些不同。当派生抽象类时,派生类和基类之间的关系为"is a"关系。例如,狗是动物,绵羊是动物,这意味着派生类从基类继承了一些属性。

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

    我希望我明白。


    我写了一篇关于何时使用抽象类以及何时使用接口的文章。除了"一是一"之外,他们之间还有很多不同之处。还有一个可以做的…。对我来说,这些都是密封的答案。我提到了一些使用它们的原因。希望它有帮助。

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


    1.如果要创建为不相关类提供公共功能的内容,请使用接口。

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


    我认为最简洁的表达方式是:

    共享属性=>抽象类。共享功能=>界面。

    简而言之…

    抽象类示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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) { }
    }

    因为动物有一个共享属性(在本例中是腿的数量),所以创建一个包含这个共享属性的抽象类是有意义的。这还允许我们编写对该属性进行操作的公共代码。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static int CountAllLegs(List<BaseAnimal> animals)
    {
        int legCount = 0;
        foreach (BaseAnimal animal in animals)
        {
            legCount += animal.NumberOfLegs;
        }
        return legCount;
    }

    接口示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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!");        
    }

    请注意,呜呜祖拉和汽车是完全不同的,但它们有共同的功能:发出声音。因此,界面在这里是有意义的。此外,它还允许程序员在一个公共接口(本例中为IMakeSound)下将发出声音的事物分组。通过这种设计,您可以编写以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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();
    }

    你能告诉我会输出什么吗?

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

    组合示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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!");
    }

    在这里,我们要求所有的EDOCX1[1]都要完善,但我们还不知道它的实现。在这种情况下,我们可以抽象接口实现并将其实现委托给它的子类。

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


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

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

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

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


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

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

  • 您期望不相关的类实现您的接口。例如,可比较和可克隆的接口由许多不相关的类实现。
  • 您希望指定特定数据类型的行为,但不关心谁实现了它的行为。
  • 你想利用多重继承。
  • 来源


    什么时候更喜欢抽象类而不是接口?

  • 如果计划在程序/项目的整个生命周期中更新一个基类,最好允许该基类是一个抽象类。
  • 如果试图为层次结构中密切相关的对象构建主干,那么使用抽象类是非常有益的。
  • 什么时候比抽象类更喜欢接口?

  • 如果不处理大规模的层次结构类型的框架,接口将是一个很好的选择。
  • 因为抽象类不支持多重继承(菱形问题),接口可以节省时间

  • 对于我来说,在很多情况下我都会使用接口。但在某些情况下,我更喜欢抽象类。

    OO中的类是指实现。当我想强制一些实现细节到子类时,我使用抽象类,否则我使用接口。

    当然,抽象类不仅在强制实现中有用,而且在许多相关类之间共享一些特定的细节。


    答案因语言而异。例如,在Java中,类可以实现(继承)多个接口,但只能从一个抽象类继承。所以界面给你更多的灵活性。但这在C++中是不正确的。


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


    纯粹基于继承,您将使用一个抽象,其中明确定义了后代、抽象关系(即动物->猫)和/或需要继承虚拟或非公共属性,特别是共享状态(接口不支持)。

    您应该在可以的地方尝试并支持组合(通过依赖注入)而不是继承,并且注意,作为契约的接口以抽象的方式支持单元测试、关注点分离和(语言变化的)多重继承。


    在爪哇中,您可以从一个抽象类继承到"提供"功能,并且可以实现许多接口来确保"功能"。


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


    抽象类可以有实现。

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

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


    这可能是一个很难打的电话…

    我可以给出一个指针:一个对象可以实现许多接口,而对象只能继承一个基类(在现代的OO语言中,例如C语言,我知道C++具有多重继承性,但这不是皱眉吗?)


    基本的经验法则是:对于"名词",使用抽象类;对于"动词",使用接口。

    例如:car是一个抽象类,drive是一个接口。