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


Подтискане vs. претоварване



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

Подтискане vs. претоварване


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

//: c07:WindError.java

// Accidentally changing the interface
class NoteX {

public static final int

MIDDLE_C = 0, C_SHARP = 1, C_FLAT = 2;

}
class InstrumentX {

public void play(int NoteX) {

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

}

}
class WindX extends InstrumentX {



// OOPS! Changes the method interface:

public void play(NoteX n) {

System.out.println("WindX.play(NoteX n)");

}

}


public class WindError {

public static void tune(InstrumentX i) {

// ...

i.play(NoteX.MIDDLE_C);



}

public static void main(String[] args) {

WindX flute = new WindX();

tune(flute); // Not the desired behavior!

}

} ///:~


Тук изпъква и друг смущаващ аспект. В InstrumentX методът play( ) взема int с иден­тификатор NoteX. Тоест въпреки че NoteX е име на клас, то също може да се използува като идентификатор без оплаквания. Но в WindX play( ) взима NoteX манипулатор който има за идентификатор n. (Въпреки че може даже да се напише play(NoteX NoteX) без грешка.) По този начин сякаш програмистът е въз­намерявал да подтисне play( ) но е направил малка печатна грешка. Ком­пи­ла­торът, обаче, е предположил че претоварване наместо подтискане е било на­ме­рението. Забележете, че ако следвате стандартната за имената в Java кон­вен­ция идентификаторът на аргумента би бил noteX, което би го отличавало от име на клас.

В tune InstrumentX i е изпратеното от play( ) съобщение с един от членовете на NoteX (MIDDLE_C) като аргумент. Тъй като NoteX съдържа int дефиниции, то­ва значи че int версията на сега претоварения play( ) метод се вика, а понеже той не е подтиснат се използва версията му в базовия клас.

Изходът е:

InstrumentX.play()

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

Абстрактни класове и методи


Във всичките примери с инструменти методите на базовия клас Instrument бя­ха винаги “празни” методи. Щеше да е нередно, грешно, ако тези методи бяха ня­кога викани. Така е защото целта на Instrument е да създаде общ интерфейс за всичките класове извлечени от него.

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

Ако имате абстрактен клас като Instrument, обектите които бихте създали от не­го почти винаги нямат смисъл. Тоест Instrument е само за да изрази ин­тер­фей­са, а не канкретна реализация, така че създаване на обекти от Instrument ня­ма смисъл и вероятно вие ще искате да пресотвратите създаването им от по­тре­бителя. Това може да се изпълни като се направят всичките методи на Instrument да извеждат съобщения за грешка, но това забавя обратната връзка чак до времето на изпълнение и изисква изтощително тестване от страна на по­тре­бителя (на класа - бел.пр.). Винаги е по-добре да се хващат проблемите още по време на компилация.

Java има механизъм за случая наречен абстрактен метод. Това е незавършен ме­тод; само декларация без тяло на метода. Ето синтаксисът на декларацията на абстрактен метод:

abstract void X();

Клас съдържащ абстрактни методи се нарича абстрактен клас. Ако класът съ­дър­жа един или повече абстрактни методи той задължително трабва да бъде ква­лифициран с abstract. (Иначе компилаторът ще издаде съобщение за гре­шка.)

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

Ако наследите от абстрактен клас и искате да се създават обекти от новия тип, тряб­ва да напишете методи които подтискат всички абстрактни методи. Ако не го направите (и може да изберете този вариант) извлеченият клас също е аб­страк­тен и компилаторът ще ви застави да квалифицирате този клас с клю­чо­ва­та дума abstract.

Възможно е да се декларира клас с abstract без да се включват abstract методи. То­ва е полезно когато не искате да има abstract методи и все пак искате да пред­отвратите създаване на обекти от него клас.

Класът Instrument лесно може да се превърне в абстрактен. Само някои от методите ще бъдат абстрактни, понеже не се изисква всичките да бъдат. Ето как изглежда:




Ето оркестърският пример променен да използва abstract класове и методи:

//: c07:Music4.java

// Abstract classes and methods

import java.util.*;


abstract class Instrument4 {

int i; // storage allocated for each

public abstract void play();

public String what() {

return "Instrument4";

}

public abstract void adjust();



}
class Wind4 extends Instrument4 {

public void play() {

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

}

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



public void adjust() {}

}
class Percussion4 extends Instrument4 {

public void play() {

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

}

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



public void adjust() {}

}
class Stringed4 extends Instrument4 {

public void play() {

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

}

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



public void adjust() {}

}
class Brass4 extends Wind4 {

public void play() {

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

}

public void adjust() {



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

}

}


class Woodwind4 extends Wind4 {

public void play() {

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

}

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



}
public class Music4 {

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

// added to the system still work right:

static void tune(Instrument4 i) {

// ...

i.play();



}

static void tuneAll(Instrument4[] e) {

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

tune(e[i]);

}

public static void main(String[] args) {



Instrument4[] orchestra = new Instrument4[5];

int i = 0;

// Upcasting during addition to the array:

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

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

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

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

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

tuneAll(orchestra);

}

} ///:~



Може да видите че няма промени освен в базовия клас.

Полезно е да се създават abstract класове и методи понеже те правят явна аб­стракт­ността на класа и казват и на потребителя и на компилатора какво се це­ли.





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




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

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