Книга е още в много ранна фаза на написване


Синтаксис на композицията



страница32/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   28   29   30   31   32   33   34   35   ...   73

Синтаксис на композицията


Композиция беше изпозвана доста често до този момент. Просто се слагат ма­ни­пулатори на обекти в новия клас. Например нека искаме обект, който съ­дър­жа няколко String обекта, двойка примитиви и обект от друг клас. За не­при­ми­тив­ните обекти само слагаме манипулатора в новия клас, а за примитивите про­сто ги декларираме в новия клас: (Виж стр. 89 ако има проблеми с пускането на та­зи програма.)

//: c06:SprinklerSystem.java

// Composition for code reuse

package c06;


class WaterSource {

private String s;

WaterSource() {

System.out.println("WaterSource()");

s = new String("Constructed");

}

public String toString() { return s; }



}
public class SprinklerSystem {

private String valve1, valve2, valve3, valve4;

WaterSource source;

int i;


float f;

void print() {

System.out.println("valve1 = " + valve1);

System.out.println("valve2 = " + valve2);

System.out.println("valve3 = " + valve3);

System.out.println("valve4 = " + valve4);

System.out.println("i = " + i);

System.out.println("f = " + f);

System.out.println("source = " + source);

}

public static void main(String[] args) {



SprinklerSystem x = new SprinklerSystem();

x.print();

}

} ///:~


Един от методите определени в WaterSource е специален: toString( ). Ще на­у­чи­те по-късно в тази глава, че всеки непримитивен обект има toString( ) метод и той се вика в специални ситуации когато компилаторът иска String но работи с един от тези обекти. Така в израза:

System.out.println("source = " + source);

Компилаторът вижда че се опитваме да добавим String обект (“source = “) към WaterSource. Това е безсмислено за него, понеже може само да се “събира” String с друг стринг String, така че казва “Ще превърна source в String чрез из­вик­ване на toString( )!” След като свърши това той може да комбинира двата Stringа и да прати резултата String на System.out.println( ). Всеки път когато ис­кате да осигурите такова поведение за писан от вас клас трябва само да на­пи­ше­те toString( ) метод.

На пръв поглед би могло да се предположи че компилаторът автоматично ще кон­струира обекти за всеки от манипулаторите в горния код, например из­вик­вай­ки конструктор по подразбиране за WaterSource за да инициализира source. Изходът е в действителност:

valve1 = null

valve2 = null

valve3 = null

valve4 = null

i = 0

f = 0.0


source = null

Примитивите които са полета в клас автоматично са инициализирани с нула, как­то отбелязахме в глава 2. Но манипулаторите на обекти се инициализират с null и ако се опитате да извикате метод на кийто и да е от тях ще получите из­клю­чение. Много добре е наистина (и полезно) че все пак може да ги из­пе­ча­та­те без изхвърляне на изключение.

Смисълът да не се създават обекти за всеки манипулатор веднага е че това че­сто би било напразно. Ако искате да се инициализират манипулаторите, може да го направите:


  1. В точката където се декларира обектът. Това значи че винаги ще бъдат инициализирани преди да се извика конструкторът на обекта.

  2. В конструктора на съответния клас

  3. Непосредствено преди употребата на съответния обект. Това може да на­ма­ли ненужната работа в случаи, когато обектът може и да не се наложи да бъде създаден.

Всичките три подхода са показани тук:

//: c06:Bath.java

// Constructor initialization with composition
class Soap {

private String s;

Soap() {

System.out.println("Soap()");

s = new String("Constructed");

}

public String toString() { return s; }



}
public class Bath {

private String

// Initializing at point of definition:

s1 = new String("Happy"),

s2 = "Happy",

s3, s4;


Soap castille;

int i;


float toy;

Bath() {


System.out.println("Inside Bath()");

s3 = new String("Joy");

i = 47;

toy = 3.14f;



castille = new Soap();

}

void print() {



// Delayed initialization:

if(s4 == null)

s4 = new String("Joy");

System.out.println("s1 = " + s1);

System.out.println("s2 = " + s2);

System.out.println("s3 = " + s3);

System.out.println("s4 = " + s4);

System.out.println("i = " + i);

System.out.println("toy = " + toy);

System.out.println("castille = " + castille);

}

public static void main(String[] args) {



Bath b = new Bath();

b.print();

}

} ///:~


