Мотивация
Эта статья написана по мотивам вопроса, который я получил на занятиях по методологии программирования. В этом классе, в котором мы пишем код на Java для решения упражнений по программированию, у нас есть ограничение, что каждый атрибут класса должен быть private
и final
. Это означает, что нет доступа к полю за пределами класса, и никакие изменения не допускаются после инициализации этого поля. Это строгое требование введено для обеспечения неизменяемости при конструировании объекта класса в Java.
Рано или поздно, когда упражнения становятся сложнее, мы переходим к ООП-решению, при котором множество классов конструируются и организуются с помощью наследования. Затем возникает проблема, когда нужно получить доступ к этому private final
полю в родительском классе из подкласса. Что же делать в таком случае?
Чтобы привести конкретный пример, предположим, что у нас есть следующие классы:
class Parent {
private final int value;
Parent(int value) {
this.value = value;
}
}
class Child extends Parent {
Child(int value) {
super(value);
}
int add(int another) {
return super.value + another; // UNABLE TO ACCESS!
}
}
Что делать, если дочерний класс хочет получить доступ к value
из родительского?
Решения
Изменить модификатор
Самый простой способ решить эту проблему — изменить модификатор доступа с private
на что-то другое — возможно, public
или protected
. Это решение может быть легитимным в зависимости от контекста. В некоторых случаях, возможно, совершенно нормально раскрывать это значение для других классов.
Добавьте метод getter
Из учебника Oracle по наследованию в Java
Подкласс не наследует
private
членов своего родительского класса. Однако, если суперкласс имеет публичные или защищенные методы для доступа к своим приватным полям, они также могут быть использованы подклассом.
Поэтому еще одно возможное решение — иметь метод getter в родительском классе и сделать этот метод общедоступным. Таким образом, дочерние классы (и технически другие классы) будут иметь доступ через метод getter. В качестве примера можно привести следующий пример:
class Parent {
private final int value;
Parent(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
class Child extends Parent {
Child(int value) {
super(value);
}
int add(int another) {
return super.getValue() + another; // CAN ACCESS!
}
}
Наличие метода getter может быть полезно в том смысле, что даже если теперь «приватное» поле открыто, у вас все еще есть один уровень абстракции над ним. Пользователям метода getter не нужно знать, как генерируется это значение, которым можно манипулировать (при необходимости) с помощью некоторых сложных шагов предварительной обработки в методе getter. Кроме того, базовое частное поле может сильно измениться, и пользователи метода getter не будут об этом знать.
Переосмыслить дизайн кода
Наконец, эта проблема может быть сигналом к переосмыслению того, есть ли законная необходимость в доступе к полю private final
. Учитывая отношения «родитель-ребенок», иногда трудно четко определить, какое поле/метод должен находиться в каком классе.
- Будет ли лучше, если поле будет находиться в дочернем классе?
- Можно ли перенести то, что дочерний класс хотел сделать со значением, в родительский класс в качестве общего метода, который дочерний класс может унаследовать и, возможно, переопределить?
Лучшая конструкция кода может предполагать, что приватное конечное поле может остаться как есть, поддерживая барьер абстракции между родительским и дочерним классом. В качестве примера можно привести следующее решение:
class Parent {
private final int value;
Parent(int value) {
this.value = value;
}
int add(int another) {
return this.value + another;
}
}
class Child extends Parent {
Child(int value) {
super(value);
}
int add(int another) { // will work if this method is omitted as well,
return super.add(another); // as it will be inherited
}
}
Антишаблон
Проблематичный обходной путь, который некоторые могут придумать, заключается в повторном объявлении того же поля в дочернем классе.
class Parent {
private final int value;
Parent(int value) {
this.value = value;
}
}
class Child extends Parent {
private final int value;
Child(int value) {
super(value);
this.value = value;
}
int add(int another) {
return this.value + another; // will work but not recommended
}
}
Это работает, но, возможно, является плохим дизайном, поскольку не использует наследование для уменьшения дубликатов между общими свойствами. Это также может привести к тому, что значения (которые должны представлять одно и то же) будут рассинхронизированы, особенно если эти поля не были объявлены как final
.
Заключение
Когда мне задали мотивирующий вопрос, моим немедленным ответом было: «сделать публичный метод getter». На что мне был задан следующий вопрос:
- Почему мы прибегаем к использованию публичного метода getter, когда хотим сохранить поле приватным?
Что заставило меня задуматься:
- Почему приватные поля не могут быть унаследованы?
Эта статья стала для меня напоминанием о том, что нужно чаще задавать вопросы «почему» и исследовать причины ответов.