Преодоление барьера абстракции между родительским и дочерним классом — [ООП и Java #15]


Мотивация

Эта статья написана по мотивам вопроса, который я получил на занятиях по методологии программирования. В этом классе, в котором мы пишем код на 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, когда хотим сохранить поле приватным?

Что заставило меня задуматься:

  • Почему приватные поля не могут быть унаследованы?

Эта статья стала для меня напоминанием о том, что нужно чаще задавать вопросы «почему» и исследовать причины ответов.

Оцените статью
Procodings.ru
Добавить комментарий