Забележете че в Bath се изпълнява оператор преди всички инициализации да са на­правени. Когато не инициализирате при декларацията, няма гаранция, че ще из­вършите инициализация преди да подадете съобщение към обектовия ма­ни­пу­латор – освен за неизбежното изключение по време на изпълнение.

Ето изходът от програмата:

Inside Bath()

Soap()


s1 = Happy

s2 = Happy

s3 = Joy

s4 = Joy


i = 47

toy = 3.14

castille = Constructed

Когато print( ) се извика той попълва s4 така че всички полета са правилно ини­циа­лизирани преди да са използвани.


Синтаксис за наследяването


Наследяването е толкова интегрална част от Java (и ООП езици изобщо), че бе­ше въведена в глава 1 и биваше използвана на места преди тази глава в си­туа­ции където е необхадима. Освен това винаги се прави наследяване, когато се съз­дава клас, понеже ако и да не се пише нищо специално се наследява стан­дарт­ния коренен клас Object на Java.

Синтаксисът за композиция е очевиден, но за наследяване е различно. Когато се наследява, казваме “Този нов клас прилича на оня стар клас.” Това се изрича в програмата чрез даване на име на новия клас както обикновено, но преди от­ва­рящата скоба на тялото на класа се слага ключовата дума extends следвана от име­то на базовия клас. Като направите така автоматично взимате всички чле­но­ве-данни и методи на базовия клас. Ето пример:

//: c06:Detergent.java

// Inheritance syntax & properties


class Cleanser {

private String s = new String("Cleanser");

public void append(String a) { s += a; }

public void dilute() { append(" dilute()"); }

public void apply() { append(" apply()"); }

public void scrub() { append(" scrub()"); }

public void print() { System.out.println(s); }

public static void main(String[] args) {

Cleanser x = new Cleanser();

x.dilute(); x.apply(); x.scrub();

x.print();

}

}


public class Detergent extends Cleanser {

// Change a method:

public void scrub() {

append(" Detergent.scrub()");

super.scrub(); // Call base-class version

}

// Add methods to the interface:



public void foam() { append(" foam()"); }

// Test the new class:

public static void main(String[] args) {

Detergent x = new Detergent();

x.dilute();

x.apply();

x.scrub();

x.foam();

x.print();

System.out.println("Testing base class:");

Cleanser.main(args);

}

} ///:~



Това демонстрира няколко неща. Първо, в Cleanser append( ) метода Stringовете се конкатенират в s чрез използване на += оператор, който е един от операторите (заедно с ‘+’) които проектантите на Java “претовариха” за да ра­ботят със Stringове.

Второ, и Cleanser и Detergent съдържат main( ) метод. Може да създадете main( ) за всеки от вашите класове и често се препоръчва да се програмира по то­зи начин за да се тества кодът затворен в класа. Даже и да имате много кла­со­ве в програмата само main( ) за public класа извикан в командния ред на про­гра­мата ще бъде извикан. (И може да имате само един public клас на файл.) Та­ка в този случай като напишем java Detergent, Detergent.main( ) ще бъде из­ви­ка­но. Но може също да се напише java Cleanser за да се извика Cleanser.main( ), въпреки че Cleanser не е public клас. Тази техника на слагане на main( ) във всеки клас позволява лесно тестване на единиците във всеки клас. И не е необходимо да махате main( ) когато приключите тестването; може да го оставите за бъдещо тестване.

Може да видите как Detergent.main( ) вика Cleanser.main( ) явно.

Важно е че всички методи в Cleanser са public. Помнете че ако не сложите ни­ка­къв спецификатор класът става “приятелски,” което позволява достъп само до членовете в пакета. Така в същия пакет всеки би могъл да използва тези ме­то­ди, ако нямат спецификатор на достъп. Detergent не би имал проблеми, на­при­мер. Обаче ако клас от друг пакет наследи Cleanser той би имал достъп са­мо до public членовете. Така че при планиране на наследяването правете всич­ки членове private и всички методи public. (protected членовете също дават до­стъп на извлечения клас; ще изучите това по-късно.) Разбира се, за конкретен клас ще се постъпи конкретно, тук даваме едн полезна насока.

Забележете че Cleanser има множество методи в интерфейса си: append( ), dilute( ), apply( ), scrub( ) и print( ). Понеже Detergent е извлечен от Cleanser (чрез ключовата дума extends) той автоматично взима всички тези методи в своя интерфейс, даже и да не ги виждате явно декларирани в Detergent. При то­ва положение може да мислите за наследяването като за повторно използване на интерфейса. (Реализацията я имате безплатно, но тази част не е по-важната.)

