为什么要使用抽象类而不是接口?

8
例如,房地产开发商正在建造一栋有多个单元的公寓。除了卧室外,所有单元房间的设计都是相同的。卧室的设计留给未来的业主决定,即不同单元的卧室可以有不同的设计。
我可以通过下面的一个抽象类实现这一点:
public abstract class Flat
{
    //some properties

    public void livingRoom(){
       //some code
    }

    public void kitchen(){
       //some code
    }

    public abstract void bedRoom();

    }
}

一个实现类应该像下面这样:

public class Flat101 extends Flat
{
    public void bedRoom() {
        System.out.println("This flat has a customized bedroom");
   }        

}

或者我可以使用接口(interface)而不是抽象类(abstract)来实现相同的目的,如下所示:

class Flat
{
  public void livingRoom(){ 
       System.out.println("This flat has a living room");
  }

  public void kitchen(){
     System.out.println("This flat has a kitchen");
  } 
}

interface BedRoomInterface
{
  public abstract void bedRoom();
}

public class Flat101 extends Flat implements BedRoomInterface
{
   public void bedRoom() {
    System.out.println("This flat has a customized bedroom");
   }
}

现在问题是:为什么要选择使用接口(或)为什么要选择使用抽象类?

3
你自己回答了这个问题:接口只定义方法名,如果你还需要实现特定的方法,你就要使用抽象类。 - Gabi Purcaru
6个回答

6
根据您的意图或使用情况而定。但总的来说,建议您优先选择接口而不是抽象类(Bloch's Effective Java中的第18项)。抽象类更加脆弱,因为有人可能会修改抽象类,从而改变其他继承它的类的行为(这是一个普遍的说法)。
使用接口更加灵活,因为如果您有BedroomInterface和LivingRoomInterface,那么您可以拥有实现两个接口的FlatInterface,然后Flat101实现类实现FlatInterface(而不是扩展Flat再实现接口)。这样更加清晰,以后您还可以有ExecutiveFlatInterface,它不仅有卧室和客厅,还有客房,然后Flat102可以从中实现。
选项2是让Flat101继承Flat,然后Flat实现BedroomInterface和LivingRoomInterface。这真的取决于您想做什么以及可能需要哪些方法。

我完全同意,但现在由于实现可以使用默认操作在接口中指定,那么什么情况下使用抽象类比接口更好呢?是否当您想要限制子类不扩展其他类时(出于任何原因)可以这样做? - vinkomlacic
@user8810865,抽象类支持包私有和受保护的字段、构造函数和方法,继承类可以使用这些内容。这不能通过接口实现,因为默认操作必须是公共的。 - Kenston Choi

4
如果你正在设计一个将被广泛使用的API,你需要同时使用接口和抽象类:接口用于表达实现类需满足的契约,而抽象类则部分实现了该接口,从而允许代码重用。例如,考虑Java中的List:Collections框架中的方法(例如Collections.sort())是基于List接口编写的,该接口由抽象类AbstractList部分实现,AbstractList又被扩展为具体实现LinkedList和ArrayList。 LinkedList和ArrayList重用了AbstractList的代码,但这并不妨碍其他人编写自己完全独立的List实现,然后使用Collections.sort()进行排序。
尽管如此,在很多情况下,这种方法可能会过度复杂。如果您正在构建的类型层次结构仅在相对较小的范围内使用,则通常只需使用抽象类即可。如果您稍后决定需要接口,那么将其更改为接口通常是一项简单的重构任务。
抽象类确实有一些优点:
- 它们允许您指定带有包/保护修饰符的抽象方法 - 它们促进代码重用 - 通过在超类上使用抽象方法和最终方法,它们允许您限制子类化类的方式,这在各种情况下都非常有用(另请参见:模板模式) - 引用类的代码通常更容易在IDE中跟踪(单击抽象类类型参数上的“打开声明”通常比单击接口类型参数上的有用)

1
如果您有一个类提供了派生类所需的某些功能,但每个派生类还需要不同的其他功能实现,则抽象类提供了一种定义通用实现的方法,同时将派生类所需的特定行为留给每个派生类来具体实现。

0

我认为这是一种概括性的意思;如果一个类的属性和行为在给定的包或模块中是共同的,那么抽象类就非常有用。一个很好的例子是鼓式制动器;因为所有的鼓式制动器都是以相同的方式保持制动器在轮毂内,所以这种行为可以被继承到使用鼓式制动器的所有汽车类中。

对于接口来说,它更像是规范或契约,强制你实现其规范。让我们以建筑模型为例,它具有所有的规范,如门、窗户、电梯等。但是当你将模型实现到实际建筑中时,你需要保留窗户,但内部行为由(例如窗户可以是简单的窗户或滑动窗户,颜色和材料等)决定。

希望这能帮到你!


0

我认为当我们需要为多个类实现一些常见功能和抽象功能时,应该使用抽象类。如果我们看一下 Flat 的例子,我们有一些共同的设计和一些自定义的设计,在这种情况下,最好使用抽象类而不是再次使用接口来实现自定义函数,并且使用抽象作为派生类不会像普通派生类那样创建额外的实例。


-3

你不能继承多个类,但可以实现多个接口

如果你需要经常更改你的设计,那么抽象类更好,因为在抽象类中发生任何变化时,子类不需要强制实现。但是,如果接口发生任何变化,则必须在实现类中实现。


1
-1 这段文字摘自以下文章:http://www.javaworld.com/javaworld/javaqa/2001-04/03-qa-0420-abstract.html - maba
@maba,你发现得好。我们需要引用我们的来源! - Kenston Choi
感谢您编辑答案而非保留剽窃版本。我认为在您说“经常更改设计”时需要更具体地说明。同时,“子类中不需要强制实现”的说法也可能存在危险,因为有些类可能需要重写它而不是采用默认实现,这就是为什么我们有“抽象”关键字来强制实现的原因。 - Kenston Choi
任何更改都意味着添加抽象方法。 - Mohammod Hossain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接