打开/关闭原理 - 如何处理此交换机?

时间:2011-08-22 15:03:18

标签: c# open-closed-principle

我一直在研究开放的封闭原则,听起来不错,所以我想练习它的教导。 我考虑将我新发现的知识应用到现有项目中,并立即陷入困境。

如果出现新的UserType(这很可能),则需要更改,尚未关闭修改。怎么能绕过这个?

从我所看到的,听起来我应该在这里实施工厂而不是应用OCP?

Factory which breaks Open-closed principle

 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 个答案:

答案 0 :(得分:6)

与任何“原则”一样,OCP不是您必须遵守的规则。

我们被告知'赞成组合而不是继承',然而装饰和复合等模式公开地促进了继承。

同样地,我们被告知'编程到一个接口,而不是一个实现,然而,在我们的应用程序的某个时刻,我们将不得不实例化一些描述的具体对象。

您的解决方案是一种经典的工厂习惯用法(如果不是完整的工厂方法或抽象工厂模式)。这就是它的目的。试图将OCP应用于它是没有意义的。

事实上,通过创建此方法,您实际上可以在代码库的其他部分中促进OCP。现在,您的应用程序中的其他一些类或类可以遵循OCP,现在您已将创建分开了。

答案 1 :(得分:3)

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

构建可用构建器列表

答案 2 :(得分:0)

要消除类型切换,您必须将职责移回到需要特定于类型操作的类型。这种类型,在您的情况下是“用户”,具有关于他自己的所有信息,并且可以基于该知识容易地调用正确的操作。你必须利用继承。

在您的情况下,您必须通过简单继承或组合来反映用户类型。你的“用户”将拥有一个属性“UserType”,就像在你的例子中一样,但它不是使它只是一个类似“Enum”的类型,它变成了一个复杂的类型,它继承了一个“IUserType”接口并且知道如何构造它的特定依赖项(“UserType”实现“IUserType”)。 “IUserType”可以通过属性公开特定于类型的属性(例如,返回“ITypeSpecificTree”的“IUserType.TypeSpecificTree”)。

因此,在您的示例中,“User”被提升为premium,您只需将该属性设置为具体“IUserType”实现的新实例(例如,PremiumUserType“),该实现执行其特定操作,如构建高级树(您的示例中的“ITypeSpecificTree”实现)以及构建关联类型。

这样通过使用组合和继承来消除switch语句。我们将复杂的“UserType”属性转换为单独的类,然后将类型特定的职责移动到类型本身。 继承,特别是依赖性反转有助于对对象进行操作(例如,在不知道具体类型的情况下获取用户类型特定信息,如(User.IUserType.IUserSpecificTree))。这有助于确保我们打开扩展。继承还有助于封装类型特定的行为,使我们的代码关闭以进行修改

如果我们需要更改特定类型树的生成方式或此用户类型的行为方式,我们只会触及相关的“IUserType”实现,但不会触及“用户”。如果添加了新的用户类型(扩展名),则必须实现基本接口“IUserType”,并且不得触及其他代码(如switch语句)以使其工作,并且不再需要进行类型检查。 为了使其完整并提供更多可扩展性,“用户”类还应实现一个接口,例如暴露用户类型的“IUser”(例如“IUser.IUserType”)。

答案 3 :(得分:0)

我将执行以下操作:

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

这样,每当需要添加新用户类型时,都可以扩展代码而不是进行修改。您只需为新的用户类型添加一个新类别,而无需接触先前的类别。

这就是他们所说的“开放封闭”,当您可以处理它时,为什么要违反它呢?