Страницы

Поиск по вопросам

воскресенье, 26 января 2020 г.

Java модификаторы доступа

#java #generics


С учебными целями создаю собственные реализации некоторых структур данных. Надо создать
обычное бинарное дерево поиска, затем от него унаследовать красно-чёрное и другие самосбалансированные.
Для каждого типа дерева есть два класса: собственно дерево и узел (поскольку узлы обычного
и красно-чёрного деревьев различаются — второй содержит ещё информацию о цвете). Соответственно
просто унаследовать от обычного дерева красно-чёрное не получится.

Я пытаюсь решить это проблему при помощи обобщённых классов. Класс GenTree выглядит
следующим образом: 

package BasicTrees;

public abstract class GenTree> 
    implements Iterable {

    protected NodeType root;

    protected abstract NodeType newNode(KeyType key, Type value);
    ...
}


в этом классе реализованы операции вставки, поиска, удаления, итератор для обычного
дерева. Метод newNode реализуется уже в каждом конкретном типе дерева, и служит в дальнейшем
вместо конструктора для создания новых узлов.
Класс GenNode выглядит следующим образом:

package BasicTrees;

public class GenNode> {

    protected final KeyType key;
    protected Type value;
    protected NodeType left;
    protected NodeType right;
    protected NodeType parent;

    protected GenNode(KeyType key, Type value) {
        this.key = key;
        this.value = value;
    }
}


После этого обычное дерево создаётся тривиально классами Tree:

package BasicTrees;

public class Tree
        extends GenTree> {

    @Override
    protected Node newNode(KeyType key, Type value) {
        return new Node<>(key, value);
    }
}


и Node: 

package BasicTrees;

public class Node
        extends GenNode> {

    protected Node(KeyType key, Type value) {
        super(key, value);
    }
}


пакет BasicTree также содержит класс TreeIterator.

Для создания красно-чёрного и других самосбалансированных деревьев требуется создать
отдельный пакет RBTrees, чтобы они не путались. Классы RBTree и RBNode наследуются
от GenTree и GenNode аналогично. В классе RBNode добавляется поле, отвечающее за цвет
узла. В классе RBTree требуется переписать методы, отвечающие за вставку и удаление
узлов, метод поиска и итератор остаются теми же. Для этого переписанные методы класса
RBTree должны иметь доступ к protected полям left, right, parent, объявленным в классе
GenNode и унаследованным RBNode. Но модификатор protected доступа к ним не даёт, так
как RBTree не является наследником GenNode и не находится с ним в одном пакете.

Можно ли как-то дать доступ к защищённым полям наследников GenNode для наследников
GenTree, не давая его всей программе? Или единственным выходом будет создание в классе
RBNode геттеров и сеттеров с видимостью пакета, а классе RBTree их использовать? Но
тогда придётся их создавать каждый раз при создании нового типа дерева. Имеет ли смысл
создание этих геттеров и сеттеров с модификатором protected в классе GenNode? В таком
случае они создаются один раз.

Хотелось бы услышать мнение сообщества на этот счёт, а также замечания по структуре
программы в целом. Был бы рад узнать, можно ли мою задумку реализовать проще.

Спасибо за возможность задать вопрос и подумать над ним в процессе написания. Вариант
с геттерами и сеттерами в GenTree (который мне кажется оптимальным) я придумал уже
в процессе когда писал.
    


Ответы

Ответ 1



Я бы всё разместил в одном пакете, а классы, связанные с конкретным типом дерева, сделал бы вложенными классами. И активно бы использовал package-private доступ. Именно так реализованы, например, HashMap и LinkedHashMap в OpenJDK. Они довольно интенсивно переиспользуют код друг друга, переиспользуемые поля, методы и классы все объявлены package-private. В целом package-private и организация кода с помощью вложенных классов — это очень хорошо. Не факт, например, что вы вообще хотите выставлять на публику базовый класс GenTree. Опубликовав его одиножды, вы навсегда будете привязаны к нему. А вдруг в дальнейшем рефакторинге вы захотите его сильно переделать (например, добавить поддержку небинарных деревьев) или вообще от него отказаться? По большому счёту это деталь реализации. Поэтому его тоже можно сделать package-private.

Ответ 2



Раз уж одному типу дерева соответствует один тип узлов, то можно делать конкретную реализацию узла внутренним классом его дерева. Тогда данный тип дерева будет иметь доступ к его закрытым (private) полям и методам. Узел при этом по-прежнему может наследоваться от абстрактного узла.

Комментариев нет:

Отправить комментарий