Повторное использование кода в Java. Понятие композиции, наследования, делегирования. Ключевое слово extends. Примеры

Повторное использование кода в классaх. Понятие композиции, наследования, делегирования. Ключевое слово extends. Примеры


Содержание



1. Каким образом повторное использование кода применяется в классах?

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

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

2. Какие существуют способы (подходы) построения классов для эффективного обеспечения повторного использования кода?

Если нужно повторно использовать существующий программный код, то классы должны строиться таким образом, чтобы не изменить (повредить) этот код.

В объектно-ориентированных языках программирования (Java, C++, C# и прочих) существуют три способа построения классов, которые повторно используют программный код:

  • композиция (composition). В этом случае класс содержит в себе объект другого класса, программный код которого нужно использовать. Такой подход использует функциональность готового кода;
  • наследование (inheritance). Класс наследует другой код (класса) и изменяет его свойства (поведение) или добавляет собственные возможности (поведение) к унаследованному коду;
  • делегирование. Это что-то промежуточное между композицией и наследованием.

Эти способы еще называют отношениями между классами. Во всех способах строятся новые классы (типы) на базе уже существующих классов (типов).

3. Примеры композиции в классах

Пример 1. Класс B включает в себя объекты (экземпляры) класса A. Значение объектов класса A инициализируются разными способами:

  • непосредственно (объект a1);
  • в конструкторе (объект a2).
// Демонстрация композиции в классе
class A
{
    int a; // внутренняя переменная класса A

    // конструктор
    A() { a = 1; }

    // методы класса A
    void SetA(int a) { this.a = a; }
    int GetA() { return a; }
}

public class B
{
    int b; // внутренняя переменная класса B

    // композиция: в классе B объявляются объекты класса A
    A a1 = new A();
    A a2;

    // конструктор класса B
    B()
    {
        b = 0;
        a2 = new A();
        a2.SetA(15);
    }

    // методы доступа к внутренней переменной b
    void SetB(int nb) { b = nb; }
    int GetB() { return b; }

    public static void main(String[] args)
    {
        // композиция: в классе B объявляются объекты класса A
        B objB = new B(); // объект (екземпляр) класса B

        // демонстрация доступа
        int t;

        // доступ к объекту a2 класса A, в котором реализован метод GetA()
        t = objB.a2.GetA(); // t = 15

        // доступ к объекту a1 класса A
        t = objB.a1.GetA(); // t = 1

        System.out.println(t);
    }
}

Пример 2. Класс Pixel содержит объект класса Point.

// Класс Point, описывающий точку на плоскости
class Point
{
    int x, y; // внутренние переменные

    // методы доступа
    void SetXY(int nx, int ny)
    {
        x = nx; y = ny;
    }

    int GetX() { return x; }
    int GetY() { return y; }
}

// Класс Pixel, содержащий в себе объект класса Point
// Демонстрация композиции
public class Pixel
{
    int color;
    Point p; // объект класса Point - композиция

    // конструктор
    Pixel()
    {
        color = 0;
        p = new Point(); // виделить память для объекта p - обязательно
    }

    int GetColor() { return color; }
    void SetXYColor(int nx, int ny, int ncolor)
    {
        // в классе Pixel формируются значения объекта класса Point
        p.SetXY(nx, ny); // вызов метода объекта p класса Point
        color = ncolor;
    }

    public static void main(String[] args)
    {
        // демонстрация использования композиции
        Pixel px = new Pixel();

        px.SetXYColor(4, 7, 5);

        // проверка
        // доступ к методам класса Point через объект p, который объявлен в классе Pixel
        int d = px.p.GetX(); // d = 4
        d = px.p.GetY(); // d = 7

        px.p.SetXY(12, 23);
        d = px.p.GetX();

        // вызов метода класса Pixel
        d = px.GetColor(); // d = 5

        System.out.println("d = " + d);
    }
}

4. Какая общая форма простейшего наследования классом другого класса. Ключевое слово extends

Если в классе B нужно унаследовать программный код класса A, то простейшая общая форма такого объявления следующая:

class A
{
    // тело класса A
}

class B extends A
{
    // тело класса B
}

В этом случае методы и внутренние переменные класса A есть доступны в классе B. Здесь есть одно ограничение: доступными в классе B есть только методы и переменные, которые не содержат в объявлении модификатор доступа private.

5. Примеры наследования в классах

Пример 1. Пример наследования класса A классом B. В классе B наследуются:

  • внутренняя переменная a класса A;
  • внутренний метод SetA() класса A.

В методе Demo() класса B демонстрируется доступ к переменной и методу класса A.

// базовый класс
class A
{
    int a; // внутренняя переменная класса A

    // внутренний метод класса A
    void SetA(int na)
    {
        a = na;
    }
}

// класс B наследует поля и методы класса A
public class B extends A
{
    int b; // внутренняя переменная класса B

    // внутренний метод класса B
    void SetB(int nb)
    {
        b = nb;
    }

    // демонстрационный метод класса B
    void Demo()
    {
        // доступ к полям и методам класса A
        a = 23;
        SetA(93);

        // доступ к полям и методам класса B
        b = 90;
        SetB(50);
    }
}

Пример 2. Пусть дан класс Point, который описывает точку на плоскости. Класс Point есть базовым для класса Pixel. Точнее класс Pixel расширяет возможности класса Point путем добавления дополнительной переменной color и методов SetColor(), SetXYColor().

class Point
{
    int x, y;

    void SetXY(int nx, int ny)
    {
        x = nx; y = ny;
    }
}

public class Pixel extends Point // класс Pixel наследует класс Point
{
    int color;

    void SetColor(int ncolor)
    {
        color = ncolor;
    }

    void SetXYColor(int nx, int ny, int ncolor)
    {
        // в классе Pixel доступны внутренние переменные и методы класса Point
        x = nx; y = ny; color = ncolor;
    }

    public static void main(String[] args)
    {
        // демонстрация использования наследования
        Pixel p1 = new Pixel();
        p1.SetXY(4, 6); // доступ к методу класса Point из объекта класса Pixel

        // проверка
        int d = p1.x; // доступ к внутренней переменной класса Point через объект класса Pixel
    }
}

В функции main() класса Pixel создается объект (экземпляр) класса Pixel. В экземпляре происходит доступ к переменной x, которая объявляется в классе Point. Такой код есть корректным, так как класс Pixel наследует класс Point. Это есть особенностью (преимуществом) наследования, при котором можно расширять возможности уже существующего класса. Класс Pixel есть надмножеством над классом Point.

6. Пример объединения композиции и наследования

На примере объявления трех классов демонстрируется объединение композиции и наследования.

Объявляется три класса с именами Point, Line, LineColor. Между классами Point и Line реализована композиция. Класс Line содержит объявление объектов класса Point.
Между классами LineColor и Line реализована наследственность. Класс LineColor наследует внутренние переменные и методы класса Line.
Схема объединения изображена на рисунке.

Рисунок. Схема объединения композиции и наследования для трех классов

Исходный код модуля с разработанными классами.

// объединение композиции и наследования
// класс, который описывает точку на плоскости
class Point
{
    int x, y;

    void SetXY(int nx, int ny)
    {
        x = nx;
        y = ny;
    }

    int GetX() { return x; }
    int GetY() { return y; }
}

// линия, состоящая из двух точек
class Line
{
    // композиция
    Point p1;
    Point p2;

    // конструктор класса Line
    Line()
    {
        p1 = new Point();
        p2 = new Point();
        p1.SetXY(0, 0);
        p2.SetXY(1, 1);
    }

    // методы доступа
    Point GetP1() { return p1; }
    Point GetP2() { return p2; }

    void SetPoints(Point np1, Point np2)
    {
        p1.SetXY(np1.GetX(), np1.GetY());
        p2.SetXY(np2.GetX(), np2.GetY());
    }
}

// класс, расширяющий клас Line (добавляет цвет)
public class LineColor extends Line
{
    int color; // добавлена внутренняя переменная color - цвет линии

    LineColor() // конструктор класса LineColor()
    {
        color = 1;
    }

    // метод Length() класса LineColor использует методы класса Line
    // длина линиии
    double Length()
    {
        double len;
        int x1, y1, x2, y2;

        // метод Length имеет доступ к p1 и p2 класса Line
        x1 = p1.GetX(); y1 = p1.GetY();
        x2 = p2.GetX(); y2 = p2.GetY();

        len = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));

        return len;
    }

    public static void main(String[] args)
    {
        // демонстрация сочетания композиции и наследственности
        LineColor lc = new LineColor(); // экземпляр класса LineColor
        Point pt1, pt2; // дополнительные переменные
        double length;

        pt1 = new Point();
        pt2 = new Point();

        pt1.SetXY(3, 4);
        pt2.SetXY(5, 7);

        lc.SetPoints(pt1, pt2);

        length = lc.Length(); // length = 3.605551275463989

        System.out.println("Length = " + length);
    }
}

