添加新字段(和/或方法)是否会破坏OCP(开放封闭原则)?

时间:2016-10-31 03:14:21

标签: c# interface abstract-class solid-principles open-closed-principle

假设我有一个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个新要求:

  1. 航班必须有目的地节点
  2. 所有乘客必须持有护照号码
  3. 所有美国乘客必须拥有SSN(社会安全号码)
  4. 这些要求的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的Open Close原理?

    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; }
    }
    

1 个答案:

答案 0 :(得分:1)

Robert Martin的敏捷原则,模式和实践引用(必读):

  

符合OCP的模块有两个主要属性。

     

他们是开放的扩展。这意味着行为了   模块可以扩展。随着应用程序的要求发生变化,   我们可以使用满足这些要求的新行为扩展模块   变化。换句话说,我们可以改变模块的功能。

     

他们因修改而关闭。扩展模块的行为   不会导致更改源代码或二进制代码   模块。模块的二进制可执行版本 - 无论是在   可链接库,DLL或.EXE文件保持不变。

(注意,当它表示“模块”时,它不一定意味着某种类型的程序集,而且还意味着类和编译单元等较小规模的程序集。)

这里重要的是它与行为有关。你所拥有的是数据结构的层次结构,或多或少没有任何行为。因此,很难说你的代码是否违反OCP,因为它不完全适用。

违反OCP通常会与某些基于类型的开关或条件一起出现。 Flight班级的任何行为是否取决于它拥有的乘客类型?如果是这样,它可能会有不同的形状,例如:

if (passenger is Kefiristani) {
    performSuperStrictSecurityChecks(passenger);
}

或将这个丑陋的代码移到performSecurityChecks类的Passenger方法中,然后在Flight类中执行

passenger.performSecurityChecks(); // non-virtual call

或者只是按照OOP的方式进行,欢呼多态!

passenger.performSecurityChecks(); // virtual call

现在假设出现了一类新的乘客Tumbombalooni,这也需要超级严格的安全检查。在前两种情况下,您必须更改新类之外的某些代码,而这正是它们未关闭的含义。在最后一种情况下,您不必更改任何内容

第一个例子违反了所有事情的SRP。它可能也违反了程序集级别的OCP,如果您的类不是内部的,并且程序集的外部可能会扩展它们,现在他们必须更改代码才能使其正常工作。如果它们是内部的,那么它是否违反OCP是有争议的,但无论如何违反SRP更糟糕。

第二个例子肯定违反了OCP。扩展类不应强制派生类的作者更改基类中的任何内容。事实上,他们可能无法做到。

最后一个示例不违反OCP。该类仍然是可扩展的,扩展不需要修改,只需编写 new 代码。这就是OCP的全部意义。

回答您的原始问题:添加新字段(和/或方法)会破坏OCP吗?不,它本身没有。但是,当在派生类中添加或更改某些内容时,会强制您对基类进行一些更改,然后OCP就会中断。

关于OCP的一点是,它根本不可能永远不会破坏它。对派生类的某种要求可能会迫使你改变基类中的某些东西。如果你提前做好每一个可能的改变计划,那么你就有可能过度设计所有的东西,然后你想到的东西来了,无论如何都会咬你。但是当它发生时,你可以用不同的方式处理它,最好只在基类中添加一个虚方法,然后在新的派生类中重新实现这个方法,而不是一次又一次陷入同一个陷阱,直到你的代码实际上变得无法维护。