提升前端代码质量之SOLID设计原则-OCP开放封闭原则

1,250 阅读4分钟

前言

在上一篇文章提升前端代码质量之SOLID设计原则-SRP单一职责中简单介绍了SOLID五种设计模式,本文不再赘述。

SOLID相关的文章索引:

前面讲解了SOLID中的单一职责,本文结合代码(React)主要讲解 SOLID 原则中的OCP - 开放封闭原则

开放封闭原则(Open-closed Principle)

实体应该对扩展是开放的,对修改是封闭的。

  • 简单的说,开闭原则就是,添加一个新的功能不修改既有代码,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等)。

先来看两个比较常见的例子:

EXAMPLE1

//src\page\OCP\Button.tsx
interface IButtonProps {
  text?:string,
  rule?:'back'|'forward',
}
import React from 'react';
import { RightOutlined, LeftOutlined } from '@ant-design/icons';
import { IButtonProps } from '@/typing';

export default (props: IButtonProps) => {
  const { text, rule } = props;
  return <>
    <button
      style={{
        background: '#1677ff',
        color: '#fff',
        boxShadow: '0 2px 0 rgb(5 145 255 / 10%)',
        border: 'none',
        cursor: 'pointer',
        padding: '6px 16px',
        borderRadius: '6px',
        textAlign: 'center'
      }}>
      {text}
      {rule === 'back' && <RightOutlined />}
      {rule === 'forward' && <LeftOutlined />}
    </button>
  </>
}

例子1是封装了一个button按钮,可用于前进和后退,效果:

1673270255931(1).png

EXAMPLE2

// src\page\OCP\utils\formValidate.ts
// 临时写的正则,校验可能不准
export const isEmail = (value: string) => {
  return /^[a-zA-Z0-9]+([-_.][A-Za-zd]+)*@([a-zA-Z0-9]+[-.])+[A-Za-zd]{2,5}$/.test(value);
}
// 临时写的正则,校验可能不准
export const isTelephone = (value: string) => {
  return /^[1][3,4,5,7,8][0-9]{9}$/.test(value);
}

/**
 * 整合校验
 * @param validName 校验的名字
 * @param message 校验不通过提示文案
 * @param options 校验方法配置参数 https://ant-design.gitee.io/components/form-cn#rule
*/

export const buildAntd4FormValidator = (
  validName: 'isEmail' | 'isTelephone',
  message: string,
  options: any = {},
) => {
  const validMap = {
    isEmail,
    isTelephone
  }
  return {
    validator(_rule, value: string, cb: (value?: string) => void) {
      if (!value) {
        cb();
      } else {
        if (validMap[validName](value)) {
          cb();
        } else {
          cb(message || 'Invalid field value')
        }
      }
    },
    ...options,
  }
}

// src\page\OCP\Validate.tsx
import React from 'react';
import { Button, Form, Input, message } from 'antd';
import { buildAntd4FormValidator, } from './utils/formValidate'

export default () => {
  const [form] = Form.useForm();
  const onFinish = () => {
    form.validateFields().then(() => {
      message.success("校验通过")
    }).catch(() => {
      message.error("校验不通过")
    })
  }
  return <>
    <Form
      name="basic"
      labelCol={{ span: 3 }}
      wrapperCol={{ span: 16 }}
      onFinish={onFinish}
    >
      <Form.Item
        label="邮箱"
        name="email"
        rules={[
          { required: true, message: '请输入电子邮箱!' },
          buildAntd4FormValidator('isEmail', '电子邮箱不正确')
        ]}
      >
        <Input placeholder='请输入电子邮箱' />
      </Form.Item>
      <Form.Item
        label="11位电话号码"
        name="telephone"
        rules={[
          { required: true, message: '请输入11位电话号码!' },
          buildAntd4FormValidator('isTelephone', '11位电话号码不正确')
        ]}
      >
        <Input placeholder='请输入11位电话号码' />
      </Form.Item>
      <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
        <Button type="primary" htmlType="submit">
          校验
        </Button>
      </Form.Item>
    </Form>
  </>
}

例子2中isEmailisTelephone是用于校验表单电子邮箱移动号码,而buildAntd4FormValidator是用于生成antd4表单的校验器,效果如下:

image.png

虽然例子很简单,但是我们还是来分析一下:我们可以直接观察到两个例子都有一个问题,就是把封装好的组件或者函数传参给 写死了

