Композиция вместо наследования
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy. Урок 41 из 70.
Наследование создаёт тесную связь между классами. Когда Car наследует от Vehicle, любое изменение в Vehicle может нарушить работу Car. Композиция предлагает более гибкую альтернативу: вместо наследования поведения класс содержит объекты, которые предоставляют это поведение.
Рассмотрим игрового персонажа, который может двигаться и атаковать. При использовании наследования можно создать сложную иерархию. При использовании композиции вы просто предоставляете персонажу необходимые ему компоненты:
public class MovementBehavior
{
public void Move() => Console.WriteLine("Moving...");
}
public class AttackBehavior
{
public void Attack() => Console.WriteLine("Attacking!");
}
public class Character
{
private MovementBehavior movement = new MovementBehavior();
private AttackBehavior attack = new AttackBehavior();
public void Move() => movement.Move();
public void Attack() => attack.Attack();
}Ключевое различие заключается в отношении. Наследование представляет отношение «является» (Собака является Животным), тогда как композиция представляет отношение «имеет» (Character имеет поведение movement). Композиция позволяет менять поведение во время выполнения, свободно объединять возможности и избегать проблемы хрупкого базового класса.
Используй наследование, когда существует настоящая иерархическая связь. Выбирай композицию, когда тебе нужна гибкость, когда поведения могут совместно использоваться несвязанными классами или когда ты хочешь динамически изменять поведение.
Задание
ЛегкоДавайте создадим систему уведомлений, которая демонстрирует преимущество композиции перед наследованием. Вместо создания сложной иерархии классов уведомлений вы соберёте NotificationSender, передав ему взаимозаменяемые компоненты поведения.
Вы организуете код в трёх файлах:
Behaviors.cs: создайте два класса поведения в пространстве имёнNotifications, которые отвечают за разные аспекты отправки уведомлений:FormattingBehavior— содержит методFormat(string message), который возвращает сообщение, обёрнутое в[ALERT]в начале и[END]в концеDeliveryBehavior— содержит методDeliver(string channel, string message), который возвращает строку в форматеSending via {channel}: {message}
NotificationSender.cs: создайте классNotificationSenderв пространстве имёнNotifications, использующий композицию для объединения поведений. Ваш отправитель должен:- содержать экземпляры обоих классов —
FormattingBehaviorиDeliveryBehavior— в качестве закрытых полей - иметь свойство
Channel(string), в котором хранится название канала доставки - иметь конструктор, который принимает название канала
- иметь метод
Send(string message), который сначала форматирует сообщение с помощью поведения форматирования, затем доставляет его с помощью поведения доставки и возвращает конечный результат
- содержать экземпляры обоих классов —
Program.cs: в основном файле создайтеNotificationSenderс каналом, полученным из ввода, затем отправьте сообщение, полученное из ввода. Выведите результат, чтобы показать, как составные поведения работают вместе.
Вы получите два входных значения:
- название канала доставки (например,
Email,SMS) - сообщение для отправки
Выведите результат вызова Send() у отправителя уведомлений.
Например, если входными значениями являются Email и Server is down, результат должен быть таким:
Sending via Email: [ALERT] Server is down [END]Обратите внимание, что NotificationSender не наследуется ни от чего — он просто «содержит» поведения форматирования и доставки. Такой подход с использованием композиции означает, что вы легко сможете заменить любое из поведений, не изменяя класс отправителя, или повторно использовать эти поведения в совершенно других классах!
Попробуйте сами
using System;
using Notifications;
class Program
{
public static void Main(string[] args)
{
// Считать ввод
string channel = Console.ReadLine();
string message = Console.ReadLine();
// TODO: Создать NotificationSender с каналом
// TODO: Отправить сообщение и вывести результат
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методовПотренируйтесь самостоятельно: Онлайн-компилятор C#