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