Композиция вместо наследования
Часть раздела Объектно-ориентированное программирование путешествия по 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();
}Ключевое различие заключается в типе отношений. Наследование представляет собой связь «is-a» (собака является животным), в то время как композиция представляет собой связь «has-a» (у персонажа есть поведение движения). Композиция позволяет менять поведение во время выполнения, свободно комбинировать возможности и избегать проблемы хрупкого базового класса.
Используйте наследование, когда существует подлинная иерархическая связь. Выбирайте композицию, когда вам нужна гибкость, когда поведение может разделяться между несвязанными классами или когда вы хотите динамически изменять поведение.
Задание
ЛегкоДавайте создадим систему уведомлений, которая демонстрирует мощь композиции над наследованием. Вместо создания сложной иерархии классов уведомлений, вы скомпонуете 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 ни от чего не наследуется — он просто «имеет» поведения форматирования и доставки. Такой подход с использованием композиции означает, что вы можете легко заменить любое поведение, не меняя класс отправителя, или повторно использовать эти поведения в совершенно других классах!
Шпаргалка
Композиция — это альтернатива наследованию, которая позволяет создавать более гибкий код за счет того, что класс содержит объекты, обеспечивающие поведение, вместо того чтобы наследовать поведение от родительского класса.
Наследование против композиции:
- Наследование представляет отношения типа «является» (Собака является Животным)
- Композиция представляет отношения типа «имеет» (Персонаж имеет поведение движения)
Преимущества композиции:
- Смена поведения во время выполнения
- Свободное комбинирование возможностей
- Избежание сильной связанности и проблем хрупкого базового класса
- Совместное использование поведения в несвязанных классах
Пример с компонентами поведения:
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();
}Когда использовать каждый из подходов:
- Используйте наследование для подлинных иерархических отношений
- Выбирайте композицию для гибкости, общего поведения или динамического изменения поведения
Попробуйте сами
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'Необязательные параметрыИменованные аргументыПерегрузка методов