添加新字段(和/或方法)是不是会破坏 OCP(开放封闭原则)?
Posted
技术标签:
【中文标题】添加新字段(和/或方法)是不是会破坏 OCP(开放封闭原则)?【英文标题】:Does adding new fields (and/or methods) break OCP (open closed principle)?添加新字段(和/或方法)是否会破坏 OCP(开放封闭原则)? 【发布时间】:2016-10-31 03:14:21 【问题描述】:假设我有一个要导入数据库的 XML 文件结构:
<Flight>
<FlightName>FN 7777</FlightName>
<Passengers>
<American>
<FirstName>Michael</FirstName>
<LastName>Smith</LastName>
</American>
<American>
<FirstName>Jack</FirstName>
<LastName>Brown</LastName>
</American>
<German>
<FirstName>Hans</FirstName>
<LastName>Schaefer</LastName>
</German>
<Ukranian>
<FirstName>Sergei</FirstName>
<LastName>Osipenko</LastName>
<CanSpeakRussian>true</CanSpeakRussian>
</Ukranian>
</Passengers>
</Flight>
根据最初的要求,我创建了这个类结构:
public class Flight
public Flight()
Passengers = new List<IPassenger>();
public string FlightNr get; set;
public List<IPassenger> Passengers get; set;
public void SomeMethod()
...
public interface IPassenger
string FirstName get; set;
string LastName get; set;
public class German : IPassenger
public string FirstName get; set;
public string LastName get; set;
public class American : IPassenger
public string FirstName get; set;
public string LastName get; set;
public class Ukranian : IPassenger
public string FirstName get; set;
public string LastName get; set;
public bool CanSpeakRussian get; set;
在项目的第二个版本中,我们有 3 个新需求:
-
航班必须有目的地节点
所有乘客都必须有护照号码
所有美国乘客都必须有 SSN(社会安全号码)
这些需求的 XML 结构是:
<Flight>
<FlightName>FN 7777</FlightName>
<Destination>Chicago</Destination>
<Passengers>
<American>
<FirstName>Michael</FirstName>
<LastName>Smith</LastName>
<PassportNr>US123456</PassportNr>
<SSN>123-45-6789</SSN>
</American>
<American>
<FirstName>Jack</FirstName>
<LastName>Brown</LastName>
<PassportNr>US556699</PassportNr>
<SSN>345-12-9876</SSN>
</American>
<German>
<FirstName>Hans</FirstName>
<LastName>Schaefer</LastName>
<PassportNr>DE112233</PassportNr>
</German>
<Ukranian>
<FirstName>Sergei</FirstName>
<LastName>Osipenko</LastName>
<CanSpeakRussian>true</CanSpeakRussian>
<PassportNr>UK447788</PassportNr>
</Ukranian>
</Passengers>
</Flight>
问题 1: 如果我将代码结构更改如下,是否违反了 SOLID 的打开关闭原则?
public class Flight
public Flight()
Passengers = new List<IPassenger>();
public string FlightNr get; set;
public string Destination get; set;
public List<IPassenger> Passengers get; set;
public interface IPassenger
string FirstName get; set;
string LastName get; set;
string PassportNr get; set;
public class German : IPassenger
public string FirstName get; set;
public string LastName get; set;
public string PassportNr get; set;
public class American : IPassenger
public string FirstName get; set;
public string LastName get; set;
public string PassportNr get; set;
public string SSN get; set;
public class Ukranian : IPassenger
public string FirstName get; set;
public string LastName get; set;
public bool CanSpeakRussian get; set;
public string PassportNr get; set;
问题 2: 你认为我应该为这个结构使用抽象类以获得更好和更短的代码吗?如果是,对可测试性有负面影响吗?
public class Flight
public Flight()
Passengers = new List<Passenger>();
public string FlightNr get; set;
public string Destination get; set;
public List<Passenger> Passengers get; set;
public abstract class Passenger
public string FirstName get; set;
public string LastName get; set;
public string PassportNr get; set;
public class German : Passenger
public class American : Passenger
public string SSN get; set;
public class Ukranian : Passenger
public bool CanSpeakRussian get; set;
【问题讨论】:
你在担心什么,跟随 SOLID 到牙齿或完成你的工作要求?你有的是一个新的要求。添加属性不违反任何规则。即使您确实有您的代码的消费者,谁引用您的代码,向类添加属性也不会受到伤害,除非您更改接口 [正如您所发现的那样]。您需要问自己一个问题,谁在使用您的代码?如果这只是内部代码并且您可以控制完整的基础 - 您可以做任何事情。如果想在很多情况下支持向后兼容,就需要使用抽象类。 @T.S.实际上,我只是想了解开放/封闭原则。我准备了这个例子来理解边界。据我从几篇文章中了解到,即使是 Jon Skeet 也对这一原则存在一些问题 (refer this link)。 所有编程模式、原则和教条都有批评。您实际上不必遵循这些中的任何一个。但是,如果您向某人解释您拥有使用 P 模式开发的子系统的工作原理,那么这很有用,bla-bla,他们会理解您。只需查看 .NET 框架即可更好地理解 o-c 原理。 .NET 在很多情况下都有接口和抽象类。因此,您可以编写完全自定义的实现对象或依赖给您的基本实现。但这取决于你,你想让你的对象打开还是关闭 - 密封类。 如果您为您的消费者编写可扩展框架,以便他们可以自定义基础产品的行为 - 这就是您需要根据 o/c 原则处理事情的时候。如果您有不希望任何人扩展的代码,则无需担心。这是底线 @T.S.我的目的不是讨论 SOLID、DRY、YAGNI 或其他是好还是坏。我想要的只是了解 OCP 原则的边界。问题很简单。添加一些字段和/或方法是否打破了开放封闭原则?对扩展开放但接近修改意味着什么?添加字段和方法是扩展还是修改? 【参考方案1】:引用 Robert Martin 的敏捷原则、模式和实践(必读):
符合 OCP 的模块有两个主要属性。
它们可以扩展。这意味着该行为 模块可以扩展。随着应用需求的变化, 我们可以用新的行为来扩展模块,以满足那些 变化。换句话说,我们能够改变模块的作用。
它们已关闭以进行修改。扩展模块的行为 不会导致对源代码或二进制代码的更改 模块。模块的二进制可执行版本——无论是在 可链接库、DLL 或 .EXE 文件——保持不变。
(请注意,当它说“模块”时,它并不一定意味着某种程序集,还包括较小规模的东西,例如类和编译单元。)
这里重要的是它与行为有关。你所拥有的是数据结构的层次结构,或多或少没有任何行为。就目前而言,很难说您的代码是否违反 OCP,因为它并不完全适用。
违反 OCP 通常伴随着一些基于类型的开关或条件。 Flight
类的任何行为是否取决于它所拥有的乘客类型?如果是这样,它可能有不同的形状,例如:
if (passenger is Kefiristani)
performSuperStrictSecurityChecks(passenger);
或者把这个丑陋的代码移到Passenger
类的performSecurityChecks
方法中,然后在Flight
类中就行了
passenger.performSecurityChecks(); // non-virtual call
或者只是用 OOP 的方式来做,万岁多态!
passenger.performSecurityChecks(); // virtual call
现在假设出现了一个新类别的乘客Tumbombalooni
,它也需要超严格的安全检查。在前两种情况下,您必须在新类之外更改一些代码,而这正是它们未关闭的含义。在最后一种情况下,您无需更改任何内容.
第一个例子违反了 SRP。如果您的类不是内部类,那么它也可能在程序集级别违反 OCP,并且您的程序集之外的人 可能会扩展它们,现在他们必须更改您的代码以使其工作。如果是内部的,那么是否违反OCP是有争议的,但违反SRP的情况更糟。
第二个例子肯定违反了OCP。扩展类不应强制派生类的作者更改基类中的任何内容。事实上,他们可能做不到。
最后一个例子没有违反 OCP。该类仍然是可扩展的,并且扩展不需要修改,只需编写 new 代码。这就是 OCP 的全部意义所在。
回答您最初的问题:添加新字段(和/或方法)是否会破坏 OCP?不,它本身不会。但是,当在派生类中添加或更改某些内容会迫使您对基类进行一些更改时,OCP 就会被破坏。
关于 OCP 的一件事是,几乎不可能永远不破坏它。派生类的某些要求可能会迫使您更改基类中的某些内容。如果你为每一个可能的改变提前计划,你就会冒着过度设计一切的风险,然后你没有想到的东西就会出现并咬你。但是当它发生时,你可以用不同的方式来处理它,最好只在基类中添加一个虚方法,然后在新的派生类中重新实现这个方法,而不是一次又一次地陷入同一个陷阱,直到您的代码实际上变得无法维护。
【讨论】:
最后一段最好。一切都归结为“谁是代码管理员”,并且它是否被设计为从一开始就进行扩展。达? 我知道这是很久以前的帖子,但也许有人可以帮助我。如果我必须添加一个新的数据库列。治理网络改变了我们需要将新属性持久化到数据库中的规律。修改实体就OK了?以上是关于添加新字段(和/或方法)是不是会破坏 OCP(开放封闭原则)?的主要内容,如果未能解决你的问题,请参考以下文章