Menu
Coddy logo textTech

Композиция вместо наследования

Часть раздела Объектно-ориентированное программирование путешествия по 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). Композиция позволяет менять поведение во время выполнения, свободно объединять возможности и избегать проблемы хрупкого базового класса.

Используй наследование, когда существует настоящая иерархическая связь. Выбирай композицию, когда тебе нужна гибкость, когда поведения могут совместно использоваться несвязанными классами или когда ты хочешь динамически изменять поведение.

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, которая демонстрирует преимущество композиции перед наследованием. Вместо создания сложной иерархии классов уведомлений вы соберёте 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: Отправить сообщение и вывести результат
        
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C#