- Создавать производный класс и использовать ключевые слова
virtual,overrideиbase - Объяснять, зачем нужны абстрактные классы и абстрактные методы
- Определять интерфейс и реализовывать его в нескольких классах
- Объяснять полиморфизм на примерах
В игре есть собака, кошка и попугай. У всех есть имена, все умеют представляться, но каждый издаёт свой звук. Вместо того чтобы трижды копировать один и тот же код, мы выносим общую часть в базовый класс, а различия пишем в производных классах. Затем со всеми животными работаем в одном цикле одним и тем же кодом — это одна из самых сильных идей объектно-ориентированного программирования.
Наследование: virtual и override
Запись class Dog : Animal означает, что Dog наследуется от Animal: он получает все public-члены родителя. Метод, помеченный в базовом классе как virtual, производный класс может переопределить с помощью override. Ключевое слово base позволяет вызвать версию родителя. При первичных конструкторах аргумент передаётся конструктору родителя напрямую: : Animal(name).
List<Animal> pets = [new Dog("Rex"), new Cat("Mestan"), new Animal("Nemo")];
foreach (Animal pet in pets)
Console.WriteLine(pet.Introduce());
class Animal(string name)
{
public string Name => name;
public virtual string Speak() => "...";
public virtual string Introduce() => $"I am {Name} and I say {Speak()}";
}
class Dog(string name) : Animal(name)
{
public override string Speak() => "Woof!";
}
class Cat(string name) : Animal(name)
{
public override string Speak() => "Meow!";
public override string Introduce() => base.Introduce() + " (and ignores you)";
}I am Rex and I say Woof! I am Mestan and I say Meow! (and ignores you) I am Nemo and I say ...
Обрати внимание: тип списка — List<Animal>, но при вызове pet.Introduce() каждый объект использует свой метод Speak. То, что один и тот же вызов даёт разный результат в зависимости от настоящего типа объекта, называется полиморфизмом. Цикл ничего не знает о собаках, кошках или попугае, которого добавят позже, — и знать не должен.
Абстрактные классы
Иногда базовый класс сам по себе неполон: площадь «фигуры вообще» не вычислить, её можно найти только для конкретного прямоугольника или круга. Такой класс объявляют abstract. Создать из него объект нельзя (new Shape() даёт ошибку CS0144), а его abstract-методы без тела каждый производный класс обязан реализовать — иначе возникнет ошибка CS0534.
using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Shape[] shapes = [new Rectangle(4, 3), new Square(5), new Circle(1)];
foreach (Shape s in shapes)
Console.WriteLine($"{s.GetType().Name}: area {s.Area():F2}");
abstract class Shape
{
public abstract double Area();
}
class Rectangle(double width, double height) : Shape
{
public override double Area() => width * height;
}
class Square(double side) : Rectangle(side, side);
class Circle(double radius) : Shape
{
public override double Area() => Math.PI * radius * radius;
}Rectangle: area 12.00 Square: area 25.00 Circle: area 3.14
Square наследуется от Rectangle и берёт его метод Area в готовом виде — ему даже не понадобилось тело.Интерфейсы
Интерфейс — это контракт: он говорит, какие методы и свойства должны быть у класса, но не говорит, как они работают. Имена интерфейсов по традиции начинаются с буквы I: INotifier, IComparable. Класс принимает контракт, написав : INotifier, и обязан реализовать все его члены. Один и тот же интерфейс могут реализовывать даже совсем не связанные классы.
List<INotifier> notifiers =
[
new EmailNotifier("aysel@example.com"),
new SmsNotifier("+994 50 000 00 00")
];
foreach (INotifier n in notifiers)
n.Send("Your order has shipped!");
interface INotifier
{
void Send(string message);
}
class EmailNotifier(string address) : INotifier
{
public void Send(string message) => Console.WriteLine($"Email to {address}: {message}");
}
class SmsNotifier(string phone) : INotifier
{
public void Send(string message) => Console.WriteLine($"SMS to {phone}: {message}");
}Email to aysel@example.com: Your order has shipped! SMS to +994 50 000 00 00: Your order has shipped!
Если завтра понадобятся уведомления в Telegram, достаточно написать новый класс TelegramNotifier, реализующий INotifier, — цикл менять не придётся. Сам .NET полон интерфейсов: IComparable<T> позволяет сортировать объекты, а IEnumerable<T> — перебирать их в foreach.
| Критерий | Абстрактный класс | Интерфейс |
|---|---|---|
| Сколько можно унаследовать | только один | сколько угодно |
| Поля и состояние | могут быть | не бывает |
| Когда выбирать | у близких классов есть общий код | разные классы разделяют одну способность |
Главное
class Dog : Animal—Dogнаследуется отAnimalи получает его члены; базовый класс может быть только один.- Производный класс переопределяет
virtual-метод черезoverride;baseвызывает версию родителя. - Из
abstract-класса нельзя создать объект; его абстрактные методы реализуют производные классы. - Интерфейс — это контракт; класс может реализовать несколько интерфейсов.
- Полиморфизм: один и тот же вызов работает по-разному в зависимости от настоящего типа объекта.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.