Примеры применения внутренних классов в сочетании с интерфейсами. Нисходящее и восходящее преобразование. Преимущества применения восходящего преобразования

Примеры применения внутренних классов в сочетании с интерфейсами. Нисходящее и восходящее преобразование. Преимущества применения восходящего преобразования


Содержание



1. Понятие нисходящего преобразования

Если в классе OutClass объявляется внутренний класс InClass, который имеет доступ public

class OutClass {
    public class InClass {
        // реализация класса InClass
        // ...

        // реализация методов класса InClass
        int method1() {
            // инструкции метода method1()
            // ...
        }

        int method2() {
            // инструкции метода method2()
            // ...
        }
    }
}

то при нисходящем преобразовании использовать методы method1(), method2() класса InClass можно по следующей схеме

// демонстрация использования нисходящего преобразования
// создать объект класса OutClass стандартным способом
OutClass oc = new OutClass();

// создать объект класса OutClass.InClass с помощью инструкции .new
OutClass.InClass ic = oc.new OutClass.InClass();

Итак, при нисходящем преобразовании важно, чтобы внутренний класс имел доступ public.

 

2. Особенности реализации восходящего преобразования в случае внутреннего private-класса

Если нужно использовать преимущества восходящего преобразования для внутреннего класса, то нужно придерживаться нижеследующих шагов. Для примера рассмотрен внутренний класс, который должен предоставить программисту-клиенту два метода в пользование с ограниченным доступом к другим типам и реализациям внутреннего класса. Методы имеют имена method1(), method2().
Последовательность шагов следующая.
1. Объявить private-внутренний класс InClass во внешнем классе OutClass

class OutClass {
    private class InClass {
        // реализация класса InClass
        // ...

        // реализация методов, которые должны быть доступны программисту-клиенту
        int method1() {
            // инструкции метода method1()
            // ...
        }

        int method2() {
            // инструкции метода method2()
            // ...
        }
    }
}

После такого объявления, доступ к внутреннему private-классу InClass имеет только внешний для него класс OutClass. Программист-клиент не имеет доступа к классу InClass. Также невозможно осуществить нисходящее преобразование к закрытому (private) классу InClass путем вызова

OutClass oc = new OutClass();
// OutClass.InClass ic = oc.new InClass(); - это есть ошибка!

2. Объявить интерфейс. На этом шаге нужно объявить интерфейс и выбрать внутренние методы, которые будут доступны программисту-клиенту а также модифицировать объявление класса InClass. Эти методы реализуются в классе InClass по нижеследующему образцу

public interface InterfaceInClass {
    // интерфейсные методы, которые представляются в пользование программисту-клиенту
    int method1();
    int method2();
}

class OutClass {
    // данные и методы класса OutClass
    // ...

    // реализация класса InClass с учетом методов интерфейса InterfaceInClass
    // добавляется 'implements '
    private class InClass implements InterfaceInClass {
        // ...

        // реализация интерфейсных методов
        int method1() {
            // ...
        }

        int method2() {
            // ...
        }
    }
}

В вышеприведенном коде программисту-клиенту предоставляются в пользование два метода

int method1();
int mehtod2();

Имена методов и их параметры выбраны наугад.

3. Объявить метод, реализующий восходящее преобразование. На третьем этапе в классе OutClass формируется метод, который возвращает объект класса InClass. После этих изменений, объявление интерфейса InterfaceInClass и класса OutClass будет иметь следующий вид:

public interface InterfaceInClass {
    // интерфейсные методы, которые предоставляются в пользование программисту-клиенту
    int method1();
    int method2();
}

class OutClass {
    // данные и методы класса OutClass
    // ...

    // реализация класса InClass с учетом методов интерфейса
    private class InClass implements InterfaceInClass {
        // ...

        // реализация интерфейсных методов
        int method1() {
            // инструкции метода method1()
            // ...
        }

        int method2() {
            // инструкции метода method2()
            // ...
        }
    }

    // метод класса OutClass, возвращающий объект интерфейса InterfaceInClass
    public InterfaceInClass GetInterfaceInClass() {
        // некоторые инструкции (могут отсутствовать)
        // ...
        return new InClass(); // возвратить экземпляр класса InClass
    }
}

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

// получить объект класса OutClass
OutClass oc = new OutClass();

// через метод GetInterfaceInClass получить объект ic
InterfaceInClass ic = oc.GetInterfaceInClass();

// вызвать метод через полученный объект ic
int res;
res = ic.method1();

 

3. Преимущества применения восходящего преобразования в сочетании с внутренними классами

Реализация восходящего преобразования дает разработчику следующие преимущества:

  • запрещается доступ к типам, которым соответствуют внутренние классы;
  • скрываются все детали реализации внутреннего класса;
  • клиенту-программисту предоставляется доступ только к необходимым (ограниченным) реализациям внутреннего класса;
  • для программиста-клиента не будет иметь смысла расширять интерфейс за счет добавления новых методов, так как он не будет иметь доступ к дополнительным методам, которые не принадлежат к открытой части класса;
  • компилятор Java будет иметь возможность оптимизировать код.

 

