📱 系列一:架构思想进阶 | 第3篇
SOLID 原则与设计模式实战:从"代码搬运工"到"架构师"的必经之路
本文导读
你是否遇到过这样的情况:改一个登录逻辑,结果把支付模块搞崩了?或者增加一个分享渠道,需要改动十几个文件?
这通常不是代码量的问题,而是设计质量 的问题。
本文将带你深入理解 SOLID 五大设计原则 ,并结合 企业级设计模式实战 ,教你如何写出像"乐高积木"一样、可插拔、可扩展、可维护的代码。
全文较长,建议配合咖啡阅读,并准备好你的 IDE。
0. 前言:为什么你写的代码总是"牵一发而动全身"?
让我们先看一段真实的"事故现场"代码。
需求:App 需要支持三种登录方式:账号密码、短信验证码、微信登录。
事故代码:
kotlin
class LoginManager {
fun login(type: Int, username: String, password: String) {
if (type == 1) {
// 账号密码登录
// 校验逻辑...
// 网络请求...
// 保存数据...
} else if (type == 2) {
// 短信登录
// 校验逻辑...
// 网络请求...
// 保存数据...
} else if (type == 3) {
// 微信登录
// 校验逻辑...
// 网络请求...
// 保存数据...
}
}
}
问题来了:
- 产品经理说:"加一个 Apple 登录"。你需要改动
LoginManager,改if-else,改测试方法。 - 测试说:"短信登录有 Bug"。你改了短信逻辑,结果不小心把账号密码登录的逻辑改坏了。
- 新同事接手:看着这一坨
if-else,不敢动,只能复制粘贴加新逻辑。
这就是"面条代码"的代价。
要解决这个问题,我们需要 SOLID 原则 和 设计模式。
1. SOLID 原则:面向对象设计的五根支柱
SOLID 是 Robert C. Martin("Bob 大叔")提出的五大面向对象设计原则。它们是写出好代码的"宪法"。
1.1 单一职责原则(SRP):一个类只做一件事
定义:一个类应该只有一个引起它变化的原因。
通俗解释 :别让一个类身兼数职。比如,User 类只负责用户属性,不要把"保存用户到数据库"的逻辑也塞进去。
反面教材:
kotlin
// ❌ 违反 SRP:这个类既管用户属性,又管存储,又管校验
class User {
var id: String = ""
var name: String = ""
fun saveToDb() { /* 数据库操作 */ }
fun validatePassword(): Boolean { /* 校验逻辑 */ }
}
正面教材:
kotlin
// ✅ 符合 SRP:各司其职
data class User(val id: String, val name: String) // 只管数据
class UserRepository { // 只管存储
fun save(user: User) {}
}
class UserValidator { // 只管校验
fun validate(user: User): Boolean {}
}
企业级收益:
- 代码易读:找功能知道去哪个类。
- 易维护:改存储逻辑不会影响校验逻辑。
- 易测试:可以单独测试校验逻辑。
1.2 开闭原则(OCP):对扩展开放,对修改关闭
定义:软件实体(类、模块、函数)应该对扩展开放,对修改关闭。
通俗解释 :当需求变化时,尽量不要改原来的代码 ,而是通过新增代码来实现。
反面教材(就是我们开头的那个):
kotlin
// ❌ 违反 OCP:每次加新登录方式,都要改这个类
class LoginManager {
fun login(type: Int) {
if (type == 1) { /* ... */ }
else if (type == 2) { /* ... */ }
}
}
正面教材(使用多态):
kotlin
// ✅ 符合 OCP:新增登录方式只需新增类,不改现有代码
interface LoginStrategy {
fun login()
}
class PasswordLogin : LoginStrategy {
override fun login() { /* 账号密码逻辑 */ }
}
class SmsLogin : LoginStrategy {
override fun login() { /* 短信逻辑 */ }
}
class LoginManager(
private val strategy: LoginStrategy
) {
fun login() {
strategy.login() // 对修改关闭
}
}
// 使用时
val manager = LoginManager(PasswordLogin())
manager.login()
// 新增微信登录?加一个 WechatLogin 类,LoginManager 一动不动。
企业级收益:
- 稳定:老代码不动,就不会引入新 Bug。
- 灵活:像搭积木一样加功能。
1.3 里氏替换原则(LSP):子类必须能替换父类
定义:所有引用基类的地方,必须能透明地使用其子类的对象。
通俗解释:如果你用父类的地方,换成子类后程序出错了,那就违反了 LSP。
反面教材:
kotlin
open class Bird {
open fun fly() {}
}
class Ostrich : Bird() {
override fun fly() {
throw UnsupportedOperationException("鸵鸟不会飞")
}
}
fun letBirdFly(bird: Bird) {
bird.fly() // 如果是鸵鸟,这里就崩了
}
正面教材:
kotlin
// 重新设计
interface Flyable {
fun fly()
}
abstract class Bird
class Sparrow : Bird(), Flyable {
override fun fly() { /* 飞 */ }
}
class Ostrich : Bird() // 鸵鸟不实现 Flyable
企业级收益:
- 避免"假继承"带来的逻辑漏洞。
- 保证多态的安全性。
1.4 接口隔离原则(ISP):客户端不应该被迫依赖它不需要的接口
定义:客户端不应该被迫依赖它不需要的接口。
通俗解释:别让一个接口胖得像头猪。应该拆成小而专的接口。
反面教材:
kotlin
// ❌ 违反 ISP:一个巨大的接口
interface Worker {
fun code()
fun test()
fun deploy()
fun cook() // 厨师也要实现这个?
}
class Developer : Worker {
override fun code() {}
override fun test() {}
override fun deploy() {}
override fun cook() {
// 我是个开发,我不会做饭,但为了接口不得不空实现
throw NotImplementedError()
}
}
正面教材:
kotlin
// ✅ 符合 ISP:拆分成小接口
interface Coder {
fun code()
}
interface Tester {
fun test()
}
interface Chef {
fun cook()
}
class Developer : Coder, Tester {
override fun code() {}
override fun test() {}
}
企业级收益:
- 接口清晰,实现类不用背负无用的负担。
- 降低耦合。
1.5 依赖倒置原则(DIP):面向接口编程,而非实现
定义:高层模块不应该依赖低层模块,二者都应该依赖抽象。
通俗解释 :别直接 new 具体的实现类,要依赖接口。
反面教材:
kotlin
// ❌ 违反 DIP:高层直接依赖低层实现
class LoginActivity {
// 直接依赖具体的实现
private val repository = UserRepositoryImpl()
}
正面教材:
kotlin
// ✅ 符合 DIP:依赖抽象
class LoginActivity(
private val repository: UserRepository // 依赖接口
) {
// 不管是 Room 还是 Network,只要是 UserRepository 就行
}
企业级收益:
- 可替换:明天换数据库,Activity 不用改。
- 可测试:Mock 一个假的 Repository 就能测试 Activity。
2. 设计模式实战:用模式解决具体问题
设计模式是 SOLID 原则的落地手段。下面我们看几个 Android 开发中最常用的模式。
2.1 创建型模式:解决"对象怎么来"的问题
单例模式(Singleton):全局唯一实例
场景 :全局只需要一个实例,比如 NetworkClient、DatabaseManager。
错误写法(线程不安全):
kotlin
object Singleton {
// Kotlin 的 object 天生是线程安全的单例,推荐用这个
}
Java 风格的正确写法(双重校验锁):
kotlin
class Singleton private constructor() {
companion object {
@Volatile
private var instance: Singleton? = null
fun getInstance(): Singleton =
instance ?: synchronized(this) {
instance ?: Singleton().also { instance = it }
}
}
}
企业级建议:
- 能用 Kotlin
object就用object。 - 单例里不要持有 Activity Context(用 Application Context)。
- 能用 依赖注入(Hilt) 就别手写单例。
工厂模式(Factory):创建对象的统一入口
场景 :根据条件创建不同的对象(比如不同的 LoginStrategy)。
kotlin
class LoginFactory {
fun create(type: LoginType): LoginStrategy {
return when (type) {
LoginType.PASSWORD -> PasswordLogin()
LoginType.SMS -> SmsLogin()
LoginType.WECHAT -> WechatLogin()
}
}
}
企业级建议:
- ViewModel 的创建必须用
ViewModelProvider.Factory。 - 复杂对象的创建用工厂,别在 Activity 里直接
new。
建造者模式(Builder):复杂对象的构建
场景:创建一个对象需要很多参数,且参数可选。
经典案例 :AlertDialog.Builder
kotlin
val dialog = AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("确定删除?")
.setPositiveButton("确定") { _, _ -> }
.setNegativeButton("取消") { _, _ -> }
.create()
自定义 Builder:
kotlin
class RequestConfig private constructor(
val url: String,
val timeout: Int,
val retryCount: Int
) {
class Builder {
private var url: String = ""
private var timeout: Int = 30
private var retryCount: Int = 3
fun url(url: String) = apply { this.url = url }
fun timeout(timeout: Int) = apply { this.timeout = timeout }
fun build() = RequestConfig(url, timeout, retryCount)
}
}
// 使用
val config = RequestConfig.Builder()
.url("https://api.xxx.com")
.timeout(60)
.build()
2.2 结构型模式:解决"类和对象怎么组合"的问题
适配器模式(Adapter):让不兼容的接口一起工作
场景 :对接第三方 SDK,或者 RecyclerView 的 Adapter。
RecyclerView 示例:
kotlin
class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
private val userList = mutableListOf<User>()
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// 把 User 适配成 View
holder.bind(userList[position])
}
}
对接 SDK 示例:
kotlin
// 旧 SDK
class OldPaySdk {
fun doPay(money: Int) {}
}
// 新 App 需要的接口
interface NewPayApi {
fun pay(amount: Double)
}
// 适配器
class PayAdapter(private val oldSdk: OldPaySdk) : NewPayApi {
override fun pay(amount: Double) {
oldSdk.doPay(amount.toInt()) // 适配转换
}
}
装饰器模式(Decorator):动态添加功能
场景:给网络请求加日志、缓存、重试。
kotlin
interface HttpClient {
fun request(url: String): String
}
class BaseHttpClient : HttpClient {
override fun request(url: String): String {
return "Response from $url"
}
}
// 装饰器:加日志
class LoggingDecorator(private val client: HttpClient) : HttpClient {
override fun request(url: String): String {
println("Requesting: $url")
return client.request(url)
}
}
// 使用
val client = LoggingDecorator(BaseHttpClient())
client.request("https://api.xxx.com")
外观模式(Facade):简化复杂子系统
场景:App 启动时需要初始化一堆 SDK。
不用 Facade(Activity 里乱成一团):
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
UMengManager.init(this)
BuglyManager.init(this)
PushManager.init(this)
// ... 还有 10 个
}
}
用 Facade(一键初始化):
kotlin
object AppInitializer {
fun init(context: Context) {
initUMeng(context)
initBugly(context)
initPush(context)
}
}
// Activity 里
AppInitializer.init(this)
2.3 行为型模式:解决"对象之间怎么通信"的问题
观察者模式(Observer):事件订阅与通知
场景:LiveData、RxJava、Flow 都是观察者模式。
自定义实现:
kotlin
class EventBus {
private val listeners = mutableMapOf<Class<*>, MutableList<(Any) -> Unit>>()
fun <T : Any> subscribe(eventType: Class<T>, action: (T) -> Unit) {
listeners.getOrPut(eventType) { mutableListOf() }.add(action as (Any) -> Unit)
}
fun post(event: Any) {
listeners[event::class.java]?.forEach { it.invoke(event) }
}
}
// 使用
EventBus.subscribe(LoginEvent::class.java) { event ->
println("User logged in: ${event.userId}")
}
策略模式(Strategy):替换复杂的 if-else
场景:支付方式、分享渠道、排序算法。
这是我们开头解决的问题。通过 LoginStrategy 接口,消除了 if-else。
责任链模式(Chain of Responsibility):请求的处理链
场景:登录前的校验链(检查网络、检查 Token、检查权限)。
kotlin
abstract class Interceptor {
protected var next: Interceptor? = null
fun setNext(next: Interceptor): Interceptor {
this.next = next
return next
}
abstract fun intercept(request: LoginRequest): LoginResult
}
class NetworkInterceptor : Interceptor() {
override fun intercept(request: LoginRequest): LoginResult {
if (!hasNetwork()) return LoginResult.Error("No network")
return next?.intercept(request) ?: LoginResult.Success
}
}
// 组装链条
val chain = NetworkInterceptor()
chain.setNext(TokenInterceptor())
.setNext(PermissionInterceptor())
.setNext(RealLoginInterceptor())
状态模式(State):对象状态的行为变化
场景:订单状态(待支付、已支付、已发货、已完成)。
kotlin
interface OrderState {
fun pay()
fun ship()
}
class PendingState : OrderState {
override fun pay() { println("Paid") }
override fun ship() { println("Cannot ship, not paid") }
}
class PaidState : OrderState {
override fun pay() { println("Already paid") }
override fun ship() { println("Shipped") }
}
class Order(var state: OrderState) {
fun pay() = state.pay()
fun ship() = state.ship()
}
3. 企业级实战:登录模块的重构之路
现在,让我们用 SOLID + 设计模式 重构开头的那个"事故代码"。
3.1 重构目标
- 消除
if-else。 - 新增登录方式不修改旧代码。
- 各模块职责单一。
3.2 重构后的架构
#mermaid-svg-fy3EKmQFgVBGltWM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fy3EKmQFgVBGltWM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fy3EKmQFgVBGltWM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fy3EKmQFgVBGltWM .error-icon{fill:#552222;}#mermaid-svg-fy3EKmQFgVBGltWM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fy3EKmQFgVBGltWM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fy3EKmQFgVBGltWM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fy3EKmQFgVBGltWM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fy3EKmQFgVBGltWM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fy3EKmQFgVBGltWM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fy3EKmQFgVBGltWM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fy3EKmQFgVBGltWM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fy3EKmQFgVBGltWM .marker.cross{stroke:#333333;}#mermaid-svg-fy3EKmQFgVBGltWM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fy3EKmQFgVBGltWM p{margin:0;}#mermaid-svg-fy3EKmQFgVBGltWM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fy3EKmQFgVBGltWM .cluster-label text{fill:#333;}#mermaid-svg-fy3EKmQFgVBGltWM .cluster-label span{color:#333;}#mermaid-svg-fy3EKmQFgVBGltWM .cluster-label span p{background-color:transparent;}#mermaid-svg-fy3EKmQFgVBGltWM .label text,#mermaid-svg-fy3EKmQFgVBGltWM span{fill:#333;color:#333;}#mermaid-svg-fy3EKmQFgVBGltWM .node rect,#mermaid-svg-fy3EKmQFgVBGltWM .node circle,#mermaid-svg-fy3EKmQFgVBGltWM .node ellipse,#mermaid-svg-fy3EKmQFgVBGltWM .node polygon,#mermaid-svg-fy3EKmQFgVBGltWM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fy3EKmQFgVBGltWM .rough-node .label text,#mermaid-svg-fy3EKmQFgVBGltWM .node .label text,#mermaid-svg-fy3EKmQFgVBGltWM .image-shape .label,#mermaid-svg-fy3EKmQFgVBGltWM .icon-shape .label{text-anchor:middle;}#mermaid-svg-fy3EKmQFgVBGltWM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fy3EKmQFgVBGltWM .rough-node .label,#mermaid-svg-fy3EKmQFgVBGltWM .node .label,#mermaid-svg-fy3EKmQFgVBGltWM .image-shape .label,#mermaid-svg-fy3EKmQFgVBGltWM .icon-shape .label{text-align:center;}#mermaid-svg-fy3EKmQFgVBGltWM .node.clickable{cursor:pointer;}#mermaid-svg-fy3EKmQFgVBGltWM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fy3EKmQFgVBGltWM .arrowheadPath{fill:#333333;}#mermaid-svg-fy3EKmQFgVBGltWM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fy3EKmQFgVBGltWM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fy3EKmQFgVBGltWM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fy3EKmQFgVBGltWM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fy3EKmQFgVBGltWM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fy3EKmQFgVBGltWM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fy3EKmQFgVBGltWM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fy3EKmQFgVBGltWM .cluster text{fill:#333;}#mermaid-svg-fy3EKmQFgVBGltWM .cluster span{color:#333;}#mermaid-svg-fy3EKmQFgVBGltWM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fy3EKmQFgVBGltWM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fy3EKmQFgVBGltWM rect.text{fill:none;stroke-width:0;}#mermaid-svg-fy3EKmQFgVBGltWM .icon-shape,#mermaid-svg-fy3EKmQFgVBGltWM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fy3EKmQFgVBGltWM .icon-shape p,#mermaid-svg-fy3EKmQFgVBGltWM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fy3EKmQFgVBGltWM .icon-shape rect,#mermaid-svg-fy3EKmQFgVBGltWM .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fy3EKmQFgVBGltWM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fy3EKmQFgVBGltWM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fy3EKmQFgVBGltWM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Data
Domain
UI
调用
依赖抽象
实现
实现
实现
LoginActivity
LoginUseCase
LoginStrategy
PasswordLoginStrategy
SmsLoginStrategy
WechatLoginStrategy
3.3 代码实现
1. 定义策略接口(DIP + OCP)
kotlin
interface LoginStrategy {
suspend fun login(credentials: Map<String, String>): Result<Boolean>
}
2. 实现具体策略(SRP)
kotlin
class PasswordLoginStrategy : LoginStrategy {
override suspend fun login(credentials: Map<String, String>): Result<Boolean> {
val username = credentials["username"] ?: return Result.failure(Exception("No username"))
val password = credentials["password"] ?: return Result.failure(Exception("No password"))
// 调用网络...
return Result.success(true)
}
}
class SmsLoginStrategy : LoginStrategy {
override suspend fun login(credentials: Map<String, String>): Result<Boolean> {
// 短信逻辑...
return Result.success(true)
}
}
3. 工厂创建策略(Factory)
kotlin
class LoginStrategyFactory {
fun create(type: LoginType): LoginStrategy {
return when (type) {
LoginType.PASSWORD -> PasswordLoginStrategy()
LoginType.SMS -> SmsLoginStrategy()
}
}
}
4. UseCase 编排(Facade + SRP)
kotlin
class LoginUseCase(
private val strategyFactory: LoginStrategyFactory
) {
suspend fun execute(type: LoginType, credentials: Map<String, String>): Result<Boolean> {
val strategy = strategyFactory.create(type)
return strategy.login(credentials)
}
}
5. ViewModel 调用(MVVM)
kotlin
@HiltViewModel
class LoginViewModel @Inject constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
private val _state = MutableStateFlow(LoginUiState())
val state = _state.asStateFlow()
fun login(type: LoginType, credentials: Map<String, String>) {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
val result = loginUseCase.execute(type, credentials)
_state.update { it.copy(isLoading = false, success = result.isSuccess) }
}
}
}
6. Activity 只负责 UI(Presentation Layer)
kotlin
class LoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 点击密码登录
binding.btnPasswordLogin.setOnClickListener {
viewModel.login(
LoginType.PASSWORD,
mapOf("username" to "xxx", "password" to "xxx")
)
}
// 点击短信登录
binding.btnSmsLogin.setOnClickListener {
viewModel.login(
LoginType.SMS,
mapOf("phone" to "13800138000")
)
}
}
}
3.4 重构收益对比
| 指标 | 重构前 | 重构后 |
|---|---|---|
| 新增微信登录 | 改 LoginManager,加 if-else |
新增 WechatLoginStrategy 类,不动任何老代码 |
| 测试 | 需要启动 App 测试 | 可以直接测试 PasswordLoginStrategy |
| 维护 | 牵一发而动全身 | 只改对应的 Strategy |
| 代码量 | 一个类几百行 | 每个类几十行,清晰 |
4. 总结与检查清单
4.1 什么时候用设计模式?
不要为了用模式而用模式。只有当你的代码出现以下症状时,才考虑模式:
- 症状 1 :
if-else/switch超过 3 层。 - 症状 2:一个类超过 300 行,且职责混乱。
- 症状 3:修改一个功能会影响其他功能。
4.2 企业级检查清单
在项目 Code Review 时,请对照这份清单:
SOLID 检查:
- 类是否只有一个职责?(SRP)
- 新增功能是否主要靠新增类,而不是改老类?(OCP)
- 子类是否能无缝替换父类?(LSP)
- 接口是否小而专?(ISP)
- 是否依赖抽象接口,而不是具体实现?(DIP)
设计模式检查:
- 对象创建是否用了工厂/建造者?
- 复杂初始化是否用了外观模式?
- 消除
if-else是否用了策略模式? - 请求预处理是否用了责任链?
- 状态变化是否用了状态模式?
下期预告 :
系列二:MVVM 深度实战与项目重构 | 第4篇:组件化架构从零搭建实战
(我们将跳出单工程,开始拆分子模块,解决"编译慢、代码冲突、业务耦合"的终极难题。)
如果这篇文章帮你理清了思路,请分享给你的队友。毕竟,架构不是一个人的事,而是一群人的共识。