Oop 非抽象类的非空方法和抽象方法的抽象方法的好处?

Oop 非抽象类的非空方法和抽象方法的抽象方法的好处?,oop,abstract,Oop,Abstract,我不明白为什么我们使用抽象方法(抽象类),而我们可以使用非抽象类的空方法,然后重写它。听起来好吗?我试图澄清这个问题 我举两个例子 public abstract class MyClass {public abstract void foo();} public MyChildClass extends MyClass {public void foo() {//..TODO}} public class MyClass {public void foo(){//empty}} public

我不明白为什么我们使用抽象方法(抽象类),而我们可以使用非抽象类的空方法,然后重写它。听起来好吗?我试图澄清这个问题

我举两个例子

public abstract class MyClass {public abstract void foo();}
public MyChildClass extends MyClass {public void foo() {//..TODO}}

public class MyClass {public void foo(){//empty}}
public class MyChildClass extends MyClass {public void foo() {//..TODO}}

哪一个更好?

我首先要说的是,您应该尝试使用接口而不是抽象类。抽象类将子类耦合到超类的实现。在Java这样的语言中,子类可以重写任何方法,即使超类不打算这样做,而且大多数人并不总是用“不要重写”来限定他们的方法

在最低级别,抽象方法在编译时为您提供了两种具体的保护:

  • 它们强制您重写子类中的方法
  • 他们不允许创建抽象类
在列出抽象方法的用例之前,我只想说“公共功能”不是抽象基类的好理由。如果您需要公共功能,只需创建一个具有公共方法的类,并让各个类根据自己的需要调用这些函数

那么什么时候使用抽象类呢?以下是一些例子:

模板法 在模板方法模式中,您拥有所有的功能,但只有一个内部方面是多态的,因此您拥有覆盖该特定方面的子类

例如,如果您正在实现一个缓存,但缓存无效策略是多态的,那么您可能有一个抽象的
invalidate()
方法,该方法由其他方法在内部调用,但实现
invalidate()
取决于子类

如果存在首选的默认缓存无效策略,则
invalidate()
可以实现该默认策略。但是,如果默认值在某些情况下是彻底破坏性的,那么它不应该是默认值——它应该是抽象的,并且创建缓存的代码应该被强制显式地选择无效策略

这也可以通过向构造函数(策略模式)传递
invalidater
类来实现,但是如果无效逻辑需要调用缓存的方法,那么最好保护这些方法,并从子类(即模板方法模式)调用它们

其他方法的默认实现 在接口不能有默认方法的语言中(例如Java 7),您可以使用抽象类来模拟它。所有接口方法都是抽象的,但默认方法是常规公共方法

通用接口和功能 这只是模板方法模式的一个更通用的版本。区别在于多态方法是API的一部分

如果您的公共功能与您想要公开的功能有很多重叠,并且您不想要堆积如山的样板代码,那么您可以使用一个抽象类。例如:

interface File {
    abstract Buffer read(int size);
    abstract void write(Buffer buf);
    abstract long getSize();
    abstract void setSize();
    // ... get/set creation time, get/set modification time, get
    // file type etc.
    abstract long getOwner();
    abstract void setOwner(long owner);
}

abstract class AbstractFile extends File {
    DataMap dataMap;
    MetadataMap metaMap;
    protected getDiskMap() { return dataMap; }
    protected getMetaMap() { return metaMap; }
    public Buffer read(int size) { /* loop here */ }
    public void write(Buffer buf) { /* loop here */ }
    public long getSize() { /* logic */ }
    public void setSize() { /* logic */ }
    // ... implementation of get/set creation time, get/set modification
    // time, get file type etc.
}

abstract class HardDriveFile extends AbstractFile {
    OwnershipMap ownerMap;
    abstract long getOwner() { /* logic */ }
    abstract void setOwner(long owner) { /* logic */ }
}

abstract class ThumbDriveFile extends AbstractFile {
    // thumb drives have no ownership
    abstract long getOwner() { return 0; }
    abstract void setOwner(long owner) { /* no-op */ }
}

abstract class SomeOtherfile extends AbstractFile {
    ...
}
如果我们减少中间人,让
硬驱动文件
拇指驱动文件
(可能还有其他类型的文件)实现
文件
,并详细说明所有常用方法,每个方法调用某个常用类的方法,我们将得到堆积如山的样板文件。因此,我们从一个抽象基类继承,该基类具有我们想要专门化的抽象方法(例如,基于所有权映射的存在)


最简单的做法是将
File
AbstractFile
组合成一个类,在这个类中可以获得抽象方法
getOwner()
setOwner()
,但最好将抽象类隐藏在实际接口后面,为了防止API使用者和抽象类之间的耦合。

对于初学者来说,抽象选项使编译器强制您重写它,这在其他人(或您自己)可能忘记放置正确的重写时非常有用。非抽象选项更适合于“可选”方法,如通知。