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



страница38/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   34   35   36   37   38   39   40   41   ...   73

Особеността


Трудността с Music.java може да се види чрез пускане на програмата. Изходът е Wind.play( ). Това е точно желаният изход, но не изглежда да има смисъл да ра­боти по този начин. Погледнете tune( ) метода:

public static void tune(Instrument i) {

// ...

i.play(Note.middleC);



}

Той приема Instrument манипулатор. Така че как е възможно компилаторът да знае че Instrument манипулаторът сочи Wind в този случай а не Brass или Stringed? Компилаторът не може да знае. За да се постигне по-дълбоко раз­би­ра­не на въпроса първо трябва да погледнем отблизо свързването.


Свързване при извикването на метод


Свързването на извикване на метод с тялото на метода се нарича binding. Ко­га­то то се направи преди програмата да е стартирана (от компилатора и линкера, ако има такъв), това се нарича ранно свързване. Може да не сте чували термина пре­ди понеже той никога не е съществувал като алтернатива в процедурното про­грамиране. C компилаторите имат само един начин за викане на функции и то­ва е ранното свързване.

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

Решението се нарича късно свързване, което значи че свързването става по вре­ме на изпълнение и е основано на типа на обекта. Късното свързване също се на­рича динамично свързване или свързване по време на изпълнение. Когато в ези­ка има късно свързване, трябва да има и съответен механизъм за раз­поз­на­ва­не на типа и викане на подходящия метод по време на изпълнение. Тоест ком­пи­латорът пак не знае типа на обекта, но механизмът на извикването на методи го намира и скача в необходимото тяло на метод. Механизмът на късното свърз­ване се мени от език на език, но е лесно да се съобрази, че някакъв вид за­пис на информация в обектите би трябвало да съществува.

В Java се използва късно свързване освен ако методът е деклариран като final. То­ва значи че обикновено не трябва да се замисляте за типа на свързването – то ста­ва автоматично.

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

Постигане на точното поведение


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

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

Примерът има базов клас наречен Shape и различни извлечени типове: Circle, Square, Triangle и т.н. Причината примерът да е толкова подходящ е че е лесно да се каже “кръгът е вид форма” и това да бъде разбрано. Диаграмата на наследяването показва зависимостите:

(липсва тук - б.пр.)




Ъпкастингът може да се наложи в ред простичъък като този:

Shape s = new Circle();

Тук Circle обект се създава и манипулаторът непосредствено се присвоява на Shape, което би изглеждало грешка (присвояване един тип на друг) и все пак всич­ко е наред понеже Circle е Shape по наследство. Така че компилаторът се съ­гласява с операторите и не издава съобщение за грешка.

Когато викате един от методите на базовия клас (които са били подтиснати в идвлечения клас):

s.draw();

отново бихте могли да очаквате че draw( ) на Shape се вика понеже това е, най-сет­не, манипулатор на Shape токо че откъде да знае компилаторът да прави не­що друго? И пак правилният (метод-б.пр.) Circle.draw( ) се вика поради къс­но­то свързване (полиморфизма).

Следващия пример показва това по малко различен начин:

//: c07:Shapes.java

// Polymorphism in Java
class Shape {

void draw() {}

void erase() {}

}
class Circle extends Shape {

void draw() {

System.out.println("Circle.draw()");

}

void erase() {



System.out.println("Circle.erase()");

}

}


class Square extends Shape {

void draw() {

System.out.println("Square.draw()");

}

void erase() {



System.out.println("Square.erase()");

}

}


class Triangle extends Shape {

void draw() {

System.out.println("Triangle.draw()");

}

void erase() {



System.out.println("Triangle.erase()");

}

}


public class Shapes {

public static Shape randShape() {

switch((int)(Math.random() * 3)) {

default: // To quiet the compiler

case 0: return new Circle();

case 1: return new Square();

case 2: return new Triangle();

}

}



public static void main(String[] args) {

Shape[] s = new Shape[9];

// Fill up the array with shapes:

for(int i = 0; i < s.length; i++)

s[i] = randShape();

// Make polymorphic method calls:

for(int i = 0; i < s.length; i++)

s[i].draw();

}

} ///:~


Базовият клас Shape основава общ интерфейс за всичко, което е наследено от Shape – тоест всички форми могат да бъдат чертани и трити. Извлечените кла­со­ве подтискат тези дефиниции за да може да се осигури специфично необ­хо­ди­мото поведение за всяка форма.

