Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Metoda_OOP.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
1.71 Mб
Скачать

5.2.2Рекурсивный проход по деревьям разных классов

Способ обращения к деревьям не должен зависеть от вида дерева, а реализация этого способа для каждого дерева должна быть своя собственная. Для решения поставленной задачи необходимо:

  1. Описание в интерфейсном классе узла ITree рекурсивных методов, например void forwardTraverse( );

  2. Реализация этих методов в классах LinkedTree и ArrayTree, например

    public void forwardTraverse( ){…}

  3. Вызов такого метода, с параметром в виде ссылки на узел типа LinkedNode или ArrayNode, например forwardTraverse(pNode);

Проблема описания и реализации интерфейсных методов forwardTraverse( ) или backwardTraverse( ), которые в качестве параметра получают ссылку на узел дерева, состоит в том, что узлы LinkedNode и ArrayNode имеют разные типы. Поэтому общий для них интерфейсный метод, требующий в качестве параметра ссылку на тот или иной узел дерева, в качестве параметра должен иметь параметр типа интерфейс INode, например

void forwardTraverse(INode pNode)

Далее в классе LinkedTree или ArrayTree необходимо описать реализацию этих методов, например

public void forwardTraverse(INode pNode) {…..}

После этого в требуемом месте программы можно вызвать такой метод, передав ему ссылку на корень дерева, например:

ITree pTree = new LinkedTree();

pTree.forwardTraverse(pTree.GetRoot());

Таким образом, становится понятной необходимость в интерфейсах и их реализации в конкретных предметных классах.

Поскольку обычно производится проход по дереву начиная с корневого узла, имеет смысл снабдить интерфейс ITree дополнительными методами-оболочками, которые не принимают ссылку на корень дерева и используют внутри метод getRoot() для получения такой ссылки. Например:

public void forwardTraverse() {

forwardTraverse(getRoot())

}

5.2.3Расширение ранее созданных классов специфическими операциями пользователей

Проблема состоит в том, что в Java, в отличии от C++, нет понятия указатель на функцию, с помощью которого можно настроить общий метод на специфические действия.

Поэтому в Java операция, настраивающая другую операцию, должна быть представлена в виде объекта. Для создания такого объекта можно воспользоваться несколькими подходами. Все они на начальном этапе предполагают:

  1. Создание дополнительного программного интерфейса, например TreeOperation, содержащего специфические пользовательские методы, принимающие узел дерева в качестве параметра, например,

    void processNode (INode pNode);

  2. Пополнить интерфейс ITree новыми объявлениями методов forwardTraverse и backwardTraverse, например, для метода forwardTraverse

void forwardTraverse(INode pNode, TreeOperation pTreeOperation)

Такой метод будет, в частности, в качестве параметра принимать экземпляр класса, реализующего интерфейс TreeOperation, и в нужный момент будет вызывать требуемый метод данного интерфейса, тем самым, эмулируя вызов функции по указателю, известный вам по языку C++.

  1. В каждом из классов реализации LinkedTree и ArrayTree выполнить реализацию нового метода прохода по дереву, например

public void forwardTraverse(INode pNode, TreeOperation pTreeOperation){

pTreeOperation.processNode(pNode);

}

Далее рассмотрим наиболее распространенные подходы.

I. Первый – универсальный, подход предполагает выполнение следующих двух дополнительных шагов:

  1. Создать класс реализации интерфейса, например PrintTreeOperation, и выполнить в нем реализацию методов интерфейса TreeOperation, например:

    public void processNode(INode pNode){

    System.out.println(pNode.getData())

    }

  2. После этого в требуемом месте программы можно вызвать метод forwardTraverse( ), передав ему ссылку на объект класса PrintTreeOperation, например

ITree pTree = new LinkedTree()

далее так:

TreeOperation pTreeOperation = new PrintTreeOperation()//ссылка //на объект класса реализации PrintTreeOperation

pTree.forwardTraverse(pTreeOperation) //вызов

или компактнее:

pTree.forwardTraverse(new PrintTreeOperation()) //вызов

Достоинство такого подхода в том, что класс реализации операций пользователя является внешним и позволяет описывать методы любой сложности. Такой подход к реализации выполняемых действий целесообразно применять, если реализация сложная и требует частого использования несколькими различными классами. Если же действия необходимо выполнять только при проходе по дереву, и они являются достаточно простыми – есть смысл рассмотреть следующие подходы.

Следующие два подхода II и III, носят преимущественно учебно-иллюстративный характер и логически подводят к IV и V подходам, которые так же могут рассматриваться как достаточно универсальные и широко используемые.

