Полиморфизмът е третата основна черта на един ООП език, след абстракцията на данните и наследяването.
Той дава друго измерение на разделянето на интерфейса от реализацията, разделя какво от как. Полиморфизмът позволява подобрена организация и четимост на кода както и разширяеми програми, които могат да бъдат “разраснати” не само при първоначалното създаване на проекта, но и когато нови черти станат желани.
Капсулирането създава нови даннови типове чрез комбиниране на характеристики и поведение. Скриването на реализацията разделя реализацията от интерфейса чрез правенето на детайлите private. Този вид механична организация има очевиден смисъл за всеки, който се е занимавал с процедурно програмиране. Но полиморфизмът се занимава с разделяне в термините на типове. В предната глава видяхте, че наследяването прави възможно третирането на обект като неговия си тип или типа на базовия клас. Тази възможност е критично важна понеже позволява много типове (извлечени от един базов тип) да се третират като че са от един тип и един и същ отрязък код да работи различно в различните случаи. Полиморфното извикване на методи позволява да се изрази разликата от друг, подобен тип, доколкото и двата са извлечени от един и същ базов тип. Това различаване се изразява чрез разликите в поведението на методите, които може да извикате чрез базовия клас.
В тази глава ще учите за полиморфизма (също наричан динамично свързване или късно свързване или свързване по време на изпълнение) започвайки от основното, с примери които махат от погледа всичко освен полиморфното поведение.
Upcasting
В глава 6 видяхме как обект може да се използва с неговия тип или с типа на базовия клас. Вземането на обектов манипулатор и третирането му като манипулатор от типа на базовия клас се нарича upcasting поради начина на изобразяване на дърветата на наследяването на хартия.
Видяхме също и възникващ проблем, който съществува и в следната програма: (Виж страница 89 ако има проблеми с пускането на програмата.)
//: c07:Music.java
// Inheritance & upcasting
package c07;
class Note {
private int value;
private Note(int val) { value = val; }
public static final Note
middleC = new Note(0),
cSharp = new Note(1),
cFlat = new Note(2);
} // Etc.
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
// Wind objects are instruments
// because they have the same interface:
class Wind extends Instrument {
// Redefine interface method:
public void play(Note n) {
System.out.println("Wind.play()");
}
}
public class Music {
public static void tune(Instrument i) {
// ...
i.play(Note.middleC);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute); // Upcasting
}
} ///:~
Методът Music.tune( ) приема Instrument манипулатор, но също и каквото и да е извлечено от Instrument. В main( ) може да се види това да става с Wind манипулатора даден на tune( ), без да е необходим каст. Това е приемливо; интерфейсът в Instrument трябва да съществува в Wind, понеже Wind е наследен от Instrument. Ъпкастингът от Wind към Instrument може да “стесни” този интерфейс, но не може да го направи по-малък от интерфейса на Instrument.
Защо ъпкастинг?
Тази програма може да ви изглежда странна. Защо трябва нарочно да се забрави типът на обекта? Това се случва при ъпкастинга и много по-праволинейно действие изглежда tune( ) да вземе просто Wind манипулатор като свой аргумент. Това изважда наяве най-важното: Ако го направите така, ще трябва да пишете нов tune( ) за всеки сорт Instrument във вашата система. Да кажем че последваме тази обосновка и добавим Stringed и Brass инструменти:
//: c07:Music2.java
// Overloading instead of upcasting
class Note2 {
private int value;
private Note2(int val) { value = val; }
public static final Note2
middleC = new Note2(0),
cSharp = new Note2(1),
cFlat = new Note2(2);
} // Etc.
class Instrument2 {
public void play(Note2 n) {
System.out.println("Instrument2.play()");
}
}
class Wind2 extends Instrument2 {
public void play(Note2 n) {
System.out.println("Wind2.play()");
}
}
class Stringed2 extends Instrument2 {
public void play(Note2 n) {
System.out.println("Stringed2.play()");
}
}
class Brass2 extends Instrument2 {
public void play(Note2 n) {
System.out.println("Brass2.play()");
}
}
public class Music2 {
public static void tune(Wind2 i) {
i.play(Note2.middleC);
}
public static void tune(Stringed2 i) {
i.play(Note2.middleC);
}
public static void tune(Brass2 i) {
i.play(Note2.middleC);
}
public static void main(String[] args) {
Wind2 flute = new Wind2();
Stringed2 violin = new Stringed2();
Brass2 frenchHorn = new Brass2();
tune(flute); // No upcasting
tune(violin);
tune(frenchHorn);
}
} ///:~
Това работи, но има голям недостатък: Трябва да се пишат специфични за типа методи за всеки Instrument2 клас който се добави. На първо място това значи повече програмиране, но също значи че ако добавите нови методи като tune( ) или нов вид Instrument, ще има много работа за свършване. Като добавим факта, че компилаторът няма да издаде никакво съобщение ако забравите да пренатоварите методите си целият процес на работа с методите се вижда явно неуправляем.
Не би ли било много по-хубаво ако веднъж пишете метод който взима типа на базовия клас за аргумент, а не на специфичен клас? Тоест, не би ли било най-хубаво да забравите за извлечените класове и да пишете методи само за базовия клас?
Точно това е което полиморфизмът позволява да се прави. Обаче повечето програмисти (които са програмирали процедурно преди) малко имат проблеми с начина на работа на полиморфизма.
Сподели с приятели: |