首页 > 其他分享 >设计原则之【开闭原则】

设计原则之【开闭原则】

时间:2022-11-05 14:05:20浏览次数:71  
标签:原则 accountService void public Shape new 设计 开闭 class


文章目录

  • ​​什么是开闭原则​​
  • ​​简单实例​​
  • ​​实战实例​​
  • ​​如何理解“对修改关闭”?修改代码就一定违背开闭原则吗​​
  • ​​参考资料​​

什么是开闭原则

开闭原则的英文全称是 Open Closed Principle,简写为 OCP。它的英文描述是:software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。

可扩展性,就是,添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。是衡量代码质量最重要的标准之一。

开闭原则理解起来并不难,难的是能够灵活的应用到实际开发工作中。

简单实例

以下代码相信也不陌生,这就是典型的使用面向对象语言做着面向过程的事:

package com.study;

public class Ocp {

public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收 Shape 对象,然后根据 type,来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1){
drawRectangle(s);
} else if (s.m_type == 2){
drawCircle(s);
} else if (s.m_type == 3){
drawTriangle(s);
}
}

//绘制矩形
public void drawRectangle(Shape r) {
System.out.println(" 绘制矩形 ");
}

//绘制圆形
public void drawCircle(Shape r) {
System.out.println(" 绘制圆形 ");
}

//绘制三角形
public void drawTriangle(Shape r) {
System.out.println(" 绘制三角形 ");
}
}

//Shape 类,基类
class Shape {
int m_type;
}

class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}

class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}

//新增画三角形
class Triangle extends Shape {
Triangle() {
super.m_type = 2;
}
}

优化后:

package com.study;

public class Ocp {

public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收 Shape 对象,然后根据 type,来绘制不同的图形
public void drawShape(Shape s) {
s.draw();
}
}

//Shape 类,基类
abstract class Shape {
abstract void draw();
}

class Rectangle extends Shape {
@Override
void draw() {
System.out.println(" 绘制矩形 ");
}
}

class Circle extends Shape {
@Override
void draw() {
System.out.println(" 绘制圆形 ");
}
}

//新增画三角形
class Triangle extends Shape {
@Override
void draw() {
System.out.println(" 绘制三角形 ");
}
}

实战实例

现在有一个登录逻辑:

public class Login {
private AccountService accountService;

@Autoware
public Login (AccountService accountService) {
this.accountService = accountService;
}

public boolean loginCheck(String password, String name) {
// 检查账号密码
if(!accountService.checkPwd(password, name)){
return false;
}
// 检查登录失败次数
if(accountService.getLoginErrorCount() >= 5){
return false;
}
return true;
}
}

上述代码相信很多小伙伴都会遇到,看上去似乎并没有什么大问题。

但是,现在新增一个逻辑:登录时,我要校验验证码是否正确,代码可能需要做以下改动:

public class Login {
private AccountService accountService;

@Autoware
public Login (AccountService accountService) {
this.accountService = accountService;
}

// 改动1:方法添加参数
public boolean loginCheck(String password, String name, String verificationCode) {
// 检查账号密码
if(!accountService.checkPwd(password, name)){
return false;
}
// 检查登录失败次数
if(accountService.getLoginErrorCount() >= 5){
return false;
}
// 改动2:检查验证码
if(!accountService.getVerificationCode().equals(verificationCode)){
return false;
}
return true;
}
}

这样的代码修改实际上存在挺多问题的。一方面,我们对接口进行了修改,这就意味着调用这个接口的代码都要做相应的修改。另一方面,修改了 loginCheck() 函数内容,相应的单元测试及其他逻辑都需要修改。

接下来我们重构一下Login的代码,让其具备扩展性,重构包括两部分:

  • 第一部分是将 loginCheck() 函数的多个入参封装成 LoginCheckInfo类;
  • 第二部分是引入 handler 的概念,将 if 判断逻辑分散在各个 handler 中。
public class LoginCheckInfo {//省略constructor/getter/setter方法
private String name;
private String password;
private String verificationCode;
}

