开放/封闭原则指出软件实体(类、模块等)应该对扩展开放,但对修改关闭。这是什么意思,为什么它是良好的面向对象设计的重要原则?
15 回答
这意味着您应该将新代码放入新的类/模块中。现有代码应仅用于错误修复。新类可以通过继承重用现有代码。
开放/封闭原则旨在降低引入新功能时的风险。由于您不修改现有代码,因此可以确保它不会被破坏。它降低了维护成本并提高了产品稳定性。
具体来说,它是关于 OOP 中设计的“圣杯”,即使实体具有足够的可扩展性(通过其单独的设计或通过其参与架构)以支持未来不可预见的变化,而无需重写其代码(有时甚至无需重新编译) **)。
实现这一点的一些方法包括多态/继承、组合、控制反转(又名 DIP)、面向方面的编程、诸如策略、访问者、模板方法等模式,以及 OOAD 的许多其他原则、模式和技术。
** 见6“包原则”,REP、CCP、CRP、ADP、SDP、SAP
比 DaveK 更具体地说,它通常意味着如果你想添加额外的功能,或者改变一个类的功能,创建一个子类而不是改变原来的。这样,任何使用父类的人都不必担心它以后会发生变化。基本上,这都是关于向后兼容性的。
面向对象设计的另一个非常重要的原则是通过方法接口松散耦合。如果您要进行的更改不影响现有界面,那么更改确实非常安全。例如,使算法更有效。面向对象的原则也需要通过常识来调和 :)
软件实体应该对扩展开放但对修改关闭
这意味着任何类或模块都应该以可以按原样使用,可以扩展但不能修改的方式编写
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;
现在如果你想添加另一种果汁类型,你必须编辑模块本身,这样我们就破坏了 OCP 。
Javascript中的好例子
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;
现在,您可以从模块外部添加新的果汁类型,而无需编辑相同的模块。
开闭原则在面向对象编程中非常重要,是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;
}
}
如果你看上面的设计,我们可以清楚地观察到它没有遵循Open/Closed 原则。每当有新的形状(Tiangle、Square 等)时,必须修改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();
}
}
每当有三角形、正方形等新形状时,您都可以轻松适应新形状,而无需修改现有类。通过这种设计,我们可以确保现有代码不会产生影响。
这是脆弱的基类问题的答案,它表示对基类的看似无辜的修改可能会对依赖于先前行为的继承者产生意想不到的后果。因此,您必须小心封装您不希望依赖的内容,以便派生类遵守基类定义的协定。一旦继承者存在,你必须非常小心你在基类中所做的更改。
让我们将问题分解为三个部分,以便更容易理解各种概念。
开闭原则背后的推理
考虑下面代码中的示例。不同的车辆以不同的方式进行维修。因此,我们有不同的类Bike
,Car
因为服务 a 的策略与服务 aBike
的策略不同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
需要修改 will 以定义一些新方法,例如serviceTruck()
和serviceBus()
。这意味着Garage
班级必须了解所有可能的车辆,例如、Bike
、Car
等。因此,它对修改的开放性违反了开闭原则。而且它不开放扩展,因为要扩展新功能,我们需要修改类。Bus
Truck
开闭原则背后的意义
抽象
为了解决上面代码中的刚性问题,我们可以使用开闭原则。这意味着我们需要Garage
通过删除它所知道的每辆车的服务实现细节来让这个类变得愚蠢。换句话说,我们应该为每个具体类型(如Bike
和)抽象出服务策略的实现细节Car
。
为了抽象出各种类型车辆的服务策略的实现细节,我们使用了一个interface
被调用的方法,Vehicle
并在其中有一个抽象方法service()
。
多态性
同时,我们也希望Garage
类能够接受多种形式的车辆,比如Bus
,Truck
等等,而不仅仅是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
该类已关闭以进行修改,因为它现在不了解各种类型车辆的服务策略的实现细节,并且可以接受任何类型的 new Vehicle
. 我们只需要从Vehicle
接口扩展新车辆并将其发送到Garage
. 就是这样!我们不需要更改Garage
类中的任何代码。
另一个对修改关闭的实体是我们的Vehicle
接口。我们不必更改界面来扩展我们软件的功能。
开放扩展
现在,Garage
该类在将支持新类型的上下文中对扩展开放Vehicle
,无需修改。
我们的Vehicle
接口对扩展是开放的,因为要引入任何新的车辆,我们可以从Vehicle
接口扩展并提供一个新的实现以及为该特定车辆提供服务的策略。
策略设计模式
你注意到我多次使用了策略这个词吗?那是因为这也是策略设计模式的一个例子。Vehicle
我们可以通过扩展实现不同的策略来服务不同类型的s。例如,服务 aTruck
的策略与服务 a 的策略不同Bus
。所以我们在不同的派生类中实现这些策略。
策略模式允许我们的软件随着需求随时间的变化而灵活变化。每当客户改变他们的策略时,只需为其派生一个新类并将其提供给现有组件,无需更改其他内容!开闭原则在实现这一模式中起着重要作用。
就是这样!希望有帮助。
该原则意味着添加新功能应该很容易,而无需更改现有的、稳定的和经过测试的功能,从而节省时间和金钱。
通常,多态性,例如使用接口,是实现这一目标的好工具。
符合 OCP 的另一个经验法则是使基类相对于派生类提供的功能抽象。或者正如 Scott Meyers 所说的“使非叶类抽象化”。
这意味着在基类中有未实现的方法,并且只在本身没有子类的类中实现这些方法。然后基类的客户端不能依赖基类中的特定实现,因为没有。
S O LID 原则中的开放封闭原则的目的是
- 降低业务变更需求的成本。
- 减少对现有代码的测试。
开放封闭原则指出,在添加新功能时,我们应该尽量不要更改现有代码。这基本上意味着现有代码应该对扩展开放,对修改关闭(除非现有代码中存在错误)。在添加新功能的同时更改现有代码需要再次测试现有功能。
让我通过 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 logError(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)
}
}
这种方法的问题在于它违反了单一责任原则,该原则指出一个方法应该只做一件事。另一种说法是方法应该只有一个改变的理由。使用这种方法,这种方法有两个改变的原因(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
}
}
现在我们要做的就是在支付功能中使用 InstaBugLogger.logError() 将错误记录到 Instabug 和 Firebase。通过这种方式,我们将新错误报告要求的测试减少/隔离为仅支付功能,因为代码更改仅在支付功能中完成。其余的应用程序功能不需要测试,因为没有对现有 Logger 进行任何代码更改。
我只想强调,“Open/Closed”虽然在 OO 编程中显然很有用,但在开发的各个方面都是一种健康的方法。例如,根据我自己的经验,在使用普通 C 时尽可能使用“打开/关闭”是一种很好的止痛药。
/罗伯特
这意味着 OO 软件应该建立在其基础之上,而不是从本质上改变。这很好,因为它确保了基类的可靠、可预测的性能。
我最近对这个原则的含义有了一个额外的想法:开闭原则立即描述了一种编写代码的方式,以及以一种有弹性的方式编写代码的最终结果。
我喜欢将 Open/Closed 分为两个密切相关的部分:
- 开放更改的代码可以更改其行为以正确处理其输入,或者需要最少的修改以提供新的使用场景。
- 如果有任何人工干预来处理新的使用场景,则关闭以进行修改的代码不需要太多。这种需求根本不存在。
因此,表现出开放/封闭行为的代码(或者,如果您愿意,满足开放/封闭原则)需要极少或不需要修改以响应超出其最初构建目的的使用场景。
就执行而言?我发现通常所说的解释,“打开/关闭是指代码是多态的!” 充其量是一个不完整的陈述。代码中的多态是实现这种行为的一种工具。继承、实现……真的,每一个面向对象的设计原则对于编写具有该原则所暗示的方式的弹性代码都是必要的。
在设计原则中,SOLID——“SOLID”中的“O”代表开/关原则。
开放封闭原则是一种设计原则,即类、模块和函数应该对扩展开放,对修改关闭。
该原则指出,代码的设计和编写应该以一种在现有代码(测试代码)中添加新功能的方式进行。设计应该以允许添加新功能作为新类的方式完成,尽可能保持现有代码不变。
开放封闭设计原则的好处:
- 应用程序将更加健壮,因为我们没有更改已经测试过的类。
- 灵活,因为我们可以轻松满足新要求。
- 易于测试且不易出错。
我的博客文章:
http://javaexplorer03.blogspot.in/2016/12/open-closed-design-principle.html
开闭原则有两个版本。
请参阅以下内容以获得对两者的清晰透彻的解释:https ://www.slideshare.net/pjschwarz/first-expedia-tech-know-how-event-the-openclosed-principle-the-original-version-and-当代版本
为了激发您的兴趣,以下是前几张幻灯片的屏幕截图: