开放/封闭原则规定软件实体(类,模块等)应该开放以进行扩展,但是关闭以进行修改。这是什么意思,为什么它是良好的面向对象设计的重要原则?
答案 0 :(得分:39)
这意味着您应该将新代码放入新的类/模块中。应仅修改现有代码以修复错误。新类可以通过继承重用现有代码。
开放/封闭原则旨在降低引入新功能时的风险。由于您不修改现有代码,因此可以确保它不会被破坏。它降低了维护成本,提高了产品的稳定性。
答案 1 :(得分:23)
具体来说,它是关于OOP中设计的“圣杯”,即使实体可扩展性足够(通过其个人设计或通过其参与架构)来支持未来不可改变的更改而无需重写其代码(有时甚至没有重新编译**)。
执行此操作的一些方法包括多态性/继承,组合,控制反转(又名DIP),面向方面编程,模式(如策略,访问者,模板方法)以及OOAD的许多其他原则,模式和技术。
**参见6“包原则”,REP, CCP, CRP, ADP, SDP, SAP
答案 2 :(得分:5)
这是脆弱的基类问题的答案,该问题表明对基类的看似无辜的修改可能会对依赖于先前行为的继承者产生意想不到的后果。因此,您必须小心地封装您不想依赖的内容,以便派生类将遵循基类定义的契约。一旦存在继承者,你必须真正小心你在基类中改变的东西。
答案 3 :(得分:5)
比DaveK更具体,通常意味着如果要添加其他功能或更改类的功能,请创建子类而不是更改原始类。这样,任何使用父类的人都不必担心以后会改变它。基本上,这都是关于向后兼容性的。
面向对象设计的另一个非常重要的原则是通过方法接口的松散耦合。如果您要进行的更改不会影响现有界面,则更改非常安全。例如,使算法更有效。面向对象的原则也需要通过常识来缓和:)
答案 4 :(得分:5)
软件实体应开放以进行扩展,但已关闭以进行修改
这意味着任何类或模块都应该以可以按原样使用的方式编写,可以扩展,但是已经过修改
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;
现在,如果你想添加Another Juice类型,你必须编辑模块本身,通过这种方式,我们打破了OCP。
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;
现在,您可以从模块外部添加新的果汁类型,而无需编辑相同的模块。
答案 5 :(得分:3)
开放式封闭原则在面向对象的编程中非常重要,它是 SOLID 原则之一。
按此,应该为扩展打开一个类,为 修改。让我们明白为什么。
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;
}
}
如果您看上面的设计,我们可以清楚地看到它不是 遵循开放/封闭原则。每当有新的 形状(矩形,正方形等), AreaService 必须修改。
具有开放/封闭原则:
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();
}
}
只要有三角形,正方形等新形状,您就可以轻松实现 适应新形状而不修改现有类。用 这样的设计,我们可以确保现有代码不会受到影响。
答案 6 :(得分:2)
该原则意味着它可以轻松添加新功能,而无需更改现有的,稳定的和经过测试的功能,从而节省时间和金钱。
通常,多态性(例如使用接口)是实现此目的的好工具。
答案 7 :(得分:2)
SOLID 原则中O笔封闭原则的目的是
开闭原则指出我们应该尽量不改变现有的 添加新功能的同时编写代码。这基本上意味着 现有代码应该对扩展开放,对扩展关闭 修改(除非现有代码中存在错误)。更改现有代码而新功能需要完成现有功能测试。
让我通过 AppLogger util 类来解释这一点。
假设我们需要将应用范围的错误记录到名为 Firebase 的在线工具中。所以我们创建了下面的类,并在 1000 多个地方使用它来记录 API 错误、内存不足错误等。
open class AppLogger {
open fun logError(message: String) {
// reporting error to Firebase
FirebaseAnalytics.logException(message)
}
}
假设一段时间后,我们将支付功能添加到应用程序中,并且有一项新要求规定,仅对于与支付相关的错误,我们必须使用名为 Instabug 的新报告工具,并像以前一样继续向 Firebase 报告错误包括支付在内的所有功能。
现在我们可以通过在我们现有的方法中放置一个 if else 条件来实现这一点
fun logger(message: String, origin: String) {
if (origin == "Payment") {
//report to both Firebase and Instabug
FirebaseAnalytics.logException(message)
InstaBug.logException(message)
} else {
// otherwise report only to Firebase
FirebaseAnalytics.logException(message)
}
}
这种方法的问题在于它违反了S单一职责原则,该原则规定一个方法应该只做一件事。另一种说法是一个方法应该只有一个改变的理由。使用这种方法有两个原因会改变这个方法(if & else 阻塞)。
更好的方法是通过继承现有的 Logger 类来创建一个新的 Logger 类,如下所示。
class InstaBugLogger : AppLogger() {
override fun logError(message: String) {
super.logError(message) // This uses AppLogger.logError to report to Firebase.
InstaBug.logException(message) //Reporting to Instabug
}
}
现在我们要做的就是在 Payment 功能中使用 InstaBugLogger.logError() 将错误记录到 Instabug 和 Firebase。通过这种方式,我们将新错误报告要求的测试减少/隔离到仅支付功能,因为代码更改仅在支付功能中完成。其他应用程序功能无需测试,因为现有 Logger 没有代码更改。
答案 8 :(得分:1)
符合OCP的另一个经验法则是根据派生类提供的功能使基类抽象化。或者正如Scott Meyers所说'Make Non-leaf classes abstract'。
这意味着在基类中有未实现的方法,并且只在没有子类的类中实现这些方法。然后基类的客户端不能依赖基类中的特定实现,因为没有。
答案 9 :(得分:1)
我只想强调“开放/封闭”即使在OO编程中明显有用,也是一种在开发的各个方面都使用的健康方法。例如,根据我自己的经验,使用普通C时尽可能使用“打开/关闭”是一个很好的止痛药。
/罗伯特
答案 10 :(得分:1)
让我们将问题分为三个部分,以使您更容易理解各种概念。
请考虑以下代码中的示例。不同的车辆以不同的方式维修。因此,我们为Bike
和Car
使用了不同的类,因为服务Bike
的策略不同于服务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();
}
}
您可能已经注意到,每当要维修诸如Truck
或Bus
之类的新车时,都需要修改Garage
以定义诸如{{1 }}和serviceTruck()
。这意味着serviceBus()
类必须知道所有可能的车辆,例如Garage
,Bike
,Car
,Bus
等。因此,它因开放修改而违反了开闭原则。另外,它也无法扩展,因为要扩展新功能,我们需要修改类。
抽象
要解决上面代码中的刚性问题,我们可以使用开闭原理。这意味着我们需要取消所有已知的车辆维修的实施细节,从而使Truck
类变得愚蠢。换句话说,我们应该为Garage
和Bike
之类的每种具体类型抽象服务策略的实现细节。
要抽象出各种类型车辆的维修策略的实施细节,我们使用称为Car
的{{1}}并在其中包含抽象方法interface
。
多态
同时,我们还希望Vehicle
类接受车辆的许多形式,例如service()
,Garage
等,而不是仅Bus
和Truck
。为此,开闭原理使用 polymorphism (许多形式)。
为使Bike
类接受多种形式的Car
,我们将其方法的签名更改为Garage
以接受接口Vehicle
而不是实际的实现例如service(Vehicle vehicle) { }
,Vehicle
等。我们还从类中删除了多个方法,因为只有一个方法可以接受许多形式。
Bike
已关闭修改
您可以在上面的代码中看到,Car
类现在已关闭以进行修改,因为它现在不知道各种车辆的维修策略的实现细节,并且可以接受任何类型的车辆新的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();
}
}
。我们只需要从Garage
界面扩展新的车辆并将其发送到Vehicle
。而已!我们不需要更改Vehicle
类中的任何代码。
另一个无法修改的实体是我们的Garage
接口。
我们不必更改界面即可扩展软件的功能。
开放扩展
Garage
类现在可以在不需要修改的情况下支持新类型的Vehicle
进行扩展。
我们的Garage
接口是开放的,因为要引入任何新的车辆,我们可以从Vehicle
接口进行扩展,并提供一种新的实施方案以及为该特定车辆提供维修服务的策略。
策略设计模式
您是否注意到我多次使用 strategy 这个词?这是因为这也是策略设计模式的一个示例。通过扩展它,我们可以实施不同的策略来服务不同类型的Vehicle
。例如,服务Vehicle
的策略与服务Vehicle
的策略不同。因此,我们在不同的派生类中实施这些策略。
随着需求随时间变化,策略模式使我们的软件更加灵活。只要客户改变策略,就可以为其派生一个新类并将其提供给现有组件,而无需更改其他内容!开闭原则在实施这种模式中起着重要作用。
就是这样!希望有帮助。
答案 11 :(得分:0)
这意味着应该构建OO软件,但本质上不会改变。这很好,因为它确保了基类的可靠,可预测的性能。
答案 12 :(得分:0)
最近我对这个原则的含义有了额外的了解:开放 - 封闭原则立即描述了编写代码的方式,以及以弹性方式编写代码的最终结果。
我喜欢将开放/封闭分为两个密切相关的部分:
因此,展示开放/封闭行为(或者,如果您愿意,满足开放/封闭原则)的代码需要进行最少的修改或不进行修改,以响应超出最初构建范围的使用场景。
就实施而言?我发现通常所说的解释,“打开/关闭是指代码是多态的!”至多是一个不完整的陈述。代码中的多态性是实现这种行为的一种工具;继承,实现......实际上,每个面向对象的设计原则都是编写具有弹性的代码所必需的。
答案 13 :(得分:0)
在设计原则上,SOLID - " O"在" SOLID"代表开放/封闭原则。
Open Closed原则是一个设计原则,它表示类,模块和函数应该是可以扩展的,但是可以关闭以进行修改。
这个原则规定代码的设计和编写应该以现有代码(测试代码)中的最小变化添加新功能的方式完成。设计应该以允许添加新功能作为新类的方式完成,尽可能保持现有代码不变。
开放式封闭式设计原则的好处:
我的博客文章:
http://javaexplorer03.blogspot.in/2016/12/open-closed-design-principle.html
答案 14 :(得分:0)
开放式封闭原则有两种版本。
请参阅以下内容,对两者进行清晰,彻底的解释:https://www.slideshare.net/pjschwarz/first-expedia-tech-know-how-event-the-openclosed-principle-the-original-version-and-the-contemporary-version
为激发食欲,以下是前几张幻灯片的屏幕截图: