54

54


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

//: reusing/Cartoon.java

// Вызовы конструкторов при проведении наследования, import static net.mindview.util.Print.*,

class Art {

ArtO { print("Конструктор Art"); }

}

class Drawing extends Art {

DrawingО { print("Конструктор Drawing"); }

}

public class Cartoon extends Drawing {

public CartoonO { print("Конструктор Cartoon"); } public static void main(String[] args) { Cartoon x = new CartoonO;

}

} /* Output; Конструктор Art Конструктор Drawing Конструктор Cartoon * ///:-

Как видите, конструирование начинается с «самого внутреннего» базового класса, поэтому базовый класс инициализируется еще до того, как он станет доступным для конструктора производного класса. Даже если конструктор класса Cartoon не определен, компилятор сгенерирует конструктор по умолча­нию, в котором также вызывается конструктор базового класса.

Конструкторы с аргументами

В предыдущем примере использовались конструкторы по умолчанию, то есть конструкторы без аргументов. У компилятора не возникает проблем с вызовом таких конструкторов, так как вопросов о передаче аргументов не возникает. Если класс не имеет конструктора по умолчанию или вам понадобится вызвать конструктор базового класса с аргументами, этот вызов придется оформить явно, с указанием ключевого слова super и передачей аргументов:

//: reusing/Chess.java

// Наследование, конструкторы и аргументы.

import static net.mindview.util.Print.*;

class Game {

Game(int i) {

print("Конструктор Game"),

}

}

class BoardGame extends Game { BoardGame(int i) { super(i);

print("Конструктор BoardGame");

}

}

public class Chess extends BoardGame { Chess О {

super(ll);

print("Конструктор Chess");

}

public static void main(String[] args) { Chess x = new ChessO:

}

} /* Output- Конструктор Game Конструктор BoardGame Конструктор Chess *///:-

Если не вызвать конструктор базового класса в BoardGame(), то компилятор «пожалуется» на то, что не может обнаружить конструктор в форме Game(). Вдобавок вызов конструктора базового класса должен быть первой командой в конструкторе производного класса. (Если вы вдруг забудете об этом, компи­лятор вам тут же напомнит.)

Делегирование

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

//. reusing/SpaceShipControls.java

public class SpaceShipControls { void up(int velocity) {} void down(int velocity) {} void left(int velocity) {} void right(int velocity) {} void forward(int velocity) {} void back(int velocity) {} void turboBoostO {} } ///-

Для построения космического корабля можно воспользоваться наследова­нием:

// reusing/SpaceShip java

public class SpaceShip extends S^ceShipControls { private String name.

public SpaceShip(String name) { this.name = name, }

public String toStringO { return name. }_______

public static void main(String[] args) {

SpaceShip protector = new SpaceShipC'NSEA Protector"), protector forward(lOO).

}

} /// ~

Однако космический корабль не может рассматриваться как частный случай своего управляющего модуля — несмотря на то, что ему, к примеру, можно при­казать двигаться вперед (forward()). Точнее сказать, что SpaceShipсодержитSpaceShipControls, и в то же время все методы последнего предоставляются клас­сом SpaceShip. Проблема решается при помощи делегирования:

// reusing/SpaceShipDelegation java

public class SpaceShipDelegation { private String name, private SpaceShipControls controls =

new SpaceShipControlsO: public SpaceShipDelegation(String name) {

this name = name. }

// Делегированные методы: public void back(int velocity) { controls.back(velocity);

}

public void down(int velocity) { controls.down(velocity);

}

public void forward(int velocity) { controls forward(velocity).

}

public void leftCint velocity) { controls left(velocity).

}

public void rightOnt velocity) { controls right(velocity);

}

public void turboBoostO {

controls.turboBoostO.

}

public void up(int velocity) { controls.up(velocity):

}

public static void main(String[] args) { SpaceShipDelegation protector =

new SpaceShipDelegationC'NSEA Protector");продолжение &

protector.forwarcK 100);

}

} ///:-

Как видите, вызовы методов переадресуются встроенному объекту controls, а интерфейс остается таким же, как и при наследовании. С другой стороны, деле­гирование позволяет лучше управлять происходящим, потому что вы можете ограничиться небольшим подмножеством методов встроенного объекта.

Хотя делегирование не поддерживается языком Java, его поддержка присут­ствует во многих средах разработки. Например, приведенный пример был авто­матически сгенерирован в JetBrains Idea IDE.

Сочетание композиции и наследования

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

II: reusing/PlaceSetting.java 11 Совмещение композиции и наследования, import static net.mindview.util.Print.*;

class Plate {

'PlateCint i) {

print("Конструктор Plate");

}

}

class DinnerPlate*extends Plate { DinnerPlate(int i) { super(i),

print("Конструктор DinnerPlate");

class Utensil {

Utensil(int i) {

print("Конструктор Utensil");

}

}

class Spoon extends Utensil { Spoon(int i) {

super(i);