Не получается вызвать метод так как в суперклассе этого класса переменная реализации интерфейса = null

Изучаю паттерны и у меня есть родительский класс Duck и его подклассы, есть метод performShowLevelOfAggression() и переменная реализации интерфейса Aggressable она определяется в подклассе и потом в performShowLevelOfAggression() выполняется её метод. Я вызываю метод performShowLevelOfAggression() подкласса MalardDuck, но метод не вызывается и пишет ошибку:

Cannot invoke "org.example.Aggressable.LevelOfAggression()" because "this.aggressable" is null at org.example.Duck.performShowLevelOfAggression(Duck.java:15) at org.example.Main.main(Main.java:14)

хотя делаю всё по книге

Код:

public abstract class Duck {
    protected Aggressable aggressable;
    public Duck(){}
    protected void performShowLevelOfAggression()
    {
        aggressable.LevelOfAggression();
    }
public class MalardDuck extends Duck {
    public Aggressable aggressable =  new ActiveAggression();
    public MalardDuck(){}
}
public interface Aggressable {
    public void LevelOfAggression();
}
public class ActiveAggression implements Aggressable{
    @Override
    public void LevelOfAggression() {
        System.out.println("ActiveAggression");
    }
}
public class Main {
    public static void main(String[] args) {
        MalardDuck malard = new MalardDuck();
        malard.performShowLevelOfAggression();
    }
}

Ответы (1 шт):

Автор решения: Nowhere Man

Вряд ли в книге написано, что нужно создавать ещё одно публичное поле в подклассе и скрывать таким образом защищённое неинициализированное поле родительского класса:

public abstract class Duck {
    protected Aggressable aggressable; // по умолчанию это поле null
    // ...
}

public class MalardDuck extends Duck {
    public Aggressable aggressable =  new ActiveAggression(); // !!! НЕВЕРНО
    // ...
}

Дело в том, что поскольку метод performShowLevelOfAggression реализован только в родительском классе, он будет обращаться к полю этого родительского класса, которое равно null.

Достаточно было бы проинициализировать защищённое поле родительского класса в конструкторе подкласса, так как для этого собственно поле и объявлено как protected

public class MalardDuck extends Duck {
    public MalardDuck() {
        this.aggressable = new ActiveAggression(); // вот и всё!
    }
}

Разумеется, данное затруднение можно было бы разрешить, переопределив метод performShowLevelOfAggression в классе-потомке, тогда этот метод обращался бы к своему перекрывающему полю agressable, но в целом такой дизайн плохой, так как он приводит к ненужному дублированию кода.

public class MalardDuckBad extends Duck {
    // скрывает поле родительского класса
    public Aggressable aggressable = new ActiveAggression();
    public MalardDuck() {}

    // дублируется код из родительского класса
    @Override
    public void performShowLevelOfAggression() {
        aggressable.LevelOfAggression();
    }
}


Также следует отметить, что метод performShowLevelOfAggression не совсем корректно объявлен как protected, так как его нельзя будет вызвать для экземпляра Duck из другого пакета, по идее он должен был бы быть публичным.

→ Ссылка