打开/关闭原理-如何处理此开关

本文关键字:处理 开关 何处理 打开 | 更新日期: 2023-09-27 18:06:25

我一直在研究开闭原理,它听起来不错,所以我想实践它的教导。我想把我新发现的知识应用到现有的项目中,但现在有点停滞不前。

如果出现了一个新的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");
        }
    }

谢谢,Kohan

打开/关闭原理-如何处理此开关

就像任何"原则"一样,OCP并不是你在任何情况下都必须遵守的规则。

我们被告知"喜欢组合而不是继承",然而像decorator和composite这样的模式公开提倡继承。

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

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

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

我会做如下操作:

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

然后通过这种方式,无论何时需要添加新的用户类型,都可以扩展代码,而不是进行修改。您只需为新的用户类型添加一个新的类,而不需要接触以前的类。

这就是他们所说的开放-关闭,当你能够处理它的时候,你为什么要违反它?

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-MEF构建,从Codeplex 迁移

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

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

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

通过这种方式,通过使用组合和继承来消除switch语句。我们将复杂的"UserType"属性转换为一个单独的类,然后将特定于类型的责任转移到类型本身。继承,尤其是依赖关系反转有助于对对象进行操作(例如,获取用户类型特定的信息,如"user.IUserType.IUserSpecificTree"(而不知道具体类型。这有助于确保我们可以进行扩展。继承还有助于封装特定于类型的行为,使我们的代码关闭以进行修改

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