Способы расширения поведения классов в C#

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

1. Наследование (Inheritance)

Наследование позволяет создавать производные классы, которые расширяют или изменяют поведение базового класса.

Пример:

public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Animal is making a sound");
    }
}

public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Dog is barking");
    }
}

Когда использовать

  • Когда новый класс логически является подтипом существующего.
  • Когда требуется переопределение поведения базового класса.

2. Интерфейсы (Interfaces)

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

Пример:

public interface IMovable
{
    void Move();
}

public class Car : IMovable
{
    public void Move()
    {
        Console.WriteLine("Car is moving");
    }
}

Когда использовать

  • Когда необходимо описать поведение без привязки к конкретной реализации.
  • Когда требуется реализация нескольких интерфейсов.

3. Абстрактные классы (Abstract Classes)

Абстрактные классы позволяют определить базовое поведение с возможностью частичной или полной реализации методов.

Пример:

public abstract class Shape
{
    public abstract void Draw();
    
    public void ShowInfo()
    {
        Console.WriteLine("This is a shape");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a circle");
    }
}

Когда использовать

  • Когда требуется базовая функциональность для наследников.
  • Когда некоторые методы должны быть реализованы в производных классах.

4. Декоратор (Decorator Pattern)

Позволяет динамически добавлять новое поведение объектам без изменения их кода.

Пример:

public interface IMessage
{
    void Send();
}

public class SimpleMessage : IMessage
{
    public void Send()
    {
        Console.WriteLine("Sending a simple message");
    }
}

public class EncryptedMessage : IMessage
{
    private IMessage _message;

    public EncryptedMessage(IMessage message)
    {
        _message = message;
    }

    public void Send()
    {
        Console.WriteLine("Encrypting message...");
        _message.Send();
    }
}

Когда использовать

  • Когда требуется динамическое изменение поведения объекта.
  • Когда необходимо избегать жесткой привязки к конкретным классам.

5. Расширяющие методы (Extension Methods)

Позволяют добавлять методы к существующим классам без их изменения.

Пример:

public static class StringExtensions
{
    public static string ToUpperFirstLetter(this string str)
    {
        if (string.IsNullOrEmpty(str)) return str;
        return char.ToUpper(str[0]) + str.Substring(1);
    }
}

// Использование:
string name = "john";
Console.WriteLine(name.ToUpperFirstLetter()); // Output: John

Когда использовать

  • Когда необходимо добавить методы к сторонним или неизменяемым классам.
  • Когда требуется расширение стандартных типов (string, List<T> и др.).

6. Делегаты и события (Delegates & Events)

Позволяют изменять поведение класса через подписку на события.

Пример:

public class Button
{
    public event Action Clicked;

    public void Click()
    {
        Clicked?.Invoke();
    }
}

// Использование:
Button button = new Button();
button.Clicked += () => Console.WriteLine("Button clicked!");
button.Click();

Когда использовать

  • Когда требуется передавать функции в качестве параметров.
  • Когда необходимо реализовать механизм событий.

7. Стратегия (Strategy Pattern)

Позволяет подменять поведение объекта в зависимости от контекста.

Пример:

public interface IPaymentStrategy
{
    void Pay();
}

public class CreditCardPayment : IPaymentStrategy
{
    public void Pay()
    {
        Console.WriteLine("Paid with credit card");
    }
}

public class PayPalPayment : IPaymentStrategy
{
    public void Pay()
    {
        Console.WriteLine("Paid with PayPal");
    }
}

public class PaymentProcessor
{
    private IPaymentStrategy _strategy;

    public PaymentProcessor(IPaymentStrategy strategy)
    {
        _strategy = strategy;
    }

    public void ProcessPayment()
    {
        _strategy.Pay();
    }
}

Когда использовать

  • Когда требуется изменение алгоритма работы в зависимости от условий.
  • Когда необходимо инкапсулировать несколько способов выполнения задачи.