例子1:rule接收'back' 'forward'来动态渲染不同的图标,根据rule的种类有条件的渲染某一个组件:rule === 'back' && <RightOutlined />,如果我们新加一个rule的种类,那可能就会打破或者违反了OCP开闭原则,因为我们没有扩展而去更改我们的组件,假设这个组件是某一个库中的组件,那我们就要更改rule的种类就必须改源代码。

例子2: 例子2和例子1类似,我们在src\page\OCP\utils\formValidate.ts封装了两个方法isEmailisTelephone,并且最后封装了buildAntd4FormValidator提供给antd4中表单的校验器validator 使用,如果要新加一个校验器比如isUrl()是否是url的校验,此时如果我们还要借助buildAntd4FormValidator的能力,我们就要单独在buildAntd4FormValidator入参validName中单独去加一个类型,这样也会打破OCP开闭原则,因为此时并没有在buildAntd4FormValidator去扩展,而是改变既有的代码。

优化

例子1:

// 
import React from 'react';
import { IGoodButtonProps } from '@/typing';

export default (props: IGoodButtonProps) => {
  const { text, icon } = props;
  return <>
    <button
      style={{
        background: '#1677ff',
        color: '#fff',
        boxShadow: '0 2px 0 rgb(5 145 255 / 10%)',
        border: 'none',
        cursor: 'pointer',
        padding: '6px 16px',
        borderRadius: '6px',
        textAlign: 'center'
      }}>
      {text}
      {icon}
    </button>
  </>
}

// 使用时

import React from 'react';
import Button from './BadButton';
import { RightOutlined, LeftOutlined } from '@ant-design/icons';

export default () => {
  return <>
    <div>EXAMPLE1:</div>
    <Button text='前进' icon={<RightOutlined />} />&emsp;
    <Button text='后退' icon={<LeftOutlined />} />
  </>
}

例子2:

...
/**
 * 整合校验
 * @param validName 校验的名字
 * @param message 校验不通过提示文案
 * @param options 校验方法配置参数 https://ant-design.gitee.io/components/form-cn#rule
*/
export const buildAntd4FormValidator = (
  validFun: (value: string) => boolean,
  message: string,
  options: any = {},
) => {
  return {
    validator(_rule, value: string, cb: (value?: string) => void) {
      if (!value) {
        cb();
      } else {
        if (validFun(value)) {
          cb();
        } else {
          cb(message || 'Invalid field value')
        }
      }
    },
    ...options,
  }
}

// 使用
import React from 'react';
import { Button, Form, Input, message } from 'antd';
import { isEmail,buildAntd4FormValidator, isTelephone } from './utils/formValidate'

export default () => {
 ...
  return <>
    <Form
     ...
    >
      <Form.Item
        label="邮箱"
        name="email"
        rules={[
          { required: true, message: '请输入电子邮箱!' },
          buildAntd4FormValidator(isEmail, '电子邮箱不正确')
        ]}
      >
        <Input placeholder='请输入电子邮箱' />
      </Form.Item>
      <Form.Item
        label="11位电话号码"
        name="telephone"
        rules={[
          { required: true, message: '请输入11位电话号码!' },
          buildAntd4FormValidator(isTelephone, '11位电话号码不正确')
        ]}
      >
        <Input placeholder='请输入11位电话号码' />
      </Form.Item>
      <Form.Item
        label="url"
        name="url"
        rules={[
          { required: true, message: '请输入url!' },
          goodBuildAntd4FormValidator((value: string) => {
            return /http(s)?:\/\/[\w.]+[\w\/]*[\w.]*\??[\w=&\+\%]*/.test(value);
          }, 'url格式不正确')
        ]}
      >
        <Input placeholder='请输入url' />
      </Form.Item>
     ...
    </Form>
  </>
}

最后

开闭原则并不是说完全杜绝修改而是以最小的修改代码的代价来完成新功能的开发

  • 修改代码有时候在实际的业务场景和个人能力有限的范围内很难做到类或者方法的替换,我们需要做到的是为后面可能的情况留口子,尽量让修改范围变得比较小。

  • 刚开始进步,只要代码比以前写的更好维护,更可读,其实对茫茫的像我一样的切图仔来说就已经提高非常多的效率了,慢慢积累,慢慢改进,不断进步!

相关的代码已经整理成一个仓库上传至github,感兴趣可以结合仓库代码阅读REACT-SOLID