Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Н.Р. Бухараев (3 семестр)(ООП в интегрированной...doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.41 Mб
Скачать

Полиморфизм Полиморфизм как уточнение семантики типа переменной

Полиморфизм (многообразие) - возможность уточнения (изменения) не только значений, состояний объекта, но и поведения объекта, то есть связанных с ним функций изменения состояния или алгоритмов реализации (изменение (уточнение) фактического типа переменной в ходе выполнения программы).

Описание типа переменной теперь означает лишь присвоение начального типа по умолчанию.

Тип

Строгая типизация – алгоритмика (типы минимизируют ошибки алгоритмов). Поэтому тип переменной описывается до появления переменной. Строгая типизация ориентирована на создание надёжных программ.

Бестиповая логика - свободная типизация (например, языки низкого уровня (ассемблер)). Свободная типизация ближе к системному программированию (ближе к эффективным реализациям)

Полиморфизм радикально изменяет взгляд на понятие типа. Если раньше переменная принадлежала одному фиксированному типу, то теперь она изменяет его, то есть тип становится атрибутом переменной.

Полиморфное программирование – сложное программирование ещё и потому, что не во всём адекватно поддерживается реализация.

Пример:

Пусть 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 используется для выполнения определенных типов преобразований между совместимыми ссылочными типами.

Точно так же дело обстоит с методами, которые есть у сына, но нет у отца.

Изменяются ли значения, то есть реализация одноимённых функций?

  1. Да, если таковые при объявлении класса cDad объявлены как виртуальные (virtual) или динамические (dynamic) (одна семантика, разная реализация), а при определении класса cSon – как переписывающие метод предка (override).

  2. Нет, если методы предка объявлены как статические (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, автоматически определяем функции на всех его предках.