我正在努力更好地理解开放/封闭原则。我熟悉参考资料,例如
和Jon Skeet对思想的探索,以及Protected Variation的相关概念。
我有一种挥之不去的感觉,我仍然没有理解开放/封闭原则的本质。我必须增加对一个概念的理解的一种方法是探索这个想法的否定或倒置。我很难想出一个违反开放/封闭原则的具体例子——我希望如果我们有这样的例子,我们可以指出它并说“看看以这种方式设计的不幸结果,如何如果我们开放/关闭,情况会好得多。”
所以,问题。你能举一个重要的例子吗,比如说,一个 Java 类对扩展关闭或对修改开放,为什么这会是一件坏事。
显然有一些微不足道的情况,例如使类最终化,因此禁止继承,但我认为这不是开放/封闭原则的核心。
通常,每当您看到可能性的枚举时,您很可能会看到违反开放/封闭原则的行为。
interface Drawing: {
addCircle(...);
addSquare(...);
// etc..
}
很明显,只要有人想支持一种新的形状,就必须修改这个接口和所有实现。
遵循开放/封闭原则,你可以这样做:
interface Drawing {
addShape(shape: IShape)
}
这里作者定义了一个接口来指定一个shape需要什么,调用者可以添加任何shape,只要实现了这个接口就可以了,不需要修改。
另一个例子可以是一个简单的计算器:
public enum Operation
{
Plus, Minus, Multiply, Divide
}
如果我们想添加一个新的操作,那么我们应该添加新的“if else”语句。违反开闭原则
public class Calculator
{
public decimal Execute(int a, int b, Operation operation)
{
if (operation == Operation.Minus)
return a - b;
else if (operation == Operation.Plus)
return a + b;
// so if we want to an new operation, then we should add new
// 'if else' statements. It is violation of Open closed principle
return a * b;
}
}
可以通过创建接口来改进:
public interface ICalculatorOperation
{
decimal Execute(int a, int b);
}
事实上,我们将依赖关系从较低级别的组件反转为较高级别的组件。 所以“低级”组件依赖于“高级”组件拥有的接口.
public class Calculator
{
public decimal Execute(ICalculatorOperation calculatorOperation, int a, int b)
{
return calculatorOperation.Execute(a, b);
}
}
和提供依赖项的工厂类:
public interface ICalculatorOperation
{
decimal Execute(int a, int b);
}
public class SumOperation : ICalculatorOperation
{
public decimal Execute(int a, int b) => a + b;
}
public class CalculatorOperationFactory
{
private Dictionary<Operation, ICalculatorOperation> _operationByType =
new Dictionary<Operation, ICalculatorOperation>()
{
{Operation.Plus, new SumOperation() }
// ... other code is omitted for the brevity
};
public ICalculatorOperation GetByOperation(Operation operation) =>
_operationByType[operation];
}
然后根据点击了什么按钮,你可以选择一个合适的操作(我们在上面的代码中使用策略模式)来处理这个按钮,使用如下代码:
CalculatorOperationFactory calculatorOperationFactory = new ();
ICalculatorOperation calculatorOperation =
calculatorOperationFactory.GetByOperation(Operation.Plus); // here
// we are assuming that user clicked "plus" button
decimal sum = new Calculator().Execute(calculatorOperation, 1, 2);
我相信上面的代码符合:
enum Operation
, CalculatorOperationFactory
和 CalculatorOperationFactory
扩展操作Execute()
的方法Calculator
进行扩展,关闭进行修改。我的意思是:
A。如果我们想编辑,例如plus
操作,那么我们将编辑SumOperation
类,而不是Calculator
类。此外,通过这样做,我们也满足了 SOLID 的单一职责原则
b.如果我们想添加新功能,例如multiple
操作,那么我们将添加MultipluOperation
类,但不需要编辑Calculator
类。此外,通过这样做,我们还满足 SOLID这里是 Java 中“为扩展关闭或为修改打开”的示例代码
这很糟糕,因为每次添加新组件时,比如从 ServiceD 到 ServiceZ,您都必须对组件的客户端(即 Program01)进行修改。
//this violates OCP
public class Program01 {
private static ServiceA objA;
private static ServiceB objB;
private static ServiceC objC;
public static void set(ServiceA obj) {
objA = obj;
}
public static void set(ServiceB obj) {
objB = obj;
}
public static void set(ServiceC obj) {
objC = obj;
}
public static void doSomething(final String id) {
if(id.equals("A")){
objA.doSomething();
}else if(id.equals("B")){
objB.doSomething();
}else if(id.equals("C")){
objC.doSomething();
}
}
}
而如果你遵循OCP,即使你有新的服务D-Z,客户端(Program02)也不需要任何修改。
//this follows OCP
public class Program02 {
static List<Service> listService = new ArrayList<Service>();
public static void set(Service obj) {
listService.add(obj);
}
public static void doSomething(final String id) {
for(Service obj : listService) {
if(!id.equals(obj.identify()))continue;
obj.doSomething();
break;
}
}
}
OCP 定义:“Closed for Modification and Open for Extension”,可以改写为,“可以添加和扩展新组件(OPEN)而不修改组件的客户端(CLOSED)。”
当客户有大量组件需要管理,并且组件数量持续增长时,这一原则尤为突出。