II. Второй подход предполагает создание дополнительного внутреннего класса, реализующего пользовательскую операцию. Для этого необходимо выполнение следующих трех дополнительных шагов:

  1. В классах LinkedTree и ArrayTree создать внутренние классы, реализующие интерфейс операции, например:

    1 public class LinkedTree {

    2 private class PrintTreeOperation implements TreeOperation{

    3 public void processNode(INode pNode){

    4 //собственная реализация интерфейса операции

    5 }

    6 } // PrintTreeOperation

    7 …

    8 } // LinkedTree

  2. В интерфейсе ITree и соответственно в классах LinkedTree и ArrayTree создать соответствующие методы, возвращающие ссылку на объект внутреннего класса PrintTreeOperation, например

    public TreeOperation getPrintOperation() {

    return new PrintTreeOperation();

    }

  3. После этого в требуемом месте программы можно вызвать метод forwardTraverse(….), передав ему ссылку на объект класса PrintTreeOperation, например:

ITree pTree = new LinkedTree()

pTree.forwardTraverse(pTree.getPrintOperation());

Достоинство такого подхода в том, что класс реализации операции пользователя располагается непосредственно в классе реализации дерева, что позволяет при реализации обращаться к закрытым полям дерева. Однако использование внутреннего класса не всегда удобно, так как предполагает изменение реализации класса дерева, его интерфейса при необходимости добавления новых операций. Таким образом, данный подход целесообразно применять лишь для реализации общедоступных операций, которые могут часто понадобиться клиентам дерева.

III. Третий подход подобен второму, но предполагает создание присущего Java безымянного (анонимного) внутреннего класса, реализующего пользовательскую операцию. Для этого необходимо выполнение следующих двух дополнительных шагов. В классах LinkedTree и ArrayTree создать безымянные внутренние классы, реализующие интерфейс операции TreeOperation.

1 public class LinkedTree {

2 public TreeOperation getPrintOperation(){

3 new TreeOperation(){ //это задание безымянного класса

4 public void processNode(INode pNode){

5 //собственная реализация интерфейса операции

6 }

7 }

8 …

9}

В этом случае метод getPrintOperation() генерирует ссылку на объект безымянного класса.

После этого в требуемом месте программы можно вызвать метод forvardTraverse(….), передав ему результат выполнения метода getPrintOperation(), например:

ITree pTree = new LinkedTree()

pTree.forwardTraverse(pTree.getPrintOperation())

Достоинства и недостатки такого подхода те же что и в предыдущем случае, разница состоит лишь в более компактном способе объявления класса реализации.

IV. Четвертый подход основан на использовании безымянного внутреннего класса, который описывается непосредственно при вызове метода прохода по дереву. Такое решение подходит для случая, когда метод пользователя состоит из одного – двух простых операторов или вызовов других методов, иначе код становится тяжело читаемым.

В результате без дополнительных шагов в требуемом месте программы можно вызвать метод forwardTraverse(….), задав определение безымянного класса:

ITree pTree = new LinkedTree();

pTree.forwardTraverse(new TreeOperation() {

public void processNode (INode pNode){

//один – два оператора, например

System.out.println(pNode.getData());

}

};) // использование символа ";" здесь обязательно

Этот подход является иллюстрацией того, ради чего в Java в первую очередь и был введен механизм безымянных внутренних классов. На этом принципе построено описание слушателей (Listener) событий в Java. Такой подход позволяет превратить проход по дереву в любую операцию, которая объявляется прямо в месте использования. Например, для выполнения слияния всех данных дерева в одну последовательную строку можно произвести следующий вызов:

1 private String joinResult = "";

2 pTree.forwardTraverse(new TreeOperation(){

3 public void processNode(INode pNode){

4 joinResult += pNode.getData()

5 }

6 };)

V. Пятый подход является развитием второго и третьего, и основан на использовании определяемых пользователем констант-объектов для безымянных внутренних классов. Фактически, отличие состоит лишь в способе получения ссылки на реализацию пользовательской операции. Достоинством данного подхода является возможность использования предопределенных часто используемых операций (например, вывода данных узла на экран), без необходимости использования экземпляра дерева.

Для этого необходимо выполнение двух дополнительных шагов. В класс реализации дерева необходимо добавить константу, например PRINT_TREE_OPER, указывающую на одну из реализаций операции обработки узла, а именно вывод данных из узла на экран, например:

1 public static final TreeOperation PRINT_TREE_OPER =

2 new TreeOperation(){//это задание безымянного класса

3 public void processNode(InNode pNode){

4 //любое количество операторов

5 System.out.println(pNode.getData());

6 }

7 … //Возможные другие функции

8 };

После этого в требуемом месте программы можно вызвать метод forwardTraverse(….), передав ему константу, PRINT_TREE_OPER, указывающую на одну из реализаций операции обработки узла, например:

pTree.forwardTraverse(LinkedTree.PRINT_TREE_OPER);