Принцип открытости/закрытости

Пытаюсь понять принцип открытости/закрытости.

Размышляю над этим примером

Пример плохого кода:

public void addButton(string os) 
{
    var creator = new Creator();
    if (os == "linux")
    {
        creator.createLinuxButton();
    }
    else if (os == " windows")
    {
        creator.createWindowsButton();
    }
}

Очевидно что при добавлении других os придётся дописывать новые else if и нарушать принцип. Лучшим выходом будет паттерн абстрактная фабрика. Однако она не помогает избавиться от расширения через else if, а лишь переносит эту конструкцию в метод уровнем выше

//метод более высокого уровня вызывающий addButton
if (os == "linux") 
{
   var creator = new LinuxCreator();
}
else if (os == "windows")
{
   var creator = new WindowsCreator();
}
addButton(creator);

В итоге при добавлении os опять придётся дописывать код в уже созданный метод и добавлять else if, принцип нарушен?


Ответы (1 шт):

Автор решения: aepot

Без лишних слов, починить это можно просто если унести код выбора из метода создания кнопки в точку сборки приложения.

Допустим, фабричный метод уже есть, и есть абстракция Creator и наследники WindowsCreator и LinuxCreator, и есть метод создания кнопки, перепишу его немного.

public Button CreateButton(string os)
{
    Creator creator = os switch
    {
        "windows" => new WindowsCreator(),
        "linux" => new LinuxCreator(),
        _ => throw new PlatformNotSupportedException()
    };
    return creator.CreateButton();
}

Чтобы не нарушать OCP, достаточно унести это в единую точку компоновки приложения, например:

public class Program
{
    public static Creator GetCreator()
    {
        string os = GetOsType(); // определяет ОС
        return os switch
        {
            "windows" => new WindowsCreator(),
            "linux" => new LinuxCreator(),
            _ => throw new PlatformNotSupportedException()
        };
    }

    // ...
}

Тогда метод CreateButton и использующие его методы не придется никогда переписывать

private readonly Creator _creator = Program.GetCreator();

public Button CreateButton()
{
    return _creator.CreateButton();
}

А для поддержки новой ОС потребуется лишь создать еще одну реализацию Creator и добавить одну строчку в точку компоновки приложения, например:

"macos" => new MacosCreator(),

Быстро и просто. Особенно это ощутится, если у вас не только кнопки, а пара сотен других контролов и штук 5 поддерживаемых платформ.


И в этом месте вы подходите к изучению шаблона проектирования "инверсия управления" (IoC, Inversion of Control).

→ Ссылка