3. Наследяване
• Какво е „наследяване“?
– Основен принцип в ООП
– Един клас може да наследи характеристиките и поведението на
друг
– Родителски клас – по-общ; обхваща по-голямо множество обекти
– Наследен клас – по-специализиран; обхваща по-тясно множество
обекти, описвайки ги по-подробно
– Един родителски клас може да има много наследници
– Йерархии от класове – един клас може едновременно да бъде
наследен за друг и да бъде родителски за няколко други класа
– Някои езици позволяват множествено наследяване (няколко
независими родителски класа); C# не е от тях
4. Наследяване
• Примери за наследяване
– Жив организъм <- Бактерия, Растение, Животно, Гъба
– Животно <- Риба, Земноводно, Влечуго, Птица, Бозайник
– Бозайник <- Лъв, Котка, Куче, Примат, Човек
– Сграда <- Жилищна сграда, Обществена сграда
– Обществена сграда <- Библиотека, Читалище, Училище
– Художествено произведение <- Картина, Скулптура, Роман,
Мюзикъл
– Файл <- Текстов файл, Двоичен файл
– Текстов файл <- TXT файл, HTML файл, XML файл
– Графичен елемент <- Прозорец, Бутон, Текстово поле, Календар
– Бутон <- Обикновен бутон, Радио бутон, Бутон в лента с инструменти
5. Наследяване
• Защо е полезно наследяването?
– Категоризирането на обекти в практиката става на различни нива
– Служи за по-добро смислово структуриране на кода на
програмата
– Позволява манипулациите с нехомогенни набори от данни
– Прави възможно приложението на алгоритми върху категория от
обекти, независимо от пълният им набор от характеристики и
поведение
6. Наследяване
• Принципи на действие на наследяването
– Родителският клас описва набор от характеристики и поведение,
присъщи на всички обекти от него
– Наследеният клас автоматично получава всички описани от
родителския клас характеристики и поведение, като в допълнение
към тях описва други, специфични само за неговите обекти
– Всеки екземпляр на наследения клас може автоматично да се
разглежда и като екземпляр на родителския клас
– Същите принципи важат и в по-сложни йерархии на повече от едно
ниво
7. Наследяване в C#
• Деклариране на наследени класове
– В декларацията на клас може да се укаже родителски клас, който
той наследява
– Не може да бъде указан повече от един родителски клас
– Родителският клас може от своя страна да наследява трети клас и
т.н.
– Не може клас да наследява себе си или някой от своите
наследници
– Ако не се укаже родителски клас, декларираният клас по
подразбиране наследява object
– Членовете на родителския клас не се декларират повторно в
наследения клас; те могат да се използват наготово
8. Наследяване в C#
// Родителски клас - произволно животно
class Animal
{
public string Species;
public int Weight;
public int Age;
public void Vocalize()
{
}
}
// Наследен клас – куче
class Dog : Animal
{
public string Breed;
public string Name;
public void Vocalize()
{
Console.WriteLine("Woof!");
}
}
• Деклариране на наследен клас
– Ключова дума class
– Наименование на класа
– Двоеточие
– Наименование на родителския клас
(частично или пълно)
– Блок с декларации на членове
– При неуказване на родителски клас,
класът по подразбиране наследява
object
– Разрешено е член на наследения клас
да има същото наименование като
член на родителския клас (но в общия
случай не е желателно)
10. Наследяване в C#
• Употреба на членовете на родителски клас
– В декларацията на наследен клас могат да бъдат достъпвани
членовете на родителския клас, все едно са членове на същия клас
– Ако наследения клас декларира член със същото име като член на
родителския клас, членът на родителския клас може да бъде
достъпен чрез представката “base.”
– В останалата част от програмата, оперирането с всички членове
на обект се извършва по еднотипен начин, независимо в кой клас в
йерархията от класове на обекта е деклариран съответният член
11. Наследяване в C#
class Animal
{
//...
public string GetDescription()
{
return string.Format(
"Species: {0}; Age: {1}",
Species,
Age);
}
}
class Dog : Animal
{
//...
public string GetDescription()
{
return string.Format(
"{0}; Breed: {1}; Name: {2}",
base.GetDescription(),
Breed,
Name);
}
}
• Употреба на членовете на
родителски клас в декларацията на
наследения клас
• Достъпване на членове с
припокриващи се имена в
декларацията на наследения клас
– Ключова дума base
– Оператор за достъпване на член .
– Наименование на члена
• Употреба на членовете на
родителски клас в останалата част
от програмата
13. Наследяване в C#
• Конструктори на наследени класове
– При деклариране на конструктор в наследен клас може да се
укаже кой конструктор на родителския клас да бъде изпълнен,
както и аргументите, които да му бъдат подадени
– Тези аргументи може да са както константи и литерали, така и
параметри на конструктора в наследения клас или по-сложни
изрази
– Конструкторът на базовия клас се изпълнява преди конструктора на
наследения клас
– Ако не е указано кой конструктор на родителския клас да бъде
изпълнен, по подразбиране се изпълнява конструкторът без
параметри
14. Наследяване в C#
class Animal
{
//...
public Animal() { }
public Animal(string species)
{
Species = species;
}
}
class Dog : Animal
{
//...
public Dog()
: base("Canis lupus familiaris") { }
public Dog(string breed)
{
Species = "Canis lupus familiaris";
Breed = breed;
}
}
• Конструктори на наследени
класове - изпълнение на
конструктор на родителския клас
– Модификатор за видимост
– Наименование
– Списък с параметри
– Двоеточие
– Ключова дума base
– Списък с аргументи на родителския
конструктор
– Тяло на конструктора
• Изпълнение на конструктор по
подразбиране
16. Превръщане на типове нагоре и надолу по
йерархията
• Променливи и екземпляри на класове в паметта
– Не е задължително типът на променлива и типът на обекта в
паметта, който тази променлива реферира, да съвпадат
– Разрешено е променлива от тип A да реферира обект от тип B, ако
типът А е част от йерархията от родителски типове на типа B
(казваме, че тип B е съвместим с тип A)
– В противен случай се предизвиква грешка (при компилация или по
време на изпълнение)
17. Превръщане на типове нагоре и надолу по
йерархията
• Превръщане на типове нагоре по йерархията (upcasting)
– Нека тип A е част от йерархията от родителски типове на тип B
– Присвоява се на променлива от тип A израз от тип B
– Друг вариант: израз от тип B участва в по-сложен израз в ролята на
израз от тип A
– Не е необходим специален синтаксис (неявно превръщане на
типове)
– Винаги е разрешено и не може да предизвика грешка
– Типът на обекта в паметта не се променя
– Следствие: на променлива от тип object може да бъде присвоен
произволен израз
18. Превръщане на типове нагоре и надолу по
йерархията
// Създаване на екземпляр на класа Dog
Dog dog = new Dog("German shepherd");
// Превръщане на типове нагоре по
// йерархията към родителския клас Animal
Animal animal = dog;
// Превръщане на типове нагоре по
// йерархията в сложен израз
((Animal)dog).Vocalize();
• Присвояване на израз от наследен
тип на променлива от родителски
тип
• Сложни изрази с превръщане на
типове нагоре по йерархията
20. Превръщане на типове нагоре и надолу по
йерархията
• Превръщане на типове надолу по йерархията (downcasting)
– Нека тип A е част от йерархията от родителски типове на тип B
– Присвоява се на променлива от тип B израз от тип A
– Друг вариант: израз от тип A участва в по-сложен израз в ролята на
израз от тип B
– Необходимо е явно превръщане на типове
– В случай че типът на обекта в паметта не е съвместим с типа B, се
предизвиква грешка
– Компилаторът не разрешава преобразуване между типове, които
се намират в различни клонове от йерархията (тъй като това води
до сигурна грешка при изпълнение)
21. Превръщане на типове нагоре и надолу по
йерархията
// Присвояване на екземпляр na класа Dog
// на променлива от тип Animal
Animal animal = new Dog("Poodle");
// Превръщане на типове надолу по
// йерархията към наследения клас Dog
Dog dog = (Dog)animal;
// Превръщане на типове надолу по
// йерархията в сложен израз
Console.WriteLine(((Dog)animal).Breed);
• Присвояване на израз от
родителски тип на променлива от
наследен тип
– Тип, към който ще бъде извършено
превръщането, заграден в кръгли
скоби
– Израз, който трябва бъде превърнат в
указания тип
• Сложни изрази с превръщане на
типове надолу по йерархията
23. Превръщане на типове нагоре и надолу по
йерархията
• Проверка за съвместимост на типове
– Нека тип A е част от йерархията от родителски типове на тип B
– С оператора is може да се провери дали обектът в паметта,
рефериран от израз от тип A, е съвместим с типа B
– Ако операторът is върне стойност истина, превръщането на типове
надолу по йерархията е безопасно
– Операторът as може да се използва за безопасно превръщане
надолу по йерархията; в случай че типовете са несъвместими,
резултатът от превръщането е null
– Операторът as може да се използва единствено за превръщане
надолу по йерархията към референтен тип
24. Превръщане на типове нагоре и надолу по
йерархията
// Създаване на екземпляр на класа Animal
Animal animal = new Animal(
"Felis silvestris catus");
// Проверка за съвместимост на типове за
// безопасно превръщане на типове надолу
// по йерархията с is
if (animal is Dog)
{
Console.WriteLine(
((Dog)animal).Breed);
}
// Безопасно превръщане на типове надолу
// по йерархията с as
Dog dog = animal as Dog;
if (dog != null)
Console.WriteLine(dog.Breed);
• Проверка за съместимост на
типове с is
– Израз, чиято стойност трябва да бъде
проверена за съвместимост
– Оператор is
– Тип, съвместимостта с който трябва да
бъде проверена
• Безопасно превръщане на типове
надолу по йерархията с as
– Израз, чиято стойност трябва да бъде
превърната към друг тип
– Оператор as
– Референтен тип, към който трябва да
бъде извършено безопасното
превръщане
25. Проверка за съместимост на типове и безопасно
превръщане на типове надолу по йерархията - демо
// Демонстрация
26. Видимост
• Какво е „видимост“?
– Понятие в програмирането, служещо за ограничаване на достъпа
до фрагменти от кода
– В ООП – видимост на членове и видимост на типове
– Видимостта на член ограничава достъпа до съответния член от
различни участъци от програмата (същия клас, наследени класове,
други класове в същия модул, други модули)
– Видимостта на тип ограничава достъпа до съответния тип от
различни участъци от програмата (същия модул, друг модул)
27. Видимост
• Видимост в C#
– Модификатори за достъп
– Поставят се в декларациите на типове и членове
– Важат единствено за съответния тип/член
– Ако модификатор за достъп не е указан, по подразбиране
видимостта е минималната възможна
28. Видимост
• Модификатори за достъп за членове в C#
– Модификаторът за достъп се поставя в началото на декларацията
на съответния член
– private – членът е достъпен единствено в рамките на декларация на
същия клас (това е видимостта по подразбиране)
– protected – членът е достъпен в рамките на декларацията на същия
клас и всички негови наследени класове
– public – членът е достъпен навсякъде в програмата
– internal – членът е достъпен навсякъде в рамките на същото
асембли
– internal protected – членът е достъпен навсякъде в рамките на
същото асембли, както и в декларациите на всички наследени
класове, независимо в кое асембли са декларирани
29. Видимост
class MailMessage
{
private string _subject;
protected MailMessage(string subject)
{
_subject = subject;
}
public string GetSubject()
{
return _subject;
}
internal void Send()
{
// ...
}
}
• Видимост на членове
– Членове, достъпни единствено за същия
клас
– Членове, достъпни за същия клас и
наследените от него класове
– Членове, достъпни за цялата програма
– Членове, достъпни в същото асембли
31. Видимост
• Модификатори за достъп за типове в C#
– Модификаторът за достъп се поставя в началото на декларацията
на съответния тип (клас, структура, изброен тип и др.)
– public – типът е достъпен навсякъде в програмата
– internal – типът е достъпен в рамките на същото асембли (това е
видимостта по подразбиране)
32. Видимост
public class MusicAlbum
{
// ...
}
public enum MusicGenre
{
// ...
}
internal class AlbumCollection
{
// ...
}
internal struct UserInfo
{
// ...
}
• Видимост на типове
– Типове, достъпни за цялата програма
– Типове, достъпни в същото асембли
34. Капсулиране
• Какво е „капсулиране“?
– Основен принцип в ООП
– Всеки обект трябва да скрива от външния свят вътрешната си
реализация
– Видими за останалата част от програмата са само важните за нея
характеристики и поведение на обектите
35. Капсулиране
• Защо е полезно капсулирането?
– Опростява „външния вид“ на обекта
– Позволява промяна на вътрешната реализация на обекта, без да се
налага промяна в останалата част от програмата
– Осигурява интегритет на вътрешните характеристики на обекта
36. Капсулиране
• Принципи на капсулирането
– Всеки член или тип данни трябва да има възможно най-
ограничената видимост, позволяваща смисленото реализиране на
програмата
– Характеристиките на обекта са достъпни само за самия клас
– Методите, реализиращи вътрешно поведение, също са достъпни
само за самия клас
– Външен достъп до характеристиките на обекта може да се
осъществи само чрез други членове на класа (методи – аксесори
и мутатори; конструктори; свойства; индексатори)
– Възможно е наследените класове да имат привилегирован достъп
до някои членове на родителския клас, но само за тези, които са
им нужни
38. Задачи за упражнение
• Преработете програмата с геометричните фигури и тела
от упражненията към предишната лекция, така че да се
възползвате от принципите за наследяване и капсулиране:
– Капсулирайте данните на всички обекти: всички полета да бъдат
частни и промяната на стойностите им да се извършва през
конструктори и методи (публични или защитени)
– Създайте базови класове Object2D (характеристики: периметър и
лице) и Object3D (характеристики: обем и пълна повърхнина) и
реализирайте изчисляването им на базата на специфичните
характеристики наследени класове
– Създайте колекции от базовите класове и реализирайте логика за
въвеждане/извеждане на характеристиките им (използвайки
upcasting и downcasting)
39. Задачи за упражнение
• Реализирайте приложение за регистриране и разглеждане
на списък с произведения в библиотека със следните
класове:
– Печатно произведение (заглавие; език; издателство)
• Периодично печатно произведение (година; брой)
– Вестник (вид: ежедневник, седмичник, двуседмичник; гл. редактор; водеща новина
за броя)
– Списание (тема; авторски колектив; описание на корицата на броя)
• Самостоятелно печатно произведение (дата на издаване; автор/автори;
брой страници)
– Научна статия (научна област; препоръчана литература)
– Книга (номер на изданието; твърди/меки корици; наличие на илюстрации)
» Художествена литаратура (жанр; целева аудитория)
» Техническа литература (научна област; ниво на аудиторията: начинаещи,
напреднали, експерти)
40. Задачи за упражнение
• Реализирайте походова конзолна ролева игра:
– Играта се развива в правоъгълна мрежа от символи, подобно на Златотърсачи
– Създайте базов клас за единица (играч или чудовище), който съдържа
информация за координатите на единицата, точките живот и нанасяните от
единицата щети
– Създайте наследени класове за играч и различни видове чудовища
– В началото на играта, поставете играча и известен брой чудовища в игралното
поле
– Реализирайте безкраен цикъл, в който всяка единица получава ход, в който
може да се придвижва или атакува противник, отнемайки му точки живот;
чудовищата управлявайте програмно
– Бонус: добавете случаен елемент в играта (например случайно количество щети
при нападение, случайно генериран терен на игралното поле, и/или различни
характеристики за чудовища от един и същи вид)
– Бонус: добавете екипировка за играча