
- •Программирование и алгоритмические языки. Курс за третий семестр.
- •Введение в объектно-ориентированное программирование (ооп)
- •Идеологический обзор
- •Базовые принципы ооп
- •Наследование имён. Наследование значений.
- •Коллизия
- •Что такое имя процедуры?
- •Ооп как оперирование типами
- •Именование типов в c# Тип данных «класс»
- •Конструкторы и деструкторы
- •И нкапсуляция данных
- •Наследование (краткое введение)
- •Наследование реализации как уточнение семантики типа
- •Пополнение и переопределение методов Отношение пополнения интерфейса
- •Открытие реализации для проектирования классов - опция protected
- •Полиморфизм Полиморфизм как уточнение семантики типа переменной
- •Динамические методы как поддержка полиморфизма- опции static, virtual, override
- •Событийное программирование Обработка событий
- •Идея: Когда что-то вставляется, срабатывает некоторый предикат. Например, после вызывается команда проверки, каскадного удаления или какая-нибудь другая команда. Генераторы и приёмники сообщений
- •Событийное программирование нужно:
- •Событийный стиль в процедурном программировании - управление данными (на примере)
- •Событийное программирование в c# (delegate, event)
- •Введение в компонентное программирование
- •Объекты, реализующие интерфейсы
- •Частный случай общей проблемы взаимодействия программного обеспечения разных производителей на уровне исполняемого кода
- •Базовый интерфейс компонент
- •Проблема множественности иерархий
- •Коллизия имён
- •Коллизия реализации
- •«Симметричное» решение – агрегаты (декартовы произведения классов)
- •Множественное наследование
- •«Асимметричное» решение - именованные интерфейсы
- •Основные отличия интерфейса и абстрактного класса
- •Наследование интерфейса (компонентный подход)
- •Обработка исключений в ооп
- •Определение и генерация исключений в c#
- •Выбрасывание исключений
- •Захват исключения
- •Блок finally
- •Коды программ
Полиморфизм Полиморфизм как уточнение семантики типа переменной
Полиморфизм (многообразие) - возможность уточнения (изменения) не только значений, состояний объекта, но и поведения объекта, то есть связанных с ним функций изменения состояния или алгоритмов реализации (изменение (уточнение) фактического типа переменной в ходе выполнения программы).
Описание типа переменной теперь означает лишь присвоение начального типа по умолчанию.
Тип
Строгая типизация – алгоритмика (типы минимизируют ошибки алгоритмов). Поэтому тип переменной описывается до появления переменной. Строгая типизация ориентирована на создание надёжных программ.
Бестиповая логика - свободная типизация (например, языки низкого уровня (ассемблер)). Свободная типизация ближе к системному программированию (ближе к эффективным реализациям)
Полиморфизм радикально изменяет взгляд на понятие типа. Если раньше переменная принадлежала одному фиксированному типу, то теперь она изменяет его, то есть тип становится атрибутом переменной.
Полиморфное программирование – сложное программирование ещё и потому, что не во всём адекватно поддерживается реализация.
Пример:
Пусть Dad – объект класса cDad,
Son – выражение класса cSon, где cDad – предок cSon в отношении наследования.
1) Присваивание Son:=Dad - синтаксическая ошибка.
Допустимо явное присваивание типов переменной любого класса любому другому классу:
Son:=Dad as cSon; - не является синтаксической ошибкой, но обращение к полям и методам сына, отсутствующим у отца, будет ошибкой времени выполнения.
2) В любом случае допустимо присваивание:
Dad:=Son и, соответственно, употребление выражений типа cSon вместо значений типа cDad, например, при обращении к процедуре.
Замечание:
Как всегда происходит присваивание ссылок. Это означает, что ссылка на предыдущие значения, связанная со ссылкой Dad, будет потеряна. Переменная Dad изменяет свой тип с формально описанного статического типа cDad на фактический тип cSon.
Что это означает для значений? Преображает ли Dad новые поля?
Фактически – да, формально – нет. Для таких полей обращение Dad.p неверно, а верно (Dad as cSon).p, где оператор as используется для выполнения определенных типов преобразований между совместимыми ссылочными типами.
Точно так же дело обстоит с методами, которые есть у сына, но нет у отца.
Изменяются ли значения, то есть реализация одноимённых функций?
Да, если таковые при объявлении класса cDad объявлены как виртуальные (virtual) или динамические (dynamic) (одна семантика, разная реализация), а при определении класса cSon – как переписывающие метод предка (override).
Нет, если методы предка объявлены как статические (static), что является опцией по умолчанию.
Динамические методы как поддержка полиморфизма- опции static, virtual, override
В синтаксисе переменный тип связан с:
-Virtual - виртуальный (динамический)- доступный для переопределения потомков (перекрытие методов базового класса)
-Override - метод, который переопределяет родительский (перекрытие методов в производном классе)
-Static-функции - функции, которые могут быть вызваны без определения
объектов. Постоянная ссылка (всегда указывает на одно и то же определение функции, т.е. она однозначно связана с каким- то определением).
Можно ли наследовать от класса, содержащего точку входа?
Да, можно. Точка входа должна быть статическим методом, но этот метод может и не принадлежать статическому классу. (Ключевое слово static означает невозможность создания экземпляров класса, но его методы доступны с момента запуска программы). Такой метод можно вызвать из любого другого метода без объявления ссылочной переменной или создания экземпляров при помощи оператора new (т.е. метод можно вызывать без создания объекта).
В соответствии со строгой типизацией фактические и формальные параметры должны соответствовать по числу и типу, т.е. попытка «засунуть» не тот тип кончается печально. Прелесть полиморфизма заключается в том, что здесь в качестве параметра может выдаваться cAnimal и все предки, т.е. в предке можно описывать общие методы для всех потомков.
Пример: Функционирование блох и динозавров :)
using System; // стандартные операции (часть языка)
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Animals // определение имён
{
class cAnimal
{
//поля:
protected string Name;
public virtual string GetName() // get-метод виртуальный, т.к. он переопределяемый (для различных классов он будет возвращать не только имя, которое есть у всех, но и род, который у всех различен)
{
return "Animal" + Name; // род и имя животного
}
public cAnimal() //конструктор по умолчанию (приходится переопределять, чтобы он был доступен)
{
}
public cAnimal(string ThatAnimal) //конструктор
{
this.Name = ThatAnimal; // создаётся животное с каким-то именем (своим прозвищем)
}
//методы(cAnimal):
//virtual- допустимо переопределение в производном классе
public virtual void Move() //описание метода "Двигаться" для животных
{
Console.WriteLine(GetName() + "moves");
}
public virtual void Cry() // описание метода "Кричать" для животных
{
Console.WriteLine(GetName() + "cries");
}
public virtual void Bite(cAnimal ThatAnimal) // описание метода "Кусать" для всех животных в целом
{
Console.WriteLine(GetName() + "bites" + ThatAnimal.GetName()); // животное (род, имя), которое мы описываем, кусает другое животное(род, имя) (т.е в качестве параметра ссылка на другое животное )
}
//пример- вызов "вперёд" виртуальных методов из невиртуального
public void Attacks(cAnimal ThatAnimal) // описание метода "нападать" для животных в целом (это невиртуальный метод - переопределять его запрещено, т.е. в потомках он изменяться не может)
{
// метод Attacks вызывает виртуальный метод
Console.WriteLine(this.GetType() + "attacs" + ThatAnimal.GetName()); //нападение одного животного на другое
//заключается в:
this.Move(); //животное передвигается
this.Bite(ThatAnimal); // кусает другое животное
ThatAnimal.Cry(); // то животное кричит (больно же)
ThatAnimal.Move(); // и убегает
}
}//class cAnimal
//класс "cDinosaur" наследник класса "сAnimal"
public class cDinosaur : cAnimal
{
public cDinosaur() //конструктор по умолчанию (при наследовании без конструктора по умолчанию выдаётся ошибка)
{
}
public cDinosaur(string ThatName) //конструктор
{
this.Name = ThatName;
}
//методы, которые добавляются к наследуемым методам (доопределение предыдущих методов для динозавров):
//модификатор override требуется для расширения или изменения абстрактной или виртуальной реализации унаследованного метода
public override string GetName() // переопределённый по отношению к животным
{
return "Dinosaur" + Name; //род и имя животного
}
public override void Cry() //переопределение метода "Кричать" для динозавров
{
Console.WriteLine(GetName() + "cries r-r!");
}
public void Growl() //описание метода "рычать" для динозавров - невиртуальный метод (для динозавров и всех его потомков не будет изменяться)
{
Cry();
}
public override void Move() //описание метода "двигаться" для динозавров
{
Console.WriteLine(GetName() + "runs");
}
} //class cDinosaur
class cTRex : cDinosaur
{
public cTRex() // конструктор по умолчанию
{
}
public cTRex(string ThatName) // конструктор
{
this.Name = ThatName;
}
public override string GetName()
{
return "TRex" + Name; // род и имя животного
}
public override void Cry() // переопределение метода "кричать" для динозавров
{
Console.WriteLine(GetName() + "cries R-R-R");
}
} // class cTRex
// класс "Flea" наследник класса "cAnimal"
class cFlea : cAnimal //производный класс "Блоха"
{
public cFlea() // конструктор по умолчанию
{
}
public cFlea(string ThatName) //конструктор
{
this.Name = ThatName;
}
//методы:
public override string GetName()
{
return "flea" + Name; // род и имя животного
}
public override void Move() // описание метода "двигаться" для блох
{
Console.WriteLine(GetName() + "jumps");
OnFleaJump(); // кратный вызов
}
public override void Cry() //описание метода "кричать" для блох
{
Console.WriteLine(GetName() + "cries Pi-pi"); //блоха
}
} //class cFlea
class cProgram
{
static void Main()
{
cDinosaur Dino = new cDinosaur("Dino"); // создаём нового динозавра с именем Dino
cFlea Bug = new cFlea("Bug");// создаём новую блоху с именем Bug
Bug.Attacks(Dino); // полиморфный вызов- неявное уточнение типа cAnimal- блоха атакует динозавра
Console.WriteLine ("______________________");
// аналогично
// Dino.cAnimal x;
// x=Dino; // уточнение типа не требует явного преобразования типа
// Bug. Attacks(x);
// отследи вызов последней реализации
cTRex Rex = new cTRex("Rex");
Bug.Attacks(Rex);
Console.WriteLine("______________________");
Console.ReadKey(); //задержка
//Распечатываем имя динозавра, говорим, что этот динозавр боится прыжков конкретной блохи (выдаётся имя блохи)
}
}
}
Мощь полиморфизма и его сложность заключается в том, что при создании объекта и вызове его в метод, имя связано неоднозначно с реализацией (т.е. имя метода присутствует во всей иерархии).
-если метод статичный, то связь имени с реализацией постоянная (т.е. будет вызываться одна и та же реализация)
-если метод переопределяемый, то возникают нюансы (какая же реализация будет работать?).
В примере переопределяются в потомках все методы, кроме Attacks (нападения), вызывается метод нападения, где блоха набрасывается на динозавра.
Bug.Attacks(Dino); - не является синтаксической ошибкой (хотя в скобках должен быть параметр Animal типа cAnimal )
c
Animal
Attacks
cDinosaur cFlea
cTRex
У блохи не написан метод Attacks. Значит, при вызове виртуального метода берётся ближайшее переопределение.
Таким образом, определяя функцию на Animal, автоматически определяем функции на всех его предках.