Както се вижда в scrub( ), възможно е да се вземе метод от базовия клас и да се мо­дифицира. В този случай може да поискате да използвате версията от ба­зо­вия клас. Но в scrub( ) не може просто да извикате scrub( ), тъъй като това би произ­вело рекурсивно извикване, което не е желаното от вас. За да реши този про­блем Java има ключовата дума super която идва от “superclass” което е кла­сът от който е извлечен въпросният клас. Така изразът super.scrub( ) извиква ме­тода scrub( ) във версията на базовия клас.

Когато наследявате не сте ограничени до методите на базовия клас. Може също да добавите нови методи в новия клас точно както се прави това в клас: просто ги дефинирате. Ключовата дума extends предполага че ще добавите нови ме­то­ди в интерфейса на класа и методът foam( ) е пример за това.

В Detergent.main( ) може да видите че за Detergent може да викате всички ме­то­ди достъпни в Cleanser както и в Detergent (т.е.. foam( )).


Инициализиране на базовия клас


Тъй като сега участват два класа – базовия клас и извлечения клас – вместо само един, може да е малко смущаващо ако се опитаме да си представим обект, съз­даден от извлечения клас. Погледнато отвън може да изглежда, че новият ме­тод има интерфейса на стария и евентуално нови методи. Но наследяването не просто копира интерфеса на стария клас. Когато създавате обект от из­вле­че­ния клас той съдържа в себе си подобект от базовия клас. Този обект е същият как­то ако го създадете от самия базов клас. Погледнато отвън това е точно ба­зо­вият клас да бъде обгърнат от извлечения.

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

//: c06:Cartoon.java

// Constructor calls during inheritance


class Art {

Art() {


System.out.println("Art constructor");

}

}


class Drawing extends Art {

Drawing() {

System.out.println("Drawing constructor");

}

}


public class Cartoon extends Drawing {

Cartoon() {

System.out.println("Cartoon constructor");

}

public static void main(String[] args) {



Cartoon x = new Cartoon();

}

} ///:~



Изходът от тази програма показва автоматичните извиквания:

Art constructor

Drawing constructor

Cartoon constructor

Може да се види, че конструирането става “външно”, за базовия клас, така че ба­зовият клас се инициализира преди конструкторът на извлечения клас да има до­стъп до него.

Даже ако не създадете конструктор за Cartoon( ), компилаторът ще синтезира сам конструктор по подразбиране, който ще вика конструктора на базовия клас.


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


Горният пример има конструктори по подразбиране; тоест те нямат никакви ар­гу­менти. Лесно е за компилатора да вика такива конструктори, понеже не стои въпросът какви аргументи да се дадат. Ако вашият канструктор има ар­гу­мен­ти или искате да извикате конструктора на базовия клас с аргументи, из­полз­вате ключовата дума super и съответния списък аргументи:

//: c06:Chess.java

// Наследяване, конструктори и аргументи
class Game {

Game(int i) {

System.out.println("Game constructor");

}

}


class BoardGame extends Game {

BoardGame(int i) {

super(i);

System.out.println("BoardGame constructor");

}

}
public class Chess extends BoardGame {



Chess() {

super(11);

System.out.println("Chess constructor");

}

public static void main(String[] args) {



Chess x = new Chess();

}

} ///:~



Ако не викате конструктора на базовия клас в BoardGame( ), компилаторът ще се оплаква че не може да намери конструктор за Game( ). В добавка из­вик­ва­не­то на конструктора на базовия клас трябва да бъде първото нещо, което се пра­ви в конструктора на извлечения клас. (Компилаторът ще ви напомни ако не го направите както трябва.)

Хващане на изключенията на базовия канструктор


Както току-що беше отбелязано, компилаторът ви принуждава да сложите из­вик­ването на конструктора на базовия клас като първо нещо в тялото на кон­струк­тора. Това значи че нищо друго не може да се появи преди него. Както ще ви­дите в глава 9, това също предотвратява прихващането на изключение из­хвър­лено от базовия клас от извлечения клас. Това може да бъде неудобно в ня­кои случаи.




Сподели с приятели:
1   ...   28   29   30   31   32   33   34   35   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница