这可能是一个通用的OOP问题。我想在接口和抽象类的使用基础上做一个通用的比较。

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


当前回答

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

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

“Template方法”的问题在于它几乎总是某种程度上是可重入的——“派生”类不仅知道它正在实现的基类的“抽象”方法,还知道基类的公共方法,即使大多数时候它不需要调用它们。

(过于简化的)例子:

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.
    }
}

这样做的问题在于,你将两个概念过度地耦合在了一起:

比较两个项目的一种方法(哪个项目应该放在前面) 排序项目的方法(即快速排序vs归并排序等)

在上面的代码中,从理论上讲,“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());
}

任何时候你有一个抽象类,你都可以这样做…即使基类和派生类之间存在自然的可重入关系,将它们显式化通常也是值得的。

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

其他回答

两者都是类定义的契约:

结论1:两种意图都是对象泛化

在定义抽象类时,它们也可以有默认实现。

结论2:区分存在于行为泛化设计中

在使用抽象类时,类只能从一个抽象类继承

结论3:抽象类在应用上存在局限性。它的意思是 行为概括的局限性。

最后结论-何时使用哪个:区分是在行为泛化层面

在类的行为设计中,如果功能在确定的类之间只是概念上的限制,换句话说,在确定的类之间是共享的,则使用抽象类。但如果功能比确定类更通用,或者我们可以/想要向其他类添加功能,则使用接口作为契约。

我写过一篇关于何时使用抽象类和何时使用接口的文章。除了“一个是a……”之外,他们之间还有很多不同之处。一个能做……”。对我来说,这些都是事先准备好的答案。我提到了使用它们中的任何一种的一些原因。希望能有所帮助。

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

简单的回答是:抽象类允许您创建子类可以实现或覆盖的功能。接口只允许您定义功能,而不能实现它。虽然一个类只能扩展一个抽象类,但它可以利用多个接口。

如果我们有一个对所有派生类都相同的实现,那么此时最好使用抽象类而不是接口。当我们有一个接口时,我们可以将我们的实现移动到任何实现接口的类。在抽象类中,它避免了代码重复,并共享所有派生类的实现。接口允许开发松散耦合的系统,这有助于更好的测试。

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

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

“Template方法”的问题在于它几乎总是某种程度上是可重入的——“派生”类不仅知道它正在实现的基类的“抽象”方法,还知道基类的公共方法,即使大多数时候它不需要调用它们。

(过于简化的)例子:

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.
    }
}

这样做的问题在于,你将两个概念过度地耦合在了一起:

比较两个项目的一种方法(哪个项目应该放在前面) 排序项目的方法(即快速排序vs归并排序等)

在上面的代码中,从理论上讲,“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());
}

任何时候你有一个抽象类,你都可以这样做…即使基类和派生类之间存在自然的可重入关系,将它们显式化通常也是值得的。

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