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();
}

Ключевое различие заключается в типе отношений. Наследование представляет собой связь «is-a» (собака является животным), в то время как композиция представляет собой связь «has-a» (у персонажа есть поведение движения). Композиция позволяет менять поведение во время выполнения, свободно комбинировать возможности и избегать проблемы хрупкого базового класса.

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

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Проверьте себя

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

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