开/关原则——如何处理这个开关?
Posted
技术标签:
【中文标题】开/关原则——如何处理这个开关?【英文标题】:Open / Closed Principle - How to deal with this Switch? 【发布时间】:2011-08-22 15:03:18 【问题描述】:我一直在研究开放封闭原则,听起来不错,所以我想实践它的教义。 我打算将我新发现的知识应用到现有项目中,但马上就有点卡住了。
如果出现一个新的 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");
谢谢, 可汗
【问题讨论】:
【参考方案1】:就像任何“原则”一样,OCP 并不是您必须在所有场合都遵守的规则。
我们被告知“优先组合而不是继承”,但装饰器和组合等模式公开促进了继承。
类似地,我们被告知“编程到一个接口,而不是一个实现,然而,在我们的应用程序的某个时刻,我们将不得不实例化一个具有某种描述的具体对象。
您的解决方案是一个经典的工厂习语(如果不是完整的工厂方法或抽象工厂模式)。这就是它的意图。尝试对其应用 OCP 是没有意义的。
事实上,通过创建此方法,您实际上可以在代码库的其他部分中促进 OCP。现在,您的应用程序中的一些其他类或多个类现在可以遵循 OCP,因为您已经分离了创建。
【讨论】:
公平点,我也这么想。只是当您学习新事物时,您会走出去尝试并立即使用知识来支持它。我将继续寻找更合适的代码部分来重构。干杯。 不用担心。对我来说也是很好的思想锻炼。我也在学习模式的过程中:-) 虽然这些确实不是也不应该成为严格的规则,但开关通常是难闻的气味,并且其他设计/模式可能会受到青睐,这些设计/模式对 OCP 更加尊重而没有任何负面缺陷。同样,正如您提到的那样,我个人尝试在任何地方使用接口(实例化安全,有时甚至可以通过依赖注入等方式完成)。【参考方案2】: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 构建可用构建器的列表
【讨论】:
+1 表示动态工厂。见also。【参考方案3】:我会这样做:
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();
然后,每当您需要添加新的用户类型时,您就可以扩展代码而不是修改代码。您只需为新用户类型添加一个新类,而无需触及以前的类。
这就是他们所说的开放封闭,当您可以设法处理它时,为什么要违反它?
【讨论】:
让孩子自己实现需求是实现OCP的关键。 我认为主要问题是当孩子们刚刚添加新内容时。像你创建文档一样,你需要添加字段标题、作者、日期等。所以最后你有一篇文章。【参考方案4】:要消除类型切换,您必须将职责移回需要特定类型操作的类型。这种类型,在您的情况下是“用户”,拥有关于他自己的所有信息,并且可以根据这些知识轻松调用正确的操作。你必须利用继承。
在您的情况下,您必须通过简单的继承或组合来反映用户类型。您的“用户”将拥有一个属性“用户类型”,就像在您的示例中一样,但它不是仅将其设为类似“枚举”的类型,而是成为一个继承“IUserType”接口并知道如何构造其特定类型的复杂类型依赖项(“UserType”实现“IUserType”)。 “IUserType”可以通过属性公开类型特定的属性(例如,返回“ITypeSpecificTree”的“IUserType.TypeSpecificTree”)。
因此,当在您的示例中将“用户”提升为高级时,您只需将该属性设置为具体“IUserType”实现的新实例(例如,PremiumUserType”),该实例执行其特定操作,例如构建高级树(您的示例中的“ITypeSpecificTree”实现)以及构造关联类型。
这种方式通过使用组合和继承来消除 switch 语句。我们将复杂的“UserType”属性转换为一个单独的类,然后将类型特定的职责转移到类型本身。 继承,尤其是依赖倒置有助于对对象进行操作(例如,在不知道具体类型的情况下获取用户类型特定信息,如 (User.IUserType.IUserSpecificTree")。这有助于确保我们对扩展开放。继承还有助于封装特定于类型的行为,以使我们的代码关闭以进行修改。
如果我们需要更改特定类型树的生成方式或此用户类型的行为方式,我们将只触及相关的“IUserType”实现,而不触及“用户”。如果添加了新的用户类型(扩展),他们将必须实现基本接口“IUserType”,并且不需要任何其他代码(如 switch 语句)来使其工作,并且不需要更多类型检查。 为了使其完整并提供更多的可扩展性,“用户”类还应该实现一个接口,例如公开用户类型的“IUser”(例如“IUser.IUserType”)。
【讨论】:
以上是关于开/关原则——如何处理这个开关?的主要内容,如果未能解决你的问题,请参考以下文章