public class Login {
private List<LoginCheckHandler> checkHandlers = new ArrayList<>();

public void addLoginCheckHandler(CheckHandler checkHandler) {
this.checkHandlers.add(checkHandler);
}

public void check(LoginCheckInfo loginCheckInfo) {
for (LoginCheckHandlerhandler : checkHandlers) {
boolean result = handler.check(loginCheckInfo);
// 处理逻辑
}
}
}

public abstract class CheckHandler {
private AccountService accountService;

@Autoware
public CheckHandler (AccountService accountService) {
this.accountService = accountService;
}
public abstract void check(LoginCheckInfo loginCheckInfo);
}

public class PwdCheckHandler extends CheckHandler {
public PwdCheckHandler(AccountService accountService) {
super(accountService);
}

@Override
public boolean check(LoginCheckInfo loginCheckInfo) {
return accountService.checkPwd(loginCheckInfo.getPassword(), loginCheckInfo.getName());
}
}

public class LoginCountCheckHandler extends CheckHandler {
public LoginCountCheckHandler(AccountService accountService) {
super(accountService);
}

@Override
public boolean check(LoginCheckInfo loginCheckInfo) {
return accountService.getLoginErrorCount() >= 5;
}
}

要想使用我们重构后的登录逻辑:

@Bean
public Login login(){
Login login = new Login();
login.addLoginCheckHandler(new PwdCheckHandler());
login.addLoginCheckHandler(new LoginCountCheckHandler());
}

// 只需要在service层注入login,然后调用login.check();即可

此时,我们如果想要扩展验证码功能:

public class VerificationCodeCheckHandler extends CheckHandler {
public VerificationCodeCheckHandler(AccountService accountService) {
super(accountService);
}

@Override
public boolean check(LoginCheckInfo loginCheckInfo) {
return accountService.getVerificationCode().equals(loginCheckInfo.getVerificationCode());
}
}

// 然后在定义Login的bean中将这个处理器添加进去即可。
login.addLoginCheckHandler(new VerificationCodeCheckHandler());

重构之后的代码更加灵活和易扩展。如果我们要想添加新的告警逻辑,只需要基于扩展的方式创建新的 handler 类即可,不需要改动原来的 check() 函数的逻辑。而且,我们只需要为新的 handler 类添加单元测试,老的单元测试都不会失败,也不用修改。

如何理解“对修改关闭”?修改代码就一定违背开闭原则吗

对修改关闭的前提是:对扩展开放。

,添加一个新功能,不可能任何模块、类、方法的代码都不“修改”,这个是做不到的。类需要创建、组装、并且做一些初始化操作,才能构建成可运行的的程序,这部分代码的修改是在所难免的。我们要做的是尽量让修改操作更集中、更少、更上层,尽量让最核心、最复杂的那部分逻辑代码满足开闭原则。

也就是说,对拓展开放是为了应对变化(需求),对修改关闭是为了保证已有代码的稳定性;最终结果是为了让系统更有弹性!

添加一个新的功能,如果能够保证老的核心代码不会被修改,那么这就是符合开闭原则的。

熟练使用各种设计模式、并且应用到实际工作中,是我们开发者一生都要去学习的。

但是,熟悉了“开闭原则”,这并不意味着你需要随时随地都要考虑扩展。需求永远是在不断变化的,即便我们对业务、对系统有足够的了解,那也不可能识别出所有的扩展点,即便你能识别出所有的扩展点,为这些地方都预留扩展点,这样做的成本也是不可接受的。我们没必要为一些遥远的、不一定发生的需求去提前买单,做过度设计。

最合理的做法是,对于一些比较确定的、短期内可能就会扩展,或者需求改动对代码结构影响比较大的情况,或者实现成本不高的扩展点,在编写代码的时候之后,我们就可以事先做些扩展性设计。但对于一些不确定未来是否要支持的需求,或者实现起来比较复杂的扩展点,我们可以等到有需求驱动的时候,再通过重构代码的方式来支持扩展的需求。

所以,具体场景还需要具体分析。


标签:原则,accountService,void,public,Shape,new,设计,开闭,class
From: https://blog.51cto.com/u_13540373/5825940

相关文章