Java抽象:抽象类委托抽象与具体类使用通用抽象

时间:2019-09-01 11:58:01

标签: java oop design-patterns

说我正在实现一个简单的界面,例如Animal。广义上讲,有两种方法可以做到这一点:

  1. 在抽象类中实现接口的方法,并为所需的任何实现特定逻辑创建抽象方法。
  2. 在具体的类中实现接口方法,并在受保护的访问权下在抽象类中移动通用逻辑。

我想了解是否-

  • 就设计而言,一种方式比另一种方式有任何客观优势。
  • 建议在一个项目中坚持一种方式(代码可维护性/可读性)。
public interface Animal {
    void makeSound();
}

public abstract class BaseAnimal implements Animal {
    @Override
    public void makeSound() {
        //do something which is common for all implementaions
        doSomeImplementaionSpecificStuff();
    }

    public abstract void doSomeImplementaionSpecificStuff();
}

public class Dog extends BaseAnimal implements Animal {
    public void doSomeImplementationSpecificStuff(){
       //do something specific to a dog
    }
}
public abstract class BaseAnimal implements Animal {
    public void doCommonStuff() {
        //any common logic that can be shared between concrete  implementation goes here
    }
}

public class Dog extends BaseAnimal implements Animal {
    @Override
    public void makeSound() {
       doCommonStuff();
       //do something specific to a dog
    }
}

4 个答案:

答案 0 :(得分:0)

主要区别在于,如果您不想实现父/抽象类的所有方法,则可以将逻辑放在父类中,并创建一种默认方法。  无论什么,界面都会强制您实现所有方法。

public abstract class BaseAnimal {
    public void doCommonStuff() {
        System.out.println("doComon1");
    }
    public void doCommonStuff2() {
        System.out.println("doComon1");
    }

    public static void main(String[] args) {
        SuperDog superDog = new SuperDog();
        superDog.doCommonStuff();
    }
}

两个实现可能是:

public class cat extends BaseAnimal {
    @Override
    public void doCommonStuff() {
        //specific logic
    }
    @Override
    public void doCommonStuff2() {
        //specific logic
    }
}

public class Dog extends BaseAnimal {
    @Override
    public void doCommonStuff() {
        //specific logic
    }
    // don't override the doCommonStuff2() method so you have the parent implementation
}

并且,还可以使用抽象类进行混合:

public class SuperDog extends BaseAnimal {
    @Override
    public void doCommonStuff(){
        super.doCommonStuff();
        System.out.println("do specific after common stuff");
    }
}

最后一个,可以调用父逻辑,也可以添加特定的逻辑。使用界面,您将无法执行任何操作。

答案 1 :(得分:0)

如果存在默认的通用方法来实现大多数子类将使用的接口,则只需在抽象类中以这种方式实现重写方法。任何需要这种方式的子类都可以保持原样。任何需要完全不同方式的子类都可以完全重写并重新实现它。任何想要使用默认实现但又向其中添加一些东西的子类都可以覆盖,然后调用super.makeSound()作为实现的一部分。

不要忘记准确记录默认实现是什么。

答案 2 :(得分:0)

这两种方式并不总是可以互换的。
您的第一个示例为子类设置了一个约束,该约束要求实现属于makeSound()方法一部分的特定方法。
使用该方法可以将父类之一的子类的实现紧密地结合在一起。
此外,子类可能仍然是makeSound()的子类,因为它不是final
所以我只会在非常特定的情况下使用这种方式:

  • 定义子类必须定义的工厂方法,因为父类依赖该方法(抽象工厂)
  • 定义通用算法,并让子类定义该算法的某些特定部分(模板方法)。

一般情况下,您想使用第二个示例的代码,但也要使用BaseAnimalAnimal

public abstract class BaseAnimal implements Animal {
    public void doCommonStuff() {
        //any common logic that can be shared between concrete  implementation goes here
    }
}

public class Dog extends BaseAnimal implements Animal {
    @Override
    public void makeSound() {
       doCommonStuff();
       //do something specific to a dog
    }
}

请注意,在Java 8中,默认接口使您无法定义仅定义通用方法的抽象类:

public interface Animal {
    void makeSound();
    default void doCommonStuff() {
            //any common logic that can be shared between concrete  implementation goes here    
}

还要注意,在抽象类的API中公开doCommonStuff()不一定是好的。客户应该可以打电话吗? 如果这是实现细节,则可以将其提取到支持类(AnimalDelegate)中,并通过将AnimalDelegate组合到Animal子类中,而倾向于继承而不是继承。

答案 3 :(得分:0)

考虑您的抽象类提供给子类实现者的接口。

记录子类可以使用或覆盖的方法。确保文档告诉子类实现者正确使用您的基类需要知道什么。

现在:

  • 选项1:在几乎所有情况下,这都不是一个好主意,因为您的抽象类假设子类将以特殊方式满足Animal接口,并且只需更改您希望它们更改的位。最终,这样的假设最终是错误的。如果您记录该doSomeImplementaionSpecificStuff,通常会发现有关该方法何时被调用以及预期将执行的操作的所有子句都会令人尴尬。就是说,有时 这样的抽象类对于实现者可能使用的 基本类而不是可能有用。 期望实现者使用的基类。
  • 选项2:可以,只要doCommonStuff是子类实现者可以选择使用或不使用的“常用东西”,而不是子类实现者必须调用,当然子类实现者不必在特定时间调用。如果您可以避免在该抽象类中使用状态,那么您可能希望将这些常用内容移至实用程序库中。

在所有情况下,要记住的事情是:使用抽象库时,尝试实现Animal接口的是子类实现程序。他可以就如何精确地做出所有决定。您的基类是一种以特定方式提供帮助的提议,子类实现者可以采取或离开该提议。

相关问题