SOLID原则解析与实战
目标
- 软件系统的价值&架构工作的目标
- 了解编程范式的定义
- 掌握基础的设计原则-SOLID原则
硬实力 软实力 => 更长远 架构师
软件系统的价值&架构工作的目标
- 结构是系统的一部分
行为价值 核心价值 90% 工作内容
架构价值
- 当需求迭代时,软件变更更加便捷,开发效率更高
业务 => 技术
前端团队 10人:leader(1) > 架构师(2) > 开发(7)
紧张重要矩阵: 紧急 ↑ | 3.不重要且紧急 | 1.重要且紧急 | —————————————|—————————————→ 重要 | 4.不重要且不紧急| 2.重要且不紧急 | |
架构的目标
服务于开发
目标:用最少的人力成本满足构建和维护系统需求 支撑软件系统的声明周期 让系统便于理解 易于修改 方便维护 轻松部署
开发阶段:CLI产出、组件库、基础依赖 => 提高开发效率 部署阶段:CI/ID(持续集成/持续部署) 预发 线上 运行阶段:稳定性 容灾演练 安全攻防 灰度(少部分用户或者测试人员使用) 压测 AB(双版本) 维护阶段:告警监控 1、5、10规则(1min识别并记录、5min通知、10min做出响应)
编程范式
上层限制:
架构的限制 => 组件库 vue/react/cli 基础依赖
底层限制:
- 结构化编程 - 控制权直接转移 1968
- 面向对象编程 - 控制权间接转移 1966
- 函数式编程 - 限制赋值 1936
结构化编程
对控制权专一进行限制
顺序结构 分支结构 循环结构 => 搭建任何一个程序 限制
goto
(去执行别的程序)的使用,因为goto紊乱了结构导致不可控test case test() expect test case: all pass
总结:赋予可证伪程序单元的能力
面向对象编程
封装 继承 多态
在非面向对象的编程语言中 函数指针
struct FILE{
void (*open)(char* name,int mode);
void (*close)();
int (*read)();
}
面向对象限制了函数指针的使用
函数式编程
函数保持独立,没有副作用和不修改状态
React props
设计原则
SOLID 面向对象设计的五条原则(Bob 2000):
- S:单一职责原则 SRP
- O:开闭原则 OCP
- L:里氏替换原则 LSP
- I:接口隔离原则 ISP
- D:依赖倒置原则 DIP
设计class
结构应该遵守的准备和最佳实践。
- 设计原则是指导
- 不一定都要遵守
开闭原则 OCP
软件实体的行为应当不是修改实体,而是对实体进行拓展
抽象约束 封装变化
// 开闭原则
// 目标:已有的场景下,对于需要拓展的进行开放,拒接直接的功能修改
// scene 春节活动
function setColor(){
if (game === 'PUBG') {
} else if (game === 'LOL') {
} else {
}
}
// 重构
class Game {
constructor(name) {
this.name = name;
}
setColor() { }
openDialog() { }
}
class LOL extends Game {
openDialog() {
}
}
class PUBG extends Game {
openDialog() {
}
}
单一职责原则 SRP
一个对象(方法)只做一件事
考虑边界
class PUBG {
openDialog() {
// 计算金额
setPrice();
}
}
const pubg = new PUBG();
pubg.openDialog();
// 改写
class PUBG {
constructor(command) {
this.command = command;
}
openDialog(price) {
// 计算金额
this.command.setPrice(price);
}
}
class PriceManager {
setPrice(price) { }
}
const exe = new PriceManager();
const pubg2 = new PUBG(exe);
pubg2.openDialog(15)
依赖倒置原则 DIP
高级模块不应该依赖低级模块,他们都应当依赖抽象
抽象不应该依赖实现,而实现应该依赖抽象
// scene1 分享
class Store {
constructor() {
this.share = new Share();
}
}
class Share {
// 分享到不同平台
shareTo() { }
}
const store = new Store();
store.share.shareTo('wx');
// scene2 评价
class Store {
constructor() {
this.share = new Share();
this.rate = new Rate();
}
}
class Rate {
star() { }
}
const store2 = new Store();
store2.rate.star();
// 重构
// 暴露挂载 => 动态加载
class Store {
// 维护模块名单
static modules = new Map();
constructor() {
for (let module of Store.modules.values()) {
module.init(this);
}
}
static inject() {
Store.modules.set(this.modules.constructor.name, this.module)
}
}
class Share {
init(store) {
store.share = this;
}
// 分享到不同平台
shareTo() { }
}
class Rate {
init(store) {
store.rate = this;
}
star() { }
}
const rate = new Rate();
Store.inject(rate)
const share = new Share();
Store.inject(share)
const store3 = new Store();
store3.rate.star(5);
接口隔离原则 ISP
不应当强迫客户依赖他们不用的方法
目标:用多个专业的接口,而不是用单个大而全的接口
class Game {
constructor() {
}
run() { }
shot() { }
mega() { }
}
class LOL extends Game { }
class PUBG extends Game { }
const lol = new LOL();
lol.run();
const pubg = new PUBG();
pubg.run();
// 很明显不同游戏相同的行为处理不同
// 重构
class Game {
constructor() { }
run() { }
}
class MOBA extends Game {
mega() { }
}
class FPS extends Game {
shot() { }
}
// 然后再继承
里氏替换原则 LSP
如果S是T的子类,那么T的对象可以替换为S的对象,而不会破坏程序。
所有引用其父类方法的地方,都可以透明的替换其子类的对象。
class Game {
start() { }
shutDown() { }
play() { }
}
const game = new Game();
game.play();
class MobileGame extends Game {
play() { }
mobileStores() { }
}
const mobileGame = new MobileGame();
mobileGame.play();
// 重构
class Game {
start() { }
shutDown() { }
}
class MobileGame extends Game {
play() { }
mobileStores() { }
}
class PCGame extends Game {
play() { }
speed() { }
}