开放/封闭原则背后的含义和推理是什么?

Phi*_*lls 42 oop definition design-principles open-closed-principle solid-principles

开放/封闭原则规定软件实体(类,模块等)应该是可以扩展的,但是对于修改是封闭的.这意味着什么,为什么它是良好的面向对象设计的重要原则?

aku*_*aku 42

这意味着您应该将新代码放入新的类/模块中.应仅修改现有代码以修复错误.新类可以通过继承重用现有代码.

开放/封闭原则旨在降低引入新功能时的风险.由于您不修改现有代码,因此可以确保它不会被破坏.它降低了维护成本并提高了产品稳定性.

  • 实际上,这就是OCP的意义所在.我相信"降低维护成本和提高产品稳定性"的目标可以通过其他方式实现:通过KISS,YAGNI,敏捷开发,TDD和重构等方式实现变革,始终将良好的自动化测试套件作为安全网. (6认同)

Tro*_*eun 25

具体来说,它是关于OOP中设计的"圣杯",即使实体可扩展(通过其个人设计或通过其参与架构)来支持未来不可更改的更改而无需重写其代码(有时甚至无需重新编译**).

一些方法包括多态性/继承,组合,控制反转(又名DIP),面向方面编程,模式,如策略,访问者,模板方法,以及OOAD的许多其他原则,模式和技术.

**参见6"包原则",REP,CCP,CRP,ADP,SDP,SAP


Yog*_*ity 9

让我们将问题分为三个部分,以便更容易理解各种概念。


开闭原则背后的推理

考虑下面代码中的示例。不同的车辆以不同的方式进行维修。因此,我们为Bike和提供了不同的类Car,因为服务 a 的策略Bike与服务 a 的策略不同Car。该Garage类别接受各种类型的车辆进行维修。

刚性问题

观察代码,看看Garage类在引入新功能时如何表现出僵化的迹象:

class Bike {
    public void service() {
        System.out.println("Bike servicing strategy performed.");
    }
}

class Car {
    public void service() {
        System.out.println("Car servicing strategy performed.");
    }
}

class Garage {
    public void serviceBike(Bike bike) {
        bike.service();
    }

    public void serviceCar(Car car) {
        car.service();
    }
}
Run Code Online (Sandbox Code Playgroud)

您可能已经注意到,每当某些新车需要维修TruckBus需要维修时,都Garage需要修改以定义一些新方法,例如serviceTruck()serviceBus()。这意味着Garage班级必须知道每种可能的车辆,Bike例如Car、、、等等。因此,它对修改开放,违反了开闭原则。而且它不开放扩展,因为要扩展新功能,我们需要修改该类。BusTruck


开闭原则背后的含义

抽象

为了解决上面代码中的刚性问题,我们可以使用开闭原则。这意味着我们需要Garage通过删除它所知道的每辆车的服务实现细节来使该类变得愚蠢。换句话说,我们应该抽象出每个具体类型(如Bike和 )的服务策略的实现细节Car

为了抽象各种类型车辆的服务策略的实现细节,我们使用一个interface被调用的方法,其中Vehicle有一个抽象方法。service()

多态性

同时,我们也希望该类Garage接受车辆的多种形式Bus,例如、Truck等等,而不仅仅是BikeCar。为此,开闭原则使用多态性(多种形式)。

为了使Garage类接受多种形式的Vehicle,我们将其方法的签名更改为service(Vehicle vehicle) { }接受接口Vehicle而不是实际的实现BikeCar例如 等。我们还从类中删除了多个方法,因为只有一个方法将接受多种形式。

interface Vehicle {
    void service();
}

class Bike implements Vehicle {
    @Override
    public void service() {
        System.out.println("Bike servicing strategy performed.");
    }
}

class Car implements Vehicle {
    @Override
    public void service() {
        System.out.println("Car servicing strategy performed.");
    }
}

class Garage {
    public void service(Vehicle vehicle) {
        vehicle.service();
    }
}
Run Code Online (Sandbox Code Playgroud)

开闭原则的重要性

因修改而关闭

正如您在上面的代码中看到的,现在该类Garage已关闭修改,因为现在它不知道各种类型车辆的服务策略的实现细节,并且可以接受任何类型的新Vehicle. 我们只需从界面扩展新车Vehicle并将其发送到Garage. 就是这样!我们不需要更改Garage类中的任何代码。

另一个禁止修改的实体是我们的Vehicle界面。我们不必更改界面来扩展软件的功能。

开放扩展