Главният клас Shapes съдържа static метод randShape( ) който произвежда ма­ни­пулатор към случайно избран Shape обект всеки път когато го викате. За­бе­ле­жете че ъпкастинг става с всеки от return операторите, който взима ма­ни­пу­ла­тор Circle, Square, или Triangle и го изпраща извън метода като връщан тип, Shape. Така че когато и да извикате този метод, никога нямате шанс да видите ка­къв точно тип е, понеже получавате обратно просто манипулатор Shape.



main( ) съдържа масив от Shape манипулатори запълнен чрез извиквания на randShape( ). В този момент вие знаете че имате Shapes (Форми -б.пр.), но не знае­те нищо по специфично от това (а също и компилаторът не знае). Обаче ка­то се движите през масива и викате draw( ) за всеки един, коректното спе­ци­фич­но за типа поведение магически се проявява, както може да видите от при­ме­ра на изхода:

Circle.draw()

Triangle.draw()

Circle.draw()

Circle.draw()

Circle.draw()

Square.draw()

Triangle.draw()

Square.draw()

Square.draw()

Разбира се тъй като формите са случайно избирани всеки път, изходът от ва­ши­те пускания все ще е различен. Доводът да се избере случайно избиране е да се по­каже, че компилаторът няма никакви допълнителни знания, които биха му по­могнали предварително да вземе решение. Всичките викания на draw( ) са чрез динамично свързване.

Разширяемост


Сега нека да се върнем към примера с музикалния инструмент. Поради по­ли­мор­физма може да добавяте колкото си искате типове без да изменяте метода tune( ). В добре проектирана ОО програма повечето или всичките ви методи ще след­ват примера на tune( ) и ще комуникират само с интерфейса на базовия клас. Такава програма е разширяема понеже може да се добавя функ­цио­нал­ност чрез наследяване но абекти от базовия клас. Методите които манипулират ин­терфейса на базовия клас не е необходимо въобще да се пипат за да работят и с новите класове.

Да видим какво ще стане ако вземем примера с инструментите и добавим нови методи към базовия клас и няколко нови класа. Ето диаграмата:




Всички нови класове работят перфектно със стария, непроменен tune( ) метод. Да­же ако tune( ) е в отделен файл и нови методи са добавени в интерфейса на Instrument, tune( ) работи коректно без рекомпилация. Ето реализацията на гор­ната програма:

//: c07:Music3.java

// An extensible program

import java.util.*;


class Instrument3 {

public void play() {

System.out.println("Instrument3.play()");

}

public String what() {



return "Instrument3";

}

public void adjust() {}



}
class Wind3 extends Instrument3 {

public void play() {

System.out.println("Wind3.play()");

}

public String what() { return "Wind3"; }



public void adjust() {}

}
class Percussion3 extends Instrument3 {

public void play() {

System.out.println("Percussion3.play()");

}

public String what() { return "Percussion3"; }



public void adjust() {}

}
class Stringed3 extends Instrument3 {

public void play() {

System.out.println("Stringed3.play()");

}

public String what() { return "Stringed3"; }



public void adjust() {}

}
class Brass3 extends Wind3 {

public void play() {

System.out.println("Brass3.play()");

}

public void adjust() {



System.out.println("Brass3.adjust()");

}

}


class Woodwind3 extends Wind3 {

public void play() {

System.out.println("Woodwind3.play()");

}

public String what() { return "Woodwind3"; }



}
public class Music3 {

// Doesn't care about type, so new types

// added to the system still work right:

static void tune(Instrument3 i) {

// ...

i.play();



}

static void tuneAll(Instrument3[] e) {

for(int i = 0; i < e.length; i++)

tune(e[i]);

}

public static void main(String[] args) {



Instrument3[] orchestra = new Instrument3[5];

int i = 0;

// Upcasting during addition to the array:

orchestra[i++] = new Wind3();

orchestra[i++] = new Percussion3();

orchestra[i++] = new Stringed3();

orchestra[i++] = new Brass3();

orchestra[i++] = new Woodwind3();

tuneAll(orchestra);

}

} ///:~



Новите методи са what( ), който връща String манипулатор с описание на класа и adjust( ), който дава някакъв начин за нагласяване на всеки инструмент.

В main( ) когато слагате нещо вътре в Instrument3 масива автоматично става ъп­кастинг към Instrument3.

Може да видите че метода tune( ) е блажено невеж относно промените на кода ста­нали около него, и все пак работи коректно. Това е точно нещото, което се очак­ва да даде полиморфизмът. Промените във вашия код не засягат частите на про­грамата, които не трябва да се засягат. Казано с други думи полиморфизмът е най-важния инструмент който помага на програмиста да “отдели нещата кои­то се променят от тези които ще останат така.”




Сподели с приятели:
1   ...   34   35   36   37   38   39   40   41   ...   73




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

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