Когда мы работаем с классам и объектами мы можем связывать их не только с помощью ссылок между объектами (Has-a) но наследуя классы друг от друга (Is-a). Это удобно когда мы имеем несколько классов у которых есть нечто общее. При этом собрать все классы в один мы не можем.
Наследование нам также нужно для реализации полиморфизма подтипов. Обычно чисто наследование, без техник полиморфизма мы не используем. Но я постараюсь привести пару примеров.
Фрагмент 1.44
class Knight
{
public int Health;
public int Armor;
public void TakeDamage(int damage)
{
Health -= damage - Armor;
}
public void Pray()
{
Armor += 1;
}
}
class Barbarian
{
public int Health;
public int Armor;
public void TakeDamage(int damage)
{
Health -= damage - Armor;
}
public void Waaaagh()
{
Health += 10;
Armor -= 1;
}
}
Как вы видите между этими классами много общего. Мы могли бы избавиться от дублирующегося кода с помощью связи Has-a, сделав третий класс Health и сделав ссылку у варвара и рыцаря на какое-то здоровье. И это могло бы сработать и чаще всего так и делается. Но не всегда. Иногда лучше сделать связь is-a, сказав что и варвар и рыцарь является чем-то, что содержит здоровье.
Фрагмент 1.45
class Warrior
{
public int Health;
public int Armor;
public void TakeDamage(int damage)
{
Health -= damage - Armor;
}
}
class Knight : Warrior
{
public void Pray()
{
Armor += 1;
}
}
class Barbarian : Warrior
{
public void Waaaagh()
{
Health += 10;
Armor -= 1;
}
}
В данном случае мы сделали новый класс “воин”. И сказали что и рыцарь и варвар является воином а значит они содержат всё тоже, что содержит класс воин. Мы как бы перенесли всё, что внутри класса воин в классы рыцарь и варвар. При этом и рыцарь и варвар обладают уникальным возможностями. Рыцарь может помолится а варвар сделать Вааагх.
Цепочки вызовов конструкторов
Когда базовый класс (тот от когда наследуемся) имеет конструктор, то мы должны его вызвать в производном классе (тот который наследуется).
Сделать мы можем это с помощь цепочки вызова конструкторов. При этом конструктор базового класса вызывается первым.
Фрагмент 1.46
class Warrior
{
public int Health;
public int Armor;
public Warrior(int health, int armor)
{
Health = health;
Armor = armor;
}
public void TakeDamage(int damage)
{
Health -= damage - Armor;
}
}
class Knight : Warrior
{
public Knight(int health, int armor) : base(health, armor) { }
public void Pray()
{
Armor += 1;
}
}
class Barbarian : Warrior
{
public int LenghtOfAxe;
public Barbarian(int health, int armor, int lenghtOfAxe) : base(health, armor)
{
LenghtOfAxe = lenghtOfAxe;
}
public void Waaaagh()
{
Health += 10;
Armor -= 1;
}
}
В базовом классе (воин) мы добавили конструктор для инициализации здоровья и брони. Это привело к тому, что нам пришлось определять конструкторы в производных классах со сходными параметрами для вызова конструктора базового класса.
Это происходит благодаря двоеточию после имени конструктора и ключевому слова base. Это и есть вызов конструктора базового класса. Мы можем передать ему те же значения что передали нам, а может подставить какие-нибудь литералы.
Фрагмент 1.47
public Barbarian(int armor, int lenghtOfAxe) : base(100, armor)
{
LenghtOfAxe = lenghtOfAxe;
}
Например в этом случае все варвары создавались бы с фиксированным здоровьем и мы могли бы назначить им только броню.
Модификатор доступа protected
Базовый класс – это отдельный класс. Как бы это не звучало очевидно. Но вам стоит держать мысль о том, что раз класс отдельный то и проектируется он отдельно. Т.е нужно жестко сливать два класса. Базовый класс может содержать члены которые доступы производным классам. А может и сокрывать члены от всех, в том числе и от базовых классов.
При использование модификатора private член не доступен производному классу. А при использование protected член доступен только самому классу (как в случае с private) и в производном классе.