该类Garage现在可以在上下文中开放扩展,它将支持 的新类型Vehicle,而无需进行修改。

我们的Vehicle接口是开放扩展的,因为要引入任何新车辆,我们可以从接口扩展Vehicle并提供新的实现以及服务该特定车辆的策略。

策略设计模式

您是否注意到我多次使用了“策略”这个词?那是因为这也是策略设计模式的一个示例。我们可以通过扩展它来实施不同的策略来服务不同类型的Vehicles。例如,为 a 提供服务的Truck策略与为 a 提供服务的策略不同Bus。因此,我们在不同的派生类中实现这些策略。

策略模式使我们的软件能够随着需求随时间的变化而灵活变化。每当客户改变策略时,只需为其派生一个新类并将其提供给现有组件,无需更改其他内容!开闭原则在实现这一模式中发挥着重要作用。


就是这样!希望有帮助。


jod*_*ell 6

这是脆弱的基类问题的答案,它说对基类的看似无辜的修改可能会对依赖于先前行为的继承者产生意想不到的后果.因此,您必须小心地封装您不想依赖的内容,以便派生类将遵循基类定义的契约.一旦存在继承者,你必须非常小心你在基类中改变了什么.

  • 我认为这是LSP(Liskov替代原则)而不是? (2认同)

Rus*_*ett 6

比DaveK更具体地说,通常意味着如果要添加其他功能或更改类的功能,请创建子类而不是更改原始类.这样,任何使用父类的人都不必担心以后会改变它.基本上,它都是关于向后兼容性的.

面向对象设计的另一个非常重要的原则是通过方法接口的松散耦合.如果您要进行的更改不会影响现有界面,则更改非常安全.例如,使算法更有效.面向对象的原则也需要通过常识来缓和:)


May*_*ndi 6

软件实体应开放以进行扩展,但应封闭以进行修改

这意味着任何类或模块都应以可以原样使用,可以扩展但必须修改的方式编写

JavaScript中的错误示例

var juiceTypes = ['Mango','Apple','Lemon'];
function juiceMaker(type){
    if(juiceTypes.indexOf(type)!=-1)
        console.log('Here is your juice, Have a nice day');
    else
        console.log('sorry, Error happned');
}

exports.makeJuice = juiceMaker;
Run Code Online (Sandbox Code Playgroud)

现在,如果要添加其他果汁类型,则必须编辑模块本身。这样,我们就打破了OCP。

Java范例

var juiceTypes = [];
function juiceMaker(type){
    if(juiceTypes.indexOf(type)!=-1)
        console.log('Here is your juice, Have a nice day');
    else
        console.log('sorry, Error happned');
}
function addType(typeName){
    if(juiceTypes.indexOf(typeName)==-1)
        juiceTypes.push(typeName);
}
function removeType(typeName){
  let index = juiceTypes.indexOf(typeName)
    if(index!==-1)
        juiceTypes.splice(index,1);
}

exports.makeJuice = juiceMaker;
exports.addType = addType;
exports.removeType = removeType;
Run Code Online (Sandbox Code Playgroud)

现在,您可以从模块外部添加新的果汁类型,而无需编辑同一模块。


Sum*_*ada 6

Open Closed Principle is very important in object oriented programming and it's one of the SOLID principles.

As per this, a class should be open for extension and closed for modification. Let us understand why.

class Rectangle {
    public int width;
    public int lenth;
}

class Circle {
    public int radius;
}

class AreaService {
    public int areaForRectangle(Rectangle rectangle) {
        return rectangle.width * rectangle.lenth;
    }

    public int areaForCircle(Circle circle) {
        return (22 / 7) * circle.radius * circle.radius;
    }
}
Run Code Online (Sandbox Code Playgroud)

If you look at the above design, we can clearly observe that it's not following Open/Closed Principle. Whenever there is a new shape(Tiangle, Square etc.), AreaService has to be modified.

With Open/Closed Principle:

interface Shape{
    int area();
}

class Rectangle implements Shape{
    public int width;
    public int lenth;

    @Override
    public int area() {
        return lenth * width;
    }
}

class Cirle implements Shape{
    public int radius;

    @Override
    public int area() {
        return (22/7) * radius * radius;
    }
}

class AreaService {
    int area(Shape shape) {
        return shape.area();
    }
}
Run Code Online (Sandbox Code Playgroud)

每当出现三角形、正方形等新形状时,您都可以轻松适应新形状,而无需修改现有类。通过这种设计,我们可以确保现有代码不会受到影响。