Цель лекции: Показать возможности технологий наследования и полиморфизма при проектировании классов. Научится применять эти технологии при программировании на языке C#.
Чтобы продемонстрировать механизм наследования классов рассмотрим несколько нарочито простых классов, которые будут описывать транспортные средства: автобус, такси, троллейбус и трамвай. Базовым классом у нас будет класс "траспортное средство", который мы определим следующим образом:
$$\begin{verbatim} class TTransport { protected int Speed; // скорость protected int Massa; // Масса protected int Payload; // грузоподъемность public TTransport(int Speed, int Massa, int Payload) { this.Speed = Speed; this.Massa = Massa; this.Payload = Payload; } public void Print() // распечатать информацию { Console.WriteLine("Speed = {0}", Speed); Console.WriteLine("Massa = {0}", Massa); Console.WriteLine("Payload = {0}", Payload); } } \end{verbatim}$$Смысл этого класса хранить и распечатывать информации о транспортном средстве. Любое транспортное средство имеет такие характеристики как скорость, масса и грузоподъемность. Однако для автотранспорта есть еще дополнительные характеристики такие, как расход топлива, а у электротранспорта есть такая дополнительная характеристика как напряжение. Поэтому мы создадим еще два класса. С использованием технологии наследования. Наследование - это технология проектирования классов, позволяющая создавать новые классы на базе предыдущих классов. При этом классы наследники будут иметь все поля и методы родительского класса. А к тем полям и методам, которые имеют спецификатор доступа $$public$$ или $$protected$$, можно будет обращаться из наследного класса. Приведем определения этих классов:
$$\begin{verbatim} class TAuto : TTransport { protected int Petrol; // Расход бензина public TAuto(int Speed, int Massa, int Payload, int Petrol) : base(Speed, Massa, Payload) { this.Petrol = Petrol; } public void Print() { base.Print(); Console.WriteLine("Petrol = {0}", Petrol); } } class TElectro : TTransport { protected int Voltage; // Напряжение public TElectro(int Speed, int Massa, int Payload, int Voltage) : base(Speed, Massa, Payload) { this.Voltage = Voltage; } public void Print() { base.Print(); Console.WriteLine("Voltage = {0}", Voltage); } } \end{verbatim}$$Разберем, как работает наследование класса. Чтобы указать, что класс $$B$$ является наследником класса $$A$$, следует написать:
$$\begin{verbatim} class B : A { ... } \end{verbatim}$$Теперь класс $$B$$ имеет все поля и методы класса $$A$$. Однако поля и методы класса $$A$$, имеющие классификатор доступа $$private$$, будут недоступны для класса $$B$$. Если в классе $$A$$ объявлены поля и методы, совпадающие с полями и методами класса $$B$$, то они будут заменены, или как говорят - сокрыты соответствующими полями и методами класса $$B$$. В нашем примере таким методом является метод $$Print$$. Если нам нужно из класса $$B$$ обратиться к полям или методам родительского класса $$A$$, то для этого нужно использовать ключевое слово $$base$$, аналогичное ключевому слову $$this$$.
Если родительский класс имеет заданный конструктор, то он должен быть вызван из конструктора наследного класса. Обратите внимание, как можно вызывать конструктор родительского класса:
$$\begin{verbatim} public TAuto(int Speed, int Massa, int Payload, int Petrol) : base(Speed, Massa, Payload) { ... } \end{verbatim}$$Попрактикуемся в создании классов наследников. Создадим два класса (автомобиль и такси) наследников от класса $$TAuto$$ и два класса (трамвай и троллейбус) наследников от класса $$TElectro$$. У каждого нового класса могут быть дополнительные поля и методы, но мы для простоты не будем добавлять новых полей и методов.
$$\begin{verbatim} class TBus : TAuto { public TBus(int Speed, int Massa, int Payload, int Petrol) : base(Speed, Massa, Payload, Petrol) { } } class TTaxi : TAuto { public TTaxi(int Speed, int Massa, int Payload, int Petrol) : base(Speed, Massa, Payload, Petrol) { } } class TTram : TElectro { public TTram(int Speed, int Massa, int Payload, int Voltage) : base(Speed, Massa, Payload, Voltage) { } } class TTroll : TElectro { public TTroll(int Speed, int Massa, int Payload, int Voltage) : base(Speed, Massa, Payload, Voltage) { } } \end{verbatim}$$Предположим, что у нас будет список различных транспортных средств, для которых нам нужно вычислить отношение стоимости единицы полезной массы к расходу топлива (бензина или электроэнергии). Создадим массив транспортных средств:
$$\begin{verbatim} TTransport[] Trans; Trans = new TTransport[4]; Trans[0] = new TAuto(120, 5, 2, 60); Trans[1] = new TTaxi(250, 1, 1, 40); Trans[2] = new TTram(100, 7, 5, 3000); Trans[3] = new TTroll(80, 4, 2, 2500); \end{verbatim}$$Заметим, что у нас в массиве $$Trans$$ все элементы из разных классов, хотя и родственники класса $$TTransport$$. Конечно, мы могли бы, перебирая массив $$Trans$$, для каждого экземпляра класса подсчитать нужное соотношение. Но тут возникает проблема - ведь у половины классов нужно делить значение $$Payload$$ на значение $$Petrol$$, а у половины нужно делить значение $$Payload$$ на значение $$Voltage$$. Тем более, что идеология объектно-ориентированного программирования подразумевает, что классы сами должны обрабатывать собственные данные. Поэтому мы поступим следующим образом - мы добавим в наши классы метод $$Calc()$$. При этом мы воспользуемся технологией полиморфизма. Полиморфизм - это возможность объектов с одинаковой спецификацией иметь различную реализацию. Мы создадим у класса TTransport виртуальный метод:
$$\begin{verbatim} class TTransport { ... public virtual double Calc() { return 0; } } \end{verbatim}$$Ключевое слово $$virtual$$ означает, что наследники данного класса смогут изменить код данного метода. Сейчас мы еще модифицируем у родительского класса $$TTransport$$ метод $$Print$$ следующим образом:
$$\begin{verbatim} public void Print() // распечатать информацию { Console.WriteLine("Speed = {0}", Speed); Console.WriteLine("Massa = {0}", Massa); Console.WriteLine("Payload = {0}", Payload); Console.WriteLine("Calc Result = {0}", Calc()); } \end{verbatim} Теперь распечатаем наш массив следующим образом: \begin{verbatim} foreach (TTransport T in Trans) { T.Print(); Console.WriteLine(); } \end{verbatim} Мы увидим следующее: \begin{verbatim} Speed = 120 Massa = 5 Payload = 2 Calc Result = 0 Speed = 250 Massa = 1 Payload = 1 Calc Result = 0 Speed = 100 Massa = 7 Payload = 5 Calc Result = 0 Speed = 80 Massa = 4 Payload = 2 Calc Result = 0 \end{verbatim}$$Разумеется, везде мы видим $$"Calc Result = 0"$$, потому что мы еще нигде не реализовали метод вычисления нужного нам коэффициента. Но вспомним, что мы пометили метод $$Calc$$ как виртуальный, и теперь мы можем заменить этот метод у классов потомков. Сделаем следующие модификации наших классов:
$$\begin{verbatim} class TAuto : TTransport { ... public override double Calc() { return (double)Payload / (double)Petrol; } } class TElectro : TTransport { ... public override double Calc() { return (double)Payload / (double)Voltage; } } \end{verbatim}$$Теперь наша программа выдаст нужный результат:
$$\begin{verbatim} Speed = 120 Massa = 5 Payload = 2 Calc Result = 0,0333333333333333 Speed = 250 Massa = 1 Payload = 1 Calc Result = 0,025 Speed = 100 Massa = 7 Payload = 5 Calc Result = 0,001666666666667 Speed = 80 Massa = 4 Payload = 2 Calc Result = 0,0008 \end{verbatim}$$Чтобы оценить всю мощь полиморфизма, нужно обратить внимание, что метод $$Calc$$ вызывается в методе $$Print$$ класса $$TTransport$$! Ведь при наследовании мы можем и не иметь исходного текста класса $$TTransport$$, этот класс может быть доступен только в виде откомпилированной библиотекой. Тем не менее механизм полиморфизма позволяет нам модифицировать виртуальные методы. Мы будем использовать систематически полиморфизм при реализации вычислительных процедур.
Сделаем еще одно замечание. В родительском классе $$TTransport$$ нам пришлось фиктивно реализовать метод $$Calc$$, хотя с точки зрения проектирования это не очень правильно. Во-первых, плохо писать код только для того, чтобы он был, а, во-вторых, плохо писать неверный код: возвращать 0. К счастью, развитое объектно-ориентированное программирование позволяет создавать так называемые абстрактные классы. Абстрактные классы могут содержать абстрактные виртуальные методы. Абстрактный метод содержит только объявление, но не имеет реализации. Разумеется, абстрактный метод является виртуальным, но ключевое слово $$virtual$$ не пишется, чтобы в наследных классах можно было перекрыть этот метод. Более того, невозможно создать экземпляр абстрактного класса, а наследник абстрактного класса или сам должен быть абстрактным или обязан реализовать все абстрактные методы. Последний штрих к нашей программе:
$$\begin{verbatim} abstract class TTransport { ... public abstract double Calc(); } \end{verbatim}$$Еще раз подчеркнем, что мы не привели в этом листинге реализацию метода $$Calc$$ не потому, чтобы сократить письмо, а потом что для абстрактного метода не бывает реализации.
Ключевые термины
Абстрактный класс - класс, содержащий нереализованные методы, которые необходимо реализовать в классах наследниках.
Наследование - технология проектирования классов, позволяющая создавать новые классы на базе предыдущих классов.
Полиморфизм - возможность объектов с одинаковой спецификацией иметь различную реализацию.
Краткие итоги: На примерах проектирования классов продемонстрированы технологии наследования и полиморфизма в языке C#. Показано, как работать с абстрактными классами.
Научиться самостоятельно создавать классы в C# и выполнять операции наследования и полиморфизма.
Рассмотрим практический пример. Нам необходимо описать структуру данных, которая будет описывать некоторую персону в нашей организации. При этом в нашей вымышленной организации все сотрудники относятся к двум категориям - "руководители" и "исполнители". Пусть личная карточка этих персон должна содержать следующую информацию:
для исполнителей:
для руководителей:
Мы видим, что первые пять пунктов личной карточки совпадает как у исполнителей, так и у руководителей. Поэтому имеет смысл создать основной класс $$TPerson$$ который будет описывать общую персону, а затем с помощью механизма наследования мы создадим еще два класса-потомка $$TWorker$$ и $$TMaster$$. В нашем примере мы будем рассматривать лишь один метод: $$int\ CalcPay()$$. Этот метод должен возвращать сумму, которую получает данный сотрудник. Для простоты мы будем считать, что эта сумма складывается из оклада и надбавки. Реализуем наши классы.
$$\begin{verbatim} class TPerson { public int ID; public string Name; public string Position; public int Salary; public int Bonus; public virtual int CalcPay() { return Salary + Bonus; } } class TWorker : TPerson { public int Deparment; } class TMaster : TPerson { public int Rank; } \end{verbatim}$$Поскольку в будущем процедура вычисления выплаты сотруднику может быть изменена мы использовали ключевое слово $$virtual$$. Предположим, в другом классе у нас есть метод, который принимает в качестве параметра экземпляр класса $$TPerson$$.
$$\begin{verbatim} public int TotalPay(TPerson Person) \end{verbatim}$$Внутри этого метода происходит вызов метода $$CalcPay()$$. Предположим, теперь, что В нашей организации появились руководители высшего звена, для которых вводится дополнительная выплата - $$SuperBonus$$. Мы хотим описать этих топ-менеджеров таким образом, чтобы использовать класс - наследник от класса $$TMaster$$. Реализуем это:
$$\begin{verbatim} class TTopMaster : TMaster { public int SuperBonus; public override int CalcPay() { return base.CalcPay() + SuperBonus; } } \end{verbatim}$$С помощью механизма полиморфизма мы можем использовать в качестве параметра в методе $$TotalPay()$$ экземпляры класса $$TTopMaster$$. При этом при расчете выплат будет учитываться и дополнительная выплата $$SuperBonus$$.
Научиться самостоятельно создавать классы в C# и выполнять операции наследования и полиморфизма.
При проектировании иерархии классов следует обращать внимание на то, чтобы продемонстрировать эффективность использования технологий наследования и полиморфизма.
Реализация методов в классах должна содержать лишь информацию о вызове и простейшие действия с полями.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.