7

我一直在研究开放封闭原则,听起来不错,所以我想练习它的教义。我看着将我新发现的知识应用到现有项目中,并立即变得有点卡住了。

如果出现一个新的 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

4 回答 4

8

与任何“原则”OCP 一样,并非您在所有场合都必须遵守的规则。

我们被告知“优先组合而不是继承”,但装饰器和组合等模式公开促进了继承。

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

您的解决方案是一个经典的工厂习语(如果不是完整的工厂方法或抽象工厂模式)。这就是它的意图。尝试对其应用 OCP 是没有意义的。

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

于 2011-08-22T15:21:49.443 回答
5
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构建

于 2011-08-22T15:10:26.693 回答
4

我会做如下:

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()
   {
   }
}

然后不是每次添加新用户类型时都需要修改的方法和 switch case,只需调用:

user.buildTree();

然后,每当您需要添加新的用户类型时,您就可以扩展代码而不是修改。您只需为新用户类型添加一个新类,而无需触及以前的类。

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

于 2018-10-12T11:00:43.120 回答
2

要消除类型转换,您必须将职责移回需要特定类型操作的类型。这种类型,在您的情况下是“用户”,拥有关于他自己的所有信息,并且可以根据这些知识轻松调用正确的操作。你必须利用继承。

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

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

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

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

于 2018-05-04T13:12:10.150 回答