1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
6.3 组合与继承的结合.md 13 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

6.3 Комбинация наследования и композиции

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

//: PlaceSetting.java
// Объединение композиции и наследования

class Plate {
  Plate(int i) {
    System.out.println("Конструктор Plate");
  }
}

class DinnerPlate extends Plate {
  DinnerPlate(int i) {
    super(i);
    System.out.println(
      "Конструктор DinnerPlate");
  }
}

class Utensil {
  Utensil(int i) {
    System.out.println("Конструктор Utensil");
  }
}

class Spoon extends Utensil {
  Spoon(int i) {
    super(i);
    System.out.println("Конструктор Spoon");
  }
}

class Fork extends Utensil {
  Fork(int i) {
    super(i);
    System.out.println("Конструктор Fork");
  }
}

class Knife extends Utensil {
  Knife(int i) {
    super(i);
    System.out.println("Конструктор Knife");
  }
}

// Культурный способ выполнения чего-либо:
class Custom {
  Custom(int i) {
    System.out.println("Конструктор Custom");
  }
}

public class PlaceSetting extends Custom {
  Spoon sp;
  Fork frk;
  Knife kn;
  DinnerPlate pl;

  PlaceSetting(int i) {
    super(i + 1);
    sp = new Spoon(i + 2);
    frk = new Fork(i + 3);
    kn = new Knife(i + 4);
    pl = new DinnerPlate(i + 5);
    System.out.println(
      "Конструктор PlaceSetting");
  }

  public static void main(String[] args) {
    PlaceSetting x = new PlaceSetting(9);
  }
} ///:~

Хотя компилятор заставляет нас инициализировать базовый класс и требует делать это в самом начале конструктора, он не контролирует корректность инициализации членов объекта. Поэтому следует особенно внимательно относиться к этому вопросу.## 6.3.1 Уверенность в правильной очистке

Java не имеет такого понятия, как "деструктор" в C++. В C++ при удалении (очистке) объекта автоматически вызывается метод деструктора. Его отсутствие может быть связано с тем, что в Java достаточно просто забыть об объекте, не выполняя явной очистки. Garbage collector автоматически освобождает память, когда это необходимо. Сборщик мусора обычно хорошо работает в большинстве случаев, но в некоторых ситуациях наши классы могут выполнять какие-то действия в течение своего жизненного цикла, требующие явной очистки. Как было указано в главе 4, мы не знаем, когда сборщик мусора будет активирован или вызван. Поэтому, если требуется очистка каких-либо данных для класса, следует написать специальный метод, который будет специально предназначен для этой задачи. В то же время важно сообщить программистам-клиентам о необходимости использования этого метода. За всем этим, как подробно объясняется в главе 9 (контроль ошибок), следует поместить такой код очистки в блок finally, чтобы защититься от любых возможных исключений.```markdown Сборщик мусора обычно хорошо работает в большинстве случаев, но в некоторых ситуациях наши классы могут выполнять какие-то действия в течение своего жизненного цикла, требующие явной очистки. Как было указано в главе 4, мы не знаем, когда сборщик мусора будет активирован или вызван. Поэтому если требуется очистка каких-либо данных для класса, следует написать специальный метод, который будет специально предназначен для этой задачи. В то же время важно сообщить программистам-клиентам о необходимости использования этого метода. За всем этим, как подробно объясняется в главе 9 (контроль ошибок), следует поместить такой код очистки в блок finally, чтобы защититься от любых возможных исключений.

Рассмотрим пример системы компьютерной графики, которая может отображать фигуры на экране:

```java
//: CADSystem.java
// Обеспечение правильной очистки
import java.util.*;

class Shape {
  Shape(int i) {
    System.out.println("Конструктор Shape");
  }
  void cleanup() {
    System.out.println("Очистка Shape");
  }
}

class Circle extends Shape {
  Circle(int i) {
    super(i);
    System.out.println("Отрисовка круга");
  }
  void cleanup() {
    System.out.println("Удаление круга");
    super.cleanup();
  }
}

class Triangle extends Shape {
  Triangle(int i) {
    super(i);
    System.out.println("Отрисовка треугольника");
  }
  void cleanup() {
    System.out.println("Удаление треугольника");
    super.cleanup();
  }
}

