Много често композицията и наследяването се използват заедно. Следният пример показва създаване на по-сложен клас чрез използване на наследяване и композиция, заедно с необходимата инициализация в конструкторите:
//: c06:PlaceSetting.java
// Combining composition & inheritance
class Plate {
Plate(int i) {
System.out.println("Plate constructor");
}
}
class DinnerPlate extends Plate {
DinnerPlate(int i) {
super(i);
System.out.println(
"DinnerPlate constructor");
}
}
class Utensil {
Utensil(int i) {
System.out.println("Utensil constructor");
}
}
class Spoon extends Utensil {
Spoon(int i) {
super(i);
System.out.println("Spoon constructor");
}
}
class Fork extends Utensil {
Fork(int i) {
super(i);
System.out.println("Fork constructor");
}
}
class Knife extends Utensil {
Knife(int i) {
super(i);
System.out.println("Knife constructor");
}
}
// A cultural way of doing something:
class Custom {
Custom(int i) {
System.out.println("Custom constructor");
}
}
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 constructor");
}
public static void main(String[] args) {
PlaceSetting x = new PlaceSetting(9);
}
} ///:~
Докато компилаторът ви кара да инициализирате базовите класове и иска това да стане в самото начало на конструктора, той не ви следи дали инициализирате член-обектите, така че трябва да помните и да внимавате за това.
Гарантиране на провилно почистване
Java няма концепцията на C++ за деструктор, метод който автоматично се вика когато обектът се разрушава. Причината вероятно е че Java практиката е просто да забравим за обектите наместо да ги разрушаваме, позволявайки на боклучаря да освободи паметта когато е необходимо.
Често това е добре, но има случаи когато даден клас може да има активности, които после да изискват почистване на нещо. Както се спомена в глава 4, не се знае кога боклучарят ще се активира и дали въобще ще се активира. Така че ако искате нещо да се почиства, трябва да напишете специален метод който да прави това и да осигурите използването му от страна на клиент-програмиста. Отгоре на това, както е описано в глава 9 (exception handling), трябва да се предпазите от изключения слагайки го във finally клаузата.
Да вземем пример с CAD система която чертае нещо на екран:
//: c06:CADSystem.java
// Ensuring proper cleanup
import java.util.*;
class Shape {
Shape(int i) {
System.out.println("Shape constructor");
}
void cleanup() {
System.out.println("Shape cleanup");
}
}
class Circle extends Shape {
Circle(int i) {
super(i);
System.out.println("Drawing a Circle");
}
void cleanup() {
System.out.println("Erasing a Circle");
super.cleanup();
}
}
class Triangle extends Shape {
Triangle(int i) {
super(i);
System.out.println("Drawing a Triangle");
}
void cleanup() {
System.out.println("Erasing a Triangle");
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("Drawing a Line: " +
start + ", " + end);
}
void cleanup() {
System.out.println("Erasing a Line: " +
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("Combined constructor");
}
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 {
// Code and exception handling...
} finally {
x.cleanup();
}
}
} ///:~
Всичко в тази система е някакъв вид Shape (което самото е вид Object понеже неявно е наследено от него клас). Всеки клас предефинира cleanup( ) на Shape в добавка на това че вика същия метод на базовия клас чрез super. Специфичните Shape класове Circle, Triangle и Line всички имат конструктори които “чертаят,” а и всеки метод извикан по време на живота на програмата може да бъде подозиран че прави нещо, което иска почистване после. Всеки клас има свой собствен cleanup( ) метод за реставриране на нещата, които не са памет, до тяхното състояние преди създаването на обекта.
В main( ) може да се видят две нови ключови думи, които няма официално да се въвеждат до глава 9: try и finally. Ключовата дума try показва че блокът който следва (отделен с фигурни скоби) е пазен регион, което значи че се третира специално. Една част от това специално третиране е че finally клаузата в този регион винаги се изпълнява, без значение как завършва try блокът. (С изключенията е вазможно try да завърши по необичайни начини.) Тук finally клаузата казва “винаги викай cleanup( ) за x, без значение какво се случва.” Тези ключови думи са обяснени напълно в глава 9.
Забележете, че вашият почистващ метод трябва да се грижи за реда на извикванията, ако подобектът зависи от друг обект. Изобщо ще следвате формата приета в C++ за деструкторите: Първо се извършва всичко специфично за вашия клас (което може да изисква елементите на базовия клас да са още жизнеспособни) и тогава да се извика почистващия метод на базовия клас, както е в примера.
Може да има много случаи, когато почистването не стои като проблем; просто оставяте боклучарят да си свърши работата. Но когато трябва да се намесите вие, прилежание и старание са необходими.
Ред при почистването на боклука
Не е много това, на което може да се разчита, що се отнася до събирането на боклука. Боклучарят може никога да не се извика. Ако сработи, може да чисти обектите във всякакъв ред както си иска. В добавка реализациите на боклучаря в Java 1.0 често не викат finalize( ) методите. Най-добре е да не се разчита за нищо друго на боклучаря освен за освобождаването на паметта. Ако искате да правите почистване, направете си собствени методи и не разчитайте на finalize( ). (Както се спомена по-рано Java 1.1 може да бъде заставен да вика всичките финализатори.)
Скриване на имената
Само C++ програмистите може да се изненадат от скриването на имената, понеже то работи различно в него език. Ако Java базов клас има име на метод което е претоварвано няклко пъти, повторното дефиниране на това име в извлечен клас не скрива никоя от тези версии в базовия клас. Така претоварването работи независимо дали е станало сега или по-рано:
//: c06:Hide.java
// Overloading a base-class method name
// in a derived class does not hide the
// base-class versions
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) used
b.doh('x');
b.doh(1.0f);
b.doh(new Milhouse());
}
} ///:~
Както ще видите в седващата глава, много по-често се подтискат методи чрез използване на точно същото име и сигнатура, както в базовия клас. Това може да бъде смущаващо все пак (понеже C++ го забранява за да не позволи да направите нещо, което вероятно е грешка).
Сподели с приятели: |