添加新字段(和/或方法)是不是会破坏 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(开放封闭原则)?的主要内容,如果未能解决你的问题,请参考以下文章

敏捷软件开发 – OCP 开放-封闭原则

在自动填充被破坏或禁用的站点上强制登录自动填充

软件设计原则

JAVA设计模式--工厂模式

OCP(开放封闭原则)与 IoC(控制反转)有啥关系?

为啥 Java 反射 API 允许我们访问私有和受保护的字段和方法?这不会破坏访问修饰符的目的吗? [复制]