Наследяването обисновено цели създаване на фамилия класове с един интерфейс. Изразяваме това с диаграма на обърнато дърво:5
Едно от най-важните неща, които могат да се правят с такава фамилия от клесове е, че може да се третира обект от извлечен клас като обект от базов клас. Това е важно, защото значи, че може да се напише единствено парче код, който да игнорира детайлите на типа и да "разговаря" с базовия клас. Кодът е развързан от информацията, кпецифична за типа и с това е по-лесен за написване и разбиране. И ако нов тип –Triangle, например – се добави чрез наследяване, вашият код ще работи също така добре с новия тип (произлязъл от - б.пр.) Shape както и със съществуващите типове. Така програмата е разширяема.
Да видим горния пример. Ако напишете функция на Java:
void doStuff(Shape s) {
s.erase();
// ...
s.draw();
}
Тази функция говори с всякакъв обект от рода на Shape, така че е независима от спецификата на триенето и чертането. В някаква друга програма използваме функцията doStuff( ):
Circle c = new Circle();
Triangle t = new Triangle();
Line l = new Line();
doStuff(c);
doStuff(t);
doStuff(l);
Извикванията на doStuff( ) автоматично работят точно, без значение точния тип на обекта.
Това е наистина вълнуващ трик. Да видим линията:
doStuff(c);
Тук става следното: манипулатора на Circle е даден на функция, която очаква Shape манипулатор. Доколкото Circle е Shape той може да се третира като едно doStuff( ). Тоест каквото и да е съобщение, което doStuff( ) може да изпрати на Shape, Circle може да го възприеме. Така че написаното е напълно сигурно и логично нещо.
Наричаме този процес на третиране на производния клас като базовия upcasting. Думата cast е използвано в смисъла на леене в калъп и up идва от типичния начин на рисуване на дървото на наследяванията - с корена нагоре. Така casting-ът към базовия тип е преместване по диаграмата на наследяванията нагоре: upcasting. (някои вероятно биха предпочели да използуваме "превръщане към тип" вместо casting -б.пр.)
Една ООП съдържа upcasting някъде, понеже това е начинът по който се развързвате от необходимостта да знаете на конкретния тип с който се работи. Погледнете кода в doStuff( ):
s.erase();
// ...
s.draw();
Забележете че той не казва “Ако е Circle, прави това, ако е Square, прави друго и т.н.” Ако се пише код от този тип, в който се проверява винаги точния тип на Shape, за да се изпълни нещо, такъв код е тежък и трябва да се променя с всяко добавяне на тип. В този пример просто казва: “То е Shape, знае се как да го erase( ) , прави се коректно.”
Поразителното в doStuff( ) е, че някак си стават правилните неща. Извикването на draw( ) за Circle предизвиква изпълнението на код различен от случая на draw( ) за Square или Line, но когато draw( ) съобщението е изпратено на безименен Shape, осигурява се коректно поведение според конкретния вид на Shape манипулатора. Това е забележително, защото по време на компилацията doStuff( ) не се знае точния тип. Така че би трябвало да се очаква да се извика erase( ) за Shape, draw( ) за Shape а не за специфичните Circle, Square или Line. И все пак правилните неща се случват. Ето как става това.
Когато се изпрати съобщение на обект без да се знае точният му вид и се получи всичко както трябва, това се нарича полиморфизъм. Процесът, изролзуван от ООП езика за да се постигне това се нарича дзинамично свързване. Компилаторът и run-time поддръжката осигуряват детайлите; всичко, което трябва да се знае е, че това става и по-важното - как да се организира по този начин.
Някои езици изискват употребата на специална ключова дума за да се получи динамично свързване. В C++ тя е virtual.В Java няма нужда да се помни подобна дума, понеже винаги се свързва динамично. Така че може да се смята, че всичко ще е както трябва, даже и в случая на upcasting.
Често при проектирането се иска да се използува базов клас само заради интерфейса му. Тоест няма да се създават обекти от него, а само ще се наследи от класове, за да се използува интерфейсът му. Това се постига като се направи класът абстрактен чрез използуването на ключовата дума abstract. Ако някой се опитва да направи обект от клас, който е abstract, компилаторът попречва на това. Това е инструмент с който се налага определен начин на проектиране.
Също може да се използува abstract за да се означи метод, който още не е написан - все едно да се каже “ето (име на - бел.пр.) функция за всички наследници, но в този момент още не съм я написал.” Един abstract може да бъде зададен само в abstract клас. Когато такъв клас е наследен, абстрактният метод трябва да се напише, иначе наследникът става също абстрактен (abstract). Създаването на abstract позволява да се сложи метод в интерфейса без да се пише (възможно) безполезен код в него.
Ключовата дума interface придвижва концепцията за abstract клас стъпка напред, предотвратявайки въобще всякакви дефиниции на функции. interface е много полезен и често използуван инструмент, понеже дава перфектно отделяне на интерфейса и приложението. Освен това може да комбинирате много интерфейси заедно, ако искате. (Не може да наследявате повече от един обикновен class или abstract class.)
Сподели с приятели: |