Открытый / Закрытый Принцип - Как бороться с этим Переключателем? - PullRequest
5 голосов
/ 22 августа 2011

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

Если появляется новый UserType (и это очень вероятно), его нужно будет изменить, он еще не закрыт для модификации. Как можно обойти это?

Из того, что я прочитал, звучит так, как будто я должен был реализовать здесь фабрику вместо применения OCP?

Фабрика, которая нарушает принцип Открыто-закрыто

 private void BuildUserTree(User user)
    {
        switch (user.UserType)
        {
            case UserType.FreeLoader:
                BuildFreeLoaderTree();
                break;
            case UserType.Premium:
                BuildPremiumTree();
                break;
            case UserType.Unlimited:
                BuildUnlimitedTree();
                break;
            default:
                throw new Exception("No UserType set");

        }
    }

Спасибо, Кохан

Ответы [ 4 ]

7 голосов
/ 22 августа 2011

Как и любой «принцип», OCP не является правилом, которому вы должны подчиняться во всех случаях.

Нам говорят: «Подари композицию предпочтительнее наследования», и все же такие шаблоны, как декоратор и композит, открыто способствуют наследованию.

Точно так же нам говорят: «Программируйте интерфейс, а не реализацию, и все же, в какой-то момент в нашем приложении нам нужно будет создать конкретный объект некоторого описания.

Ваше решение - это классическая фабричная идиома (если не совсем фабричный метод или абстрактный фабричный шаблон). Это то, что он должен делать. Попытка применить OCP к нему не имеет смысла.

На самом деле, создавая этот метод, вы фактически можете использовать OCP в какой-то другой части вашей кодовой базы. Некоторые другие классы или классы в вашем приложении теперь могут подчиняться OCP, теперь вы разделили создание.

3 голосов
/ 22 августа 2011
internal class UserTreeBuilder
{
    [ImportMany(AllowRecomposition=true)]
    public IEnumerable<IBuilder> Builders{ get; set; }

    public UserTreeBuilder()
    {
        // Load all builders from a MEF CompositionContainer
    }

    public void BuildUserTree(User user)
    {
        var builder = Builders.FirstOrDefault(b => b.CanHandleUserType(user.UserType));

        if(builder == null)
        {
            throw new Exception("No UserType set");
        }else{
            builder.BuildTree();
        }
    }
}

Список доступных сборщиков может быть построен с использованием MEF

0 голосов
/ 12 октября 2018

Я бы сделал, как показано ниже:

abstract class User {
   .
   .
   .
   abstract public void buildTree
}

class FreeLoaderUser: User {
   override public void buildTree()
   {
   }
}

class PremiumUser: User {
   override public void buildTree()
   {
   }
}

 class UnlimitedUser: User {
   override public void buildTree()
   {
   }
}

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

user.buildTree();

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

это то, что они называют открытыми закрытыми, и когда вы можете справиться с этим, зачем вам нарушать его?

0 голосов
/ 04 мая 2018

Чтобы исключить переключение типа, вы должны переместить обязанности обратно к типу, который требует определенного типа действия.Этот тип, в вашем случае «Пользователь», имеет всю информацию о себе и может легко вызвать правильную операцию на основе этих знаний.И вы должны использовать наследство.

В вашем случае вам придется отражать пользовательские типы с помощью простого наследования или с помощью композиции.Ваш «пользователь» будет иметь свойство «UserType», как в вашем примере, но вместо того, чтобы делать его просто как тип «Enum», он становится сложным типом, который наследует интерфейс «IUserType» и знает, как создать его конкретныйзависимости (UserType реализует IUserType).«IUserType» может предоставлять атрибуты, специфичные для типа, через свойство (например, «IUserType.TypeSpecificTree», которое возвращает «ITypeSpecificTree»).

Так что, когда в вашем примере «Пользователь» повышен до «премиум», вы простоустановите для свойства новый экземпляр конкретной реализации «IUserType» (например, PremiumUserType »), которая выполняет свои конкретные действия, такие как построение премиального дерева (реализация« ITypeSpecificTree ») из вашего примера, а также создание связанных типов.

Таким образом, оператор switch устраняется с помощью композиции и наследования. Мы преобразовали сложное свойство «UserType» в отдельный класс, а затем перенесли специфические для типа обязанности в сам тип. Наследование и особенно инверсия зависимостей помогли работать с объектом(например, получение информации о типе пользователя, например (User.IUserType.IUserSpecificTree ") без знания конкретного типа. Это помогло убедиться, что мы открыты для расширения . НаследоватьКроме того, ce помог инкапсулировать поведение, специфичное для типа, чтобы сделать наш код закрытым для модификации .

Если нам нужно внести изменения в то, как генерируется дерево, специфичное для типа, или как ведет себя этот тип пользователя, мыбудет касаться только связанной реализации «IUserType», но не «пользователь».Если добавляются новые типы пользователей (расширение), им необходимо будет реализовать базовый интерфейс «IUserType», и никакой другой код, например операторы-переключатели, не должен затрагиваться, чтобы заставить его работать, и больше никаких проверок типов не требуется.И чтобы сделать его завершенным и предложить некоторую более расширяемость, класс «Пользователь» должен также реализовать интерфейс, например «IUser», который предоставляет тип пользователя (например, «IUser.IUserType»).

...