2、分层架构设计原则:单一职责原则、开闭原则、依赖倒置原则、接口隔离原则在多屏系统中的体现
好,咱们接着聊。上一章我讲了多屏系统的整体分层结构,那这一章,咱们得深入一点,聊聊支撑这套分层的四个核心设计原则。
说白了,原则就是规矩。没有规矩,不成方圆。尤其是在多屏系统这种复杂度极高的场景下,没有原则的代码,最后就是一坨谁也动不了的屎山。我见过太多这样的项目了,一开始图快,后面改一个bug要花三天。
这四个原则,就是帮你把代码写得「活」起来的钥匙。咱们一个一个看。
2.1 单一职责原则(SRP)
这个原则,字面意思很好理解:一个模块只干一件事。但在多屏系统里,它其实有更深的含义。
我举个例子。你有一个「用户登录」的模块。它是不是只负责登录?
嗯,看起来是。但在多屏场景下,登录模块可能要处理手机端的指纹登录、电视端的扫码登录、车机端的无感登录。如果你把这些逻辑全塞到一个类里,那这个类就「不单一」了。
正确的做法是什么?
把「登录」这个行为抽象出来。手机端只关心手机端的登录逻辑,电视端只关心电视端的。每个屏幕的登录模块,只对自己屏幕的交互负责。
我在项目中遇到过这样一个坑:一个同事把电视端的登录逻辑和手机端的混在一起,结果电视端要加一个「记住密码」功能,改完代码,手机端的登录就崩了。为什么?因为职责没分开,牵一发而动全身。
核心要点:
- 每个屏幕的UI层,只负责该屏幕的展示和交互。
- 业务逻辑层,只处理业务规则,不关心你在哪个屏幕上点。
- 数据层,只负责数据的存取,不关心数据怎么用。
我的小技巧: 写代码前,先问自己三个问题:这个类改了,会影响其他屏幕吗?会影响其他业务吗?会影响数据存储吗?如果答案是「会」,那说明职责没分清楚。
2.2 开闭原则(OCP)
开闭原则,说的是「对扩展开放,对修改关闭」。你想想看,多屏系统最怕什么?最怕加一个新屏幕。
比如,你原来支持手机和电视,现在要加一个车载屏幕。如果你的代码是写死的,那你就得去改原来的逻辑,改完还得重新测试手机和电视的功能。这成本太高了。
怎么做到开闭?
用抽象。把「屏幕」抽象成一个接口或者基类。手机、电视、车机都去实现这个接口。当你需要加一个新屏幕时,只需要写一个新的实现类,然后注册进去。原来的代码,一行都不用改。
我曾经在一个智能家居项目里,用这种方式支持了7种不同的屏幕。后来客户要加一个冰箱上的屏幕,我花了半天就搞定了。同事都惊呆了。其实没什么,就是遵循了开闭原则。
注意: 开闭原则不是让你把所有东西都抽象化。过度抽象也是一种灾难。我见过有人为了「开闭」,搞了七八层继承,最后自己都看不懂。适度就好。
2.3 依赖倒置原则(DIP)
这个原则,名字有点绕。说白了就是:高层模块不应该依赖低层模块,两者都应该依赖抽象。
在多屏系统里,这个原则特别重要。你想想,UI层是高层,数据层是低层。如果UI层直接依赖了数据层的具体实现,比如直接new了一个数据库对象,那你就完了。哪天你想把数据库从MySQL换成MongoDB,UI层也得跟着改。
正确的做法:
UI层依赖一个「数据接口」,数据层也去实现这个接口。这样,UI层不关心数据到底存在哪里,数据层也不关心UI层怎么展示。两者通过接口解耦。
我记得有一次,一个项目需要把本地存储换成云端存储。因为用了依赖倒置,我们只改了数据层的实现,UI层一行代码没动。测试的时候,UI的同事甚至不知道底层换了。
代码示例(伪代码):
// 不好的做法:UI层直接依赖具体数据类
class LoginUI {
private LocalDatabase db = new LocalDatabase();
public void login() {
db.saveUser(); // 直接调用
}
}
// 好的做法:依赖抽象接口
interface IUserRepository {
void saveUser();
}
class LocalDatabase implements IUserRepository {
public void saveUser() { /* 本地存储 */ }
}
class CloudDatabase implements IUserRepository {
public void saveUser() { /* 云端存储 */ }
}
class LoginUI {
private IUserRepository repo;
public LoginUI(IUserRepository repo) {
this.repo = repo; // 通过构造注入
}
public void login() {
repo.saveUser(); // 只依赖接口
}
}
2.4 接口隔离原则(ISP)
这个原则,说的是:客户端不应该依赖它不需要的接口。
嗯,这里要注意。在多屏系统里,不同屏幕的能力是不一样的。手机有触摸屏,电视有遥控器,车机有语音。如果你设计一个「万能屏幕接口」,里面包含了触摸、遥控、语音所有的方法,那手机端就得实现它用不到的遥控方法,电视端就得实现它用不到的触摸方法。
这会造成什么?接口臃肿,实现类里一堆空方法。看着就难受。
我的建议:
把大接口拆成小接口。比如:ITouchable、IRemoteControllable、IVoiceControllable。每个屏幕只实现它需要的接口。
我曾经接手过一个遗留系统,里面有一个叫 IDevice 的接口,里面有30多个方法。每个屏幕的实现类里,都有十几个方法直接抛出 NotImplementedException。那代码,简直没法看。后来我花了两个星期,把接口拆成了5个小接口,世界清净了。
避坑指南: 接口不是越多越好。拆得太细,管理起来也麻烦。我的经验是:如果一个接口里,有超过20%的方法在实现类里是空实现,那就该拆了。
2.5 四个原则如何协同工作
这四个原则不是孤立的。它们是一个整体。
- 单一职责 帮你把模块切小。
- 开闭原则 帮你把模块变活。
- 依赖倒置 帮你把模块解耦。
- 接口隔离 帮你把接口做精。
你想想看,一个系统,模块职责单一,对扩展开放,高层不依赖低层,接口又很干净。这样的系统,想不稳定都难。
我个人习惯,在开始一个新项目时,会先花一天时间,用这四个原则去审视我的分层设计。虽然前期慢一点,但后期会省下大量的时间。说白了,就是「磨刀不误砍柴工」。
总结一下:
| 原则 | 核心思想 | 多屏系统中的应用 |
|---|---|---|
| 单一职责 | 一个模块只做一件事 | 每个屏幕的UI层只负责自己的交互 |
| 开闭原则 | 对扩展开放,对修改关闭 | 新增屏幕时,不修改原有代码 |
| 依赖倒置 | 依赖抽象,不依赖具体实现 | UI层依赖数据接口,不依赖具体数据库 |
| 接口隔离 | 客户端不依赖不需要的接口 | 不同屏幕只实现自己需要的交互接口 |
好,这一章就到这里。下一章,我会讲讲具体的分层实现,比如怎么定义接口,怎么组织代码。咱们到时候见。