ocomfd
ocomfd

Reputation: 4020

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

Consider there is a simple use case, a parent class with child classes which has common properties, eg: class Animal which has a name:

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

    abstract void printInfo();
}

and subclass :

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");
    }
}

according to Prefer composition over inheritance? and https://softwareengineering.stackexchange.com/questions/162643/why-is-clean-code-suggesting-avoiding-protected-variables, inheritance and protected variables should be avoid, so I modified Animal into an interface:

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

but the nightmare comes when moving the class property:

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");
    }
}

which the following code:

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

needs to copy and paste into each class. Furthermore, if there is one more property to add, eg:weight, I need to update Animal and each subclass manually.

My question is, is it violating DRY principle? if so, is there any method to refactor the original code so that it avoids inheritance and protected variables and also obeys DRY principle at the same time, so that I don't need to copy and paste codes about common properties into each subclass?

(or is the original already fine?)

Upvotes: 2

Views: 1956

Answers (3)

user6874700
user6874700

Reputation:

As variant to avoid copy-paste of 'name' variable in Cat/Dog classes you can use separate class for name handling. Example:

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.");
    }
}

Upvotes: 2

davidxxx
davidxxx

Reputation: 131486

Prefer composition over inheritance

In your second way, you don't use composition.
You implement all abstract methods in the subclasses. Which is different.
Here you fall on a duplication problem as you don't have a abstract skeleton class as base class for all concrete classes.
It is really a distinct matter.

In fact "Prefer composition over inheritance" is right only for classes not designed for inheritance.
Your abstract class is designed for inheritance : it is abstract and has an abstract method.

Your first way using the inheritance makes sense. So in this case, good practice is using inheritance and it allows also to respect the DRY principle.

For classes not designed for inheritance, composition should be favored and in this case, you have to wrap/compose the class in the composer class.

Upvotes: 0

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726929

inheritance and protected variables should be avoided

Inheritance is fine, as long as you are not forcing users into using it when all they need an interface. Java's List<T> and AbstractList<T> provide a good example: if you need to use parts of shared implementation, inherit the abstract class; if you don't, implement the interface.

protected String name field could be made private, too, eliminating the use of protected variables.

Here is how the approach would work for your class hierarchy:

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");
    }
}

Upvotes: 5

Related Questions