7. Особенности делегирования по сравнению с наследованием и композицией. Пример

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

class A
{
    // метод класса A
    void MethodA() { ... }

    // ...
}

// класс B реализует делегирование
class B
{
    private A objA = new A();

    // делегированный метод
    Method()
    {
        obj.MethodA(); // переадресация к встроенному объекту obj
    }
}

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

MethodA()
{
    objA.MethodA();
}

После такого переопределения обращение к методу MethodA() класса B будет выглядеть так если бы MethodA() был унаследован:

...
// объявить объект класса B
B obj = new B();

// обращение к MethodA() класса B, а выглядит как к методу MethodA() класса A
B.MethodA(); // будет вызван B.objA.MethodA()

...

Такая организация отношения между классами называется делегированием. Метод MethodA() класса B называется делегированным методом.

8. Какие отличия между композицией и наследованием?

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

Однако, между композицией и наследованием существуют различия с точки зрения выбора способа создания нового класса.

Наследование позволяет расширять возможности нового класса с точки зрения его специализации. Это значит, что создается специализированная версия уже существующего класса. То есть, берется уже существующий класс (базовый класс) и создается его новая специализированная версия. Как правило, в роли базового класса выступает некий класс общего назначения, в котором выделены общие черты задачи. Наследованием выражается взаимосвязь «есть» или «является».

Композиция используется, когда нужно использовать функциональность уже существующего класса в другом (новосозданном) классе. В этом случае в новосозданном классе объявляется объект класса, функциональность которого нужно использовать.

Объект класса в другом классе может быть скрытым (private), открытым (public) или защищенным (protected).


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