class Line extends Shape {
  private int start, end;
  Line(int start, int end) {
    super(start);
    this.start = start;
    this.end = end;
    System.out.println("Отрисовка линии: "
           + start + ", " + end);
  }
  void cleanup() {
    System.out.println("Удаление линии: "
           + start + ", " + end);
    super.cleanup();
  }
}

public class CADSystem extends Shape {
  private Circle c;
  private Triangle t;
  private Line[] lines = new Line[10];
  CADSystem(int i) {
    super(i + 1);
    for(int j = 0; j < 10; j++)
      lines[j] = new Line(j, j * j);
    c = new Circle(1);
    t = new Triangle(1);
    System.out.println("Объединённый конструктор");
  }
  void cleanup() {
    System.out.println("CADSystem.cleanup()");
    t.cleanup();
    c.cleanup();
    for(int i = 0; i < lines.length; i++)
      lines[i].cleanup();
    super.cleanup();
  }
  public static void main(String[] args) {
    CADSystem x = new CADSystem(47);
    try {
      // Код и обработка исключений...
    } finally {
      x.cleanup();
    }
  }
} ///:~
```В этой системе все объекты являются некоторым типом `Shape` (геометрической формы). `Shape` сам по себе является `Object` (объектом), так как он явно наследуется от базового класса. Каждый класс переопределяет метод `cleanup()` класса `Shape`, а также вызывает его версию в базовом классе с помощью ключевого слова `super`. Хотя все методы могут выполнять задачи очистки во время существования объекта, каждый конкретный тип `Shape` — `Circle` (круг), `Triangle` (треугольник) и `Line` (линия) имеют свои конструкторы для выполнения задачи отрисовки (`draw`). У каждого класса есть свой метод `cleanup()`, который используется для восстановления состояния до создания объекта, если это возможно. В методе `main()`, можно заметить два новых ключевых слова: `try` и `finally`. Полное объяснение этих ключевых слов будет дано в главе 9. Ключевое слово `try` указывает, что следующий за ним блок (ограниченный фигурными скобками) является "опасной зоной". Это значит, что он получает особое обращение. Одним из таких обращений является то, что код, расположенный после ключевого слова `finally`, обязательно выполнится — вне зависимости от того, произошло ли что-либо в блоке `try` (через механизм управления исключениями, блок `try` может иметь множество необычных применений). В данном случае, ключевое слово `finally` означает "всегда вызывать `cleanup()` для `x`, независимо от того, что происходит". Эти ключевые слова будут подробно объяснены в главе 9.При реализации своего метода очистки, важно обратить внимание на порядок вызова методов очистки базового класса и членских объектов — если один подкласс зависит от другого. Обычно следует придерживаться формы, аналогичной тому, как компилятор C++ обращается со своими "деструкторами": сначала выполняются все специальные операции, связанные с классом (что может потребовать видимости элементов базового класса), а затем вызывается метод очистки базового класса, как показано здесь.

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

### 6.3.2 Прятание имен

Только программисты C++ могут удивиться прятанию имен, так как принцип работы отличается от C++. Если базовый класс Java имеет метод, имя которого используется несколько раз ("перегружено"), переопределение этого имени в производном классе не скроет ни одной версии базового класса. Таким образом, перегрузка будет работать независимо от того, где метод был определён:```java
//: Hide.java
// Переопределение имени метода базового класса
// в производном классе не скрывает версий
// базового класса

Как будет объяснено в следующей главе, редко когда переопределяемые методы имеют одинаковые сигнатуры и типы возвращаемых значений с базовым классом, так как это может вызвать путаницу (это одна из причин, почему C++ запрещает такое поведение, что позволяет избежать некоторых ненужных ошибок).```markdown class Homer { char doh(char c) { System.out.println("doh(char)"); return 'd'; } float doh(float f) { System.out.println("doh(float)"); return 1.0f; } }

class Milhouse {}

class Bart extends Homer { void doh(Milhouse m) {} }

class Hide { public static void main(String[] args) { Bart b = new Bart(); b.doh(1); // doh(float) используется b.doh('x'); b.doh(1.0f); b.doh(new Milhouse()); } } ///:~


Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master