4. Особенности реализации восходящего преобразования в случае внутреннего protected-класса

Восходящее преобразование для внутренних protected-классов работает по такому же принципу, как и для внутренних private-классов как описано в п.2. В случае private-классов доступ к внутреннему классу имеет внешний класс. В случае protected-классов доступ к внутреннему классу имеют:

  • внешний класс;
  • унаследованный от внешнего класс;
  • классы собственного пакета, в котором объявлен внешний класс. Спецификатор protected дает доступ в пределах собственного пакета.

Для protected-классов можно сделать нисходящее преобразование только из унаследованного класса.

 

5. Пример, который демонстрирует нисходящее преобразование

Объявляется внешний класс Calculation, в котором реализовано множество внутренних классов, которые проводят математические вычисления над числами. Один из внутренних классов называется Complex. Он реализует базовые операции над комплексными числами. В результате объявления класс Calculation имеет вид:

// внешний класс, который содержит реализацию класса Complex
class Calculation {
    // внутренний класс Complex
    public class Complex {
        double real; // вещественая часть
        double imag; // мнимая часть

        // метод, возвращающий модуль комплексного числа
        double Abs() {
            return Math.sqrt(real*real+imag*imag);
        }
    }
}

Использование класса Complex в другом методе демонстрирует нисходящее преобразование

// нисходящее преобразование
// создать объект класса Complex с помощью объекта класса Calculation
Calculation calc = new Calculation();
Calculation.Complex comp = calc.new Complex();
double res;

comp.imag = 3;
comp.real = 5;

// вызов метода вычисления модуля комплексного числа
res = comp.Abs(); // res = 5.830951894845301

 

6. Пример, который демонстрирует восходящее преобразование

Дан класс Calculation, в котором реализован скрытый (private) внутренний класс Complex. Класс Complex дает объявление своего общедоступного метода Abs() с помощью интерфейса IComplex.
Реализация класса Calculation и интерфейса IntComplex следующая:

// интерфейс, содержащий сигнатуру метода Abs()
interface IntComplex {
    double Abs();
}

// внешний класс, который содержит внутренний скрытый класс Complex
class Calculation {
    // класс реализует интерфейс IntComplex, обрабатывает комплексные числа
    private class Complex implements IntComplex {
        // внутренние данные класса complex
        private double imag; // мнимая часть комплексного числа
        private double real; // вещественная часть

        // конструктор класса с двумя параметрами
        public Complex(double _imag, double _real) {
            imag = _imag;
            real = _real;
        }

        // реализация метода, который объявляется в интерфейсе IntComplex
        public double Abs() {
            return Math.sqrt(real*real+imag*imag);
        }
    }

    // возвратить объект класса Complex
    public IntComplex GetComplex(double _imag, double _real) {
        // возвратить объект внутреннего класса
        return new Complex(_imag, _real); // вызов конструктора с 2 параметрами
    }
}

Метод GetComplex() класса Calculation возвращает объект (экземпляр) внутреннего класса Complex, реализуя тем самым восходящее преобразование. При создании объекта класса Complex в методе GetComplex() вызывается конструктор с 2-мя параметрами.
Ниже демонстрируется использование класса Complex в другом методе

// восходящее преобразование
Calculation calc = new Calculation(); // создать объект внешнего класса
IntComplex ic = calc.GetComplex(5, 6); // получить объект внутреннего класса
double res;

// вызвать метод внутреннего класса
res = ic.Abs(); // res = 7.810249675906654

Через интерфейс IntComplex() программист-клиент имеет доступ к методам внутреннего скрытого класса Complex. Доступ осуществляется через объект внешнего класса Calculation.

 

7. Какое отличие между нисходящим преобразованием и восходящим преобразованием? Пример

Отличие между восходящим и нисходящим преобразованием проявляется в способе получения объекта внутреннего класса. При нисходящем преобразовании объект внутреннего класса создается с помощью оператора .new путем обращения к имени объекта внешнего класса

// нисходящее преобразование
OutClass oc = new OutClass(); // создать объект внешнего класса
OutClass.InClass ic = oc.new InClass(); // создать объект внутреннего класса

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

OutClass oc = new OutClass();
OutClass.InClass ic = oc.GetObjInClass();

где GetObjInClass() – метод, который создает экземпляр внутреннего класса OutClass.InClass. В этом случае внутренний класс OutClass.InClass должен иметь доступ public или protected для унаследованного класса.
В случае реализации внутреннего класса в сочетании с интерфейсом, программный код восходящего преобразования может выглядеть следующим образом:

OutClass oc = new OutClass();
InterfaceInClass ic = oc.GetInterfaceInClass();

здесь

  • OutClass – внешний класс, для которого создается объект с именем oc;
  • InterfaceInClass – интерфейс, который объявляет методы внутреннего класса для их использования программистом-клиентом;
  • GetInterfaceInClass() – метод, который возвращает объект (конкретный экземпляр) внутреннего класса OutClass.InClass(), который реализует (implements) интерфейс InterfaceInClass.

 


Связанные темы