这里如何同时遵守"composition over inheritance"和DRY原则呢?

How to obey "composition over inheritance" and DRY principle at the same time here?

考虑一个简单的用例,父 class 和子 class 具有共同属性,例如:class 动物,其名称为:

public class abstract Animal{
    protected String name;
    public void setName(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }

    abstract void printInfo();
}

和子class :

public class Cat extends Animal{
    @Override
    public void printInfo(){
        System.out.println("I'm a cat");
    }
}

public class Dog extends Animal{
    @Override
    public void printInfo(){
        System.out.println("I'm a dog");
    }
}

根据Prefer composition over inheritance? and https://softwareengineering.stackexchange.com/questions/162643/why-is-clean-code-suggesting-avoiding-protected-variables,应该避免继承和保护变量,所以我将Animal修改为一个接口:

public interface Animal{
    void setName(String name);
    String getName();
    void printInfo();
}

但是当移动 class 属性:

时噩梦就来了
public class Cat implements Animal{
    private String name;
    @Override
    public void setName(String name){
        this.name=name;
    }
    @Override
    public String getName(){
        return name;
    }
    @Override
    public void printInfo(){
        System.out.println("I'm a cat");
    }
}

public class Dog implements Animal{
    private String name;
    @Override
    public void setName(String name){
        this.name=name;
    }
    @Override
    public String getName(){
        return name;
    }
    @Override
    public void printInfo(){
        System.out.println("I'm a dog");
    }
}

其中以下代码:

private String name;
@Override
public void setName(String name){
    this.name=name;
}
@Override
public String getName(){
    return name;
}

需要复制并粘贴到每个 class。此外,如果还有一个 属性 要添加 eg:weight,我需要手动更新 Animal 和每个子 class。

我的问题是,这是否违反了DRY原则?如果是这样,有什么方法可以重构原始代码,使其避免继承和受保护的变量,同时也遵守 DRY 原则,这样我就不需要将有关公共属性的代码复制并粘贴到每个 subclass?

(或者原来的已经好了?)

inheritance and protected variables should be avoided

继承很好,只要您不强迫用户在他们需要接口时使用它。 Java的List<T>AbstractList<T>提供了一个很好的例子:如果你需要使用共享实现的部分,继承抽象class;如果不这样做,请实现接口。

protected String name 字段也可以private,消除对受保护变量的使用。

以下是该方法如何适用于您的 class 层次结构:

public interface Animal {
    void setName(String name);
    String getName();
    void printInfo();
}

public abstract class AbstractAnimal implements Animal {
    private String name;
    public void setName(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
    abstract void printInfo();
}

public class Cat extends AbstractAnimal {
    @Override
    public void printInfo(){
        System.out.println("I'm a cat");
    }
}

public class Dog extends AbstractAnimal {
    @Override
    public void printInfo(){
        System.out.println("I'm a dog");
    }
}

Prefer composition over inheritance

在你的第二种方式中,你没有使用组合。
您在 subclasses 中实现所有抽象方法。哪个不一样。
在这里,您遇到了重复问题,因为您没有抽象骨架 class 作为所有具体 classes.class 的基础 class。
真是另当别论。

事实上 "Prefer composition over inheritance" 仅适用于 class 不是为继承而设计的。
您的抽象 class 是为继承而设计的:它是抽象的并且有一个抽象方法。

您使用继承的第一种方式很有意义。 所以在这种情况下,好的做法是使用继承,它也允许尊重 DRY 原则。

对于 class 不是为继承而设计的,组合应该受到青睐,在这种情况下,您必须 wrap/compose 作曲家 class 中的 class。

作为避免在 Cat/Dog class 中复制粘贴 'name' 变量的变体,您可以使用单独的 class 进行名称处理。示例:

public interface Animal {
    void setName(String name);
    String getName();
    void printInfo();
}

public class DefaultNameHolder {
    private String name;
    public void setName(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
}

// Same for Dog class
public class Cat implements Animal {
    private DefaultNameHolder nameHolder = new DefaultNameHolder();

    @Override
    public void setName(String name) {
        // Delegation
        nameHolder.setName(name);
    }

    @Override
    public String getName() {
        // Delegation
        return nameHolder.getName();
    }

    @Override
    public void printInfo(){
        System.out.println("I'm a cat");
    }
}

public class Developer implements Animal {
    private String firstName;
    private String lastName;

    @Override
    public void setName(String name) {
        // Some special logic for setName() 
        this.firstName = name.split(" ")[0];
        this.lastName = name.split(" ")[1];
    }

    @Override
    public String getName() {
        // Some special logic for getName()
        return "My first name is " + firstName + ", my last name is " + lastName;
    }

    @Override
    public void printInfo(){
        System.out.println("Will code for food.");
    }
}