Наследование. Данный самоучитель сильно отличается от академических курсов программирования и дает необходимый минимум для начала программирования на практике

Данный самоучитель сильно отличается от академических курсов программирования и дает необходимый минимум для начала программирования на практике, не забывайте об этом и главной задаче программирования.

На практике разные классы очень часто пересекаются по функциональности. Новый класс или классы могут лишь изменять некоторые функции старых и/или добавлять к ним что-то, не использующееся в базовом. Чтобы не повторять одну и ту же функциональность в разных местах используется механизм наследования.

Один класс считается базовым. Второй, меняющий часть функциональности базового, считается его наследником. В C# наследовать сразу от нескольких базовых классов нельзя. Но у одного базового класса может быть сколько угодно наследников. Как и большинство продвинутых методик программирования, наследование крайне сложно иллюстрировать простыми примерами - очень уж надуманными и бессмысленными они кажутся. ООП упрощает написание больших и сложных программ - в них оно экономит время и силы разработчиков.

Предположим, что мы пишем программу для управления кораблем, на котором будут перевозить людей и кошек (а что еще надо для счастья?). Для людей создадим класс Person, для кошек Cat. Само собой люди отличаются от кошек даже на крайне примитивном уровне - у кошек нет фамилии и отчества, у людей нет хозяев... и так далее. Но для управления кораблем нам надо знать общий вес пассажиров, чтобы избежать перегрузки.

Таким образом можно создать базовый класс Cargo (Груз), в котором будет описана масса, а Person и Cat унаследовать от базового, после чего массу отдельно описывать не понадобится. Интересно, сколько весил Пушкин?

class Program

{

static void Main(string[] args)

{

List<Person> personsList = new List<Person>();

personsList.Add(new Person("Пушкин", "Александр", "Сергеевич", 60));

personsList.Add(new Person("Гончарова", "Наталья", "Николаевна", 40));

List<Cat> catsList = new List<Cat>();

catsList.Add(new Cat("Барсик", personsList[0], 10));

catsList.Add(new Cat("Багира", personsList[1], 8));

List<Cargo> cargoList = new List<Cargo>();

cargoList.Add(personsList[0]);

cargoList.Add(personsList[1]);

cargoList.Add(catsList[0]);

cargoList.Add(catsList[1]);

double totalMass = 0;

foreach (Cargo currCargo in cargoList)

{

totalMass += currCargo.Mass;

}

Console.WriteLine("суммарная масса всех пассажиров включая и котов и людей: " + totalMass);

Console.ReadLine();

}

}

public class Cargo

{

private double _mass = 0;

public double Mass

{

get { return _mass; }

}

public Cargo(double mass)

{

_mass = mass;

}

}

// класс человека

public class Person: Cargo

{

// три строковых переменные-свойства, доступные извне класса - public

private string _name = "";

public string Name

{

get { return _name; }

set { _name = value; }

}

private string _surname = "";

public string Surname

{

get { return _surname; }

set { _surname = value; }

}

private string _otchestvo = "";

public string Otchestvo

{

get { return _otchestvo; }

set { _otchestvo = value; }

}

public string Fio

{

get

{

string fio = Surname + " " + Name + " " + Otchestvo;

return fio;

}

}

// конструктор, специальная функция, которая вызывается при создани экземпляра класса с помощью слова new

public Person(string surname, string name, string otchestvo, double mass)

: base(mass)

{

Name = name;

Surname = surname;

Otchestvo = otchestvo;

}

}

public class Cat: Cargo

{

public string Name = "";

public Person Master = null;

public Cat(string name, Person master, double mass)

: base(mass)

{

Name = name;

Master = master;

}

}

Обратите внимание на код

List<Cargo> cargoList = new List<Cargo>();

cargoList.Add(personsList[0]);

cargoList.Add(personsList[1]);

cargoList.Add(catsList[0]);

cargoList.Add(catsList[1]);

Дочерний класс можно конвертировать в базовый. При этом в переменной типа базового класса будет храниться ссылка на объект дочернего класса, через нее будут доступны общие для них обоих функции и свойства. А вот обратная конвертация чревата ошибками.

Person person = personsList[0];

Cargo cargo = (Cargo)person;

Person person2 = (Person)cargo;

Cat cat = (Cat)cargo; // ошибка во время выполнения Unable to cast object of type 'TestConsoleApplication.Person' to type 'TestConsoleApplication.Cat'.

Наследники могут и менять какие-то функции базового класса - это называется переопределением. На самом деле все классы в C# наследуются от базового класса Object среди немногих методов которого присутствует метод ToString(). Но если мы попытаемся использовать его для вывода информации о наших пассажирах, то ничего хорошего не получится.

foreach (Cargo currCargo in cargoList)

{

Console.WriteLine(currCargo.ToString());

}

На самом деле именно этот метод неявно вызывается при скрытых преобразованиях в строку (в духе " масса = " + mass)

так что предыдущий пример можно записать короче, но правильно он от этого работать не начнет

foreach (Cargo currCargo in cargoList)

{

Console.WriteLine(currCargo);

}

Но в классах наследниках мы можем переопределить базовый метод с помощью ключевого слова override

Добавим в класс Cat функцию

public override string ToString()

{

return Name + ", вес " + Mass + " кг., хозяин " + Master.Fio;

}

В класс Person функцию

public override string ToString()

{

return Fio + ", вес " + Mass + " кг.";

}

И вновь посмотрим на вывод кода

foreach (Cargo currCargo in cargoList)

{

Console.WriteLine(currCargo);

}

Важной особенностью наследования является то, что им не следует чрезмерно увлекаться. Все методики программирования должны упрощать код, а не усложнять его. Длинные цепочки наследования могут создать массу проблем. Частой болезнью начинающих программистов является то, что узнав о продвинутых и красивых методиках программирования они начинают пихать их повсюду, даже если это вредит проекту. Стив Макконел в книге Совершенный код не рекомендует создавать более 2-3 уровней наследования и более 7-9 наследников базового класса.

Другим правилом в отношении наследования является принцип подстановки Барбары Лисков. Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа не зная об этом. Или, если попроще, подклассы должны являться специализированной версией базового класса. В нашем случае и человек и кот являются специализированными версиями груза для корабля. Не стоит забывать, что членами класса могут быть любые другие классы. Пример плохого использования наследования (тоже с котом!) из Совершенного кода

Так же стоит с подозрением относиться к классам, которые переопределяют метод, делая его пустым. Например если есть базовый класс Cat, уже после создания которого выяснилось, что некоторые коты не могут царапаться и был создан подкласс ScratchlessCat. Но что делать, если вы найдете кота без хвоста? Кота, который не пьет молоко? Кота, который не ловит мышей? В итоге можно получить гигантскую иерархию с классами вроде ScratchlessTailessMicelessMilklessCat. На самом деле в данном случае вместо наследования надо использовать включение, добавив в базовый класс Cat класс Claws в качестве одного из членов.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: