Принцип Open-Closed (OCP) — один из пяти принципов SOLID, которые должен знать каждый разработчик программного обеспечения. OCP утверждает, что программные объекты (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации. Это означает, что вы должны иметь возможность расширять поведение программного объекта без изменения его исходного кода.

Unity — популярный движок для разработки игр, использующий C# в качестве основного языка программирования. В Unity вы можете создавать игровые объекты и добавлять к ним поведение с помощью скриптов C#. Давайте рассмотрим пример, в котором у нас есть игровой объект, который перемещается в ответ на действия пользователя. Мы можем создать сценарий C#, который обрабатывает поведение игрового объекта при движении.

Без использования OCP мы могли бы написать такой скрипт:

public class PlayerController : MonoBehaviour
{
    private Rigidbody2D rb;
    public float speed = 5f;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector2 movement = new Vector2(moveHorizontal, moveVertical);

        rb.AddForce(movement * speed);
    }
}

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

Чтобы применить OCP, мы можем использовать паттерн «Стратегия». Шаблон стратегии — это шаблон проектирования, который позволяет нам определить семейство алгоритмов, инкапсулировать каждый из них и сделать их взаимозаменяемыми. В нашем случае мы можем определить интерфейс стратегии движения, который определяет поведение игрового объекта. Затем мы можем реализовать несколько стратегий перемещения, которые можно переключать во время выполнения без изменения существующего кода.

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

public interface IMovementStrategy
{
    void Move(Rigidbody2D rb);
}

public class UserInputMovement : IMovementStrategy
{
    public float speed = 5f;

    public void Move(Rigidbody2D rb)
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector2 movement = new Vector2(moveHorizontal, moveVertical);

        rb.AddForce(movement * speed);
    }
}

public class PathFollowingMovement : IMovementStrategy
{
    public List<Vector2> path;
    public float speed = 5f;

    public void Move(Rigidbody2D rb)
    {
        Vector2 direction = (path[0] - rb.position).normalized;

        rb.AddForce(direction * speed);
    }
}

public class PlayerController : MonoBehaviour
{
    private Rigidbody2D rb;
    public IMovementStrategy movementStrategy;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        movementStrategy = new UserInputMovement();
    }

    void FixedUpdate()
    {
        movementStrategy.Move(rb);
    }

    public void SwitchMovementStrategy(IMovementStrategy newStrategy)
    {
        movementStrategy = newStrategy;
    }
}

В этом рефакторинге скрипта мы определяем интерфейс IMovementStrategy, который определяет поведение игрового объекта. Затем мы создаем два класса, которые реализуют интерфейс IMovementStrategy: UserInputMovement и PathFollowingMovement. Класс UserInputMovement обрабатывает движение в ответ на ввод пользователя, а класс PathFollowingMovement обрабатывает движение, следуя заранее определенному пути.

Класс PlayerController теперь имеет переменную-член movementStrategy типа IMovementStrategy, которая может содержать любую реализацию интерфейса IMovementStrategy. В методе Start() мы инициализируем movementStrategy экземпляром класса UserInputMovement. В методе FixedUpdate() мы вызываем метод Move() текущего экземпляра movementStrategy для перемещения игрового объекта.

Чтобы переключать стратегию движения во время выполнения, мы добавили метод SwitchMovementStrategy(), который принимает экземпляр IMovementStrategy и устанавливает movementStrategy в новый экземпляр.

Используя паттерн «Стратегия», мы сделали наш код открытым для расширения, но закрытым для модификации. Мы можем легко добавить новое поведение движения, реализовав интерфейс IMovementStrategy и передав экземпляр новой реализации методу SwitchMovementStrategy().

Чтобы продемонстрировать преимущества использования OCP, давайте рассмотрим пример без использования OCP. Предположим, у нас есть игровой объект, который движется в ответ на действия пользователя, но мы также хотим, чтобы он двигался быстрее, когда игрок берет бонус. Без использования OCP мы можем изменить существующий скрипт следующим образом:

public class PlayerController : MonoBehaviour
{
    private Rigidbody2D rb;
    public float speed = 5f;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector2 movement = new Vector2(moveHorizontal, moveVertical);

        rb.AddForce(movement * speed);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            speed *= 2;
        }
    }
}

В этом модифицированном скрипте мы добавили проверку клавиши пробела и удваивали переменную speed, если она была нажата. Хотя это работает, это нарушает OCP, потому что мы изменили существующий код, а не расширили его.

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

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

Если вы прочитаете здесь, мы будем очень признательны за подписку.
Вы можете найти учебник на YouTube здесь https://youtu.be/AnlHnsYr5Uo