首页/新闻资讯/正文详情

面向对象六大基本原则:从概念到 Android 实战

发布时间:2026/9/27 23:06:55 来源:云帆数科 栏目:资讯中心
面向对象六大基本原则:从概念到 Android 实战
不少 Android 项目在第一个版本里都很“顺”Activity 里发请求、解析 JSON、更新 UI几百行代码也能按时上线。问题往往出现在后面——接口要增加公共参数、网络库要替换、列表要同时支持缓存、埋点和重试最后一个小改动牵动十几个页面。这时我们缺的通常不是又一个设计模式而是一套判断代码边界的方法。常说的面向对象六大基本原则包括原则英文缩写一句话理解单一职责原则SRP一个模块只对一类变化负责开闭原则OCP新需求优先通过扩展完成而不是反复修改稳定代码里氏替换原则LSP子类型必须能安全替换父类型或抽象依赖倒置原则DIP业务策略依赖抽象不依赖基础设施细节接口隔离原则ISP使用方只依赖自己真正需要的最小能力最少知识原则迪米特法则LoD对象只和直接协作者沟通少了解内部结构前五项就是经典的SOLID。国内很多资料把迪米特法则加入后合称“六大原则”。它们不是必须逐条套用的教条也不是类和接口越多越好。六项原则最终都在解决同一件事把变化限制在尽可能小的范围内。一、单一职责原则一个类只做一件事吗单一职责原则Single Responsibility PrincipleSRP更准确的定义是一个模块应该只有一个引起它变化的原因。“只做一件事”容易被误解。一个 Repository 可以同时包含查询、保存和删除方法只要这些方法都服务于同一类业务变化就仍然可以职责单一。真正的问题是把不同角色关心的变化混在一起。反例无所不能的 ViewModelclassOrderViewModel:ViewModel(){funloadOrders(){// 1. 拼接 URL 和公共参数// 2. 使用 OkHttp 发请求// 3. 使用 Gson 解析 JSON// 4. 写入 Room// 5. 计算订单展示状态// 6. 更新界面并上报埋点}}这段代码至少会因为六类原因变化后端协议、网络框架、序列化格式、数据库结构、业务规则、UI 与埋点。任何一处变化都需要修改同一个类它也很难被独立测试。Android 中的拆分方式classOrderViewModel(privatevalobserveOrders:ObserveOrdersUseCase):ViewModel(){valuiState:StateFlowOrderUiStateobserveOrders().mapListOrder,OrderUiState{OrderUiState.Success(it)}.stateIn(scopeviewModelScope,startedSharingStarted.WhileSubscribed(5_000),initialValueOrderUiState.Loading)}classOrderRepositoryImpl(privatevalremote:OrderRemoteDataSource,privatevallocal:OrderLocalDataSource):OrderRepository{overridefunobserveOrders():FlowListOrderlocal.observeOrders()overridesuspendfunrefresh(){local.replaceAll(remote.fetchOrders())}}职责可以这样划分ViewModel 负责把业务状态转换成 UI 状态UseCase 负责业务流程与规则Repository 负责协调数据来源RemoteDataSource 负责远端访问LocalDataSource 负责本地持久化Mapper 负责 DTO、Entity 与领域模型转换。Android 中常见的 SRP 场景把 Activity/Fragment 中的状态管理移入 ViewModel将 Retrofit 接口、缓存策略和业务规则分开RecyclerView 的 Adapter 只负责列表展示把点击后的业务动作交给上层WorkManager 的 Worker 负责任务调度具体同步逻辑由 UseCase 承担Compose 中把有状态组件与无状态 UI 拆分。不要拆得过细如果为了 SRP 把每个三行函数都变成一个类阅读一次业务要跳转十几个文件复杂度只是从类内部转移到了类之间。判断是否需要拆分可以问这几段代码是否由不同原因、不同角色推动变化它们能否独立测试或复用拆分后是否形成清晰边界而不只是增加文件数量二、开闭原则需求变化时增加代码而不是改遍代码开闭原则Open/Closed PrincipleOCP指的是软件实体应该对扩展开放对修改关闭。“关闭”不是一行旧代码都不能改而是让已经稳定、验证过的核心流程尽量不因新增类型而被反复修改。反例不断增长的whenfuntrack(event:TrackEvent){when(event.channel){Channel.FIREBASE-firebaseAnalytics.logEvent(event.name,event.params)Channel.UMENG-MobclickAgent.onEventObject(context,event.name,event.params)Channel.INTERNAL-api.upload(event)}}每增加一个埋点平台都要修改这个分支。如果类似判断散落在多个模块中新增渠道会变成一次全局搜索。通过扩展实现变化interfaceAnalyticsTracker{funtrack(event:TrackEvent)}classFirebaseTracker:AnalyticsTracker{overridefuntrack(event:TrackEvent){// 调用 Firebase SDK}}classCompositeTracker(privatevaltrackers:SetJvmSuppressWildcardsAnalyticsTracker):AnalyticsTracker{overridefuntrack(event:TrackEvent){trackers.forEach{it.track(event)}}}接入新平台时增加一个AnalyticsTracker实现并通过 Hilt multibinding 注入集合稳定的调用流程不需要改变。Android 中常见的 OCP 场景RecyclerView 使用不同ViewHolder/delegate 扩展新的 item 类型OkHttp 通过 Interceptor 扩展鉴权、日志、重试和公共参数Retrofit 通过 Converter.Factory 扩展序列化能力图片加载库通过接口支持磁盘、内存、网络等不同数据源Compose 通过Modifier链扩展布局和交互行为支付、分享、登录等多渠道能力使用策略实现。什么时候直接修改更合适如果需求只会出现一次、变化方向尚不明确提前设计扩展点可能得到一个没人需要的抽象。OCP 的前提是已经识别到稳定部分和可变部分没有稳定边界时先写清楚、补测试再随着真实变化重构。三、里氏替换原则能编译不代表能替换里氏替换原则Liskov Substitution PrincipleLSP要求使用父类型或抽象的地方替换成任意子类型后程序仍应保持正确行为。它约束的不只是方法签名还包括行为契约输入范围不能被子类无故收紧输出承诺不能削弱异常和副作用也应符合调用方预期。一个违反 LSP 的缓存实现interfaceUserCache{suspendfunsave(user:User)suspendfunget(id:String):User?}classReadOnlyUserCache:UserCache{overridesuspendfunsave(user:User){throwUnsupportedOperationException(read only)}overridesuspendfunget(id:String):User?null}ReadOnlyUserCache虽然实现了接口却无法履行save契约。任何认为UserCache可读写的调用方在替换后都会崩溃。更合理的方式是拆开能力interfaceUserReader{suspendfunget(id:String):User?}interfaceUserWriter{suspendfunsave(user:User)}classRoomUserCache(privatevaldao:UserDao):UserReader,UserWriter{overridesuspendfunget(id:String):User?dao.find(id)?.toDomain()overridesuspendfunsave(user:User){dao.insert(user.toEntity())}}Android 中常见的 LSP 场景RecyclerView.setLayoutManager()可以接收LinearLayoutManager、GridLayoutManager等实现Repository 的生产实现与 Fake 实现应保持相同业务语义替换 Retrofit、Ktor 或本地 Mock 数据源后上层不应感知基础设施差异自定义 View 覆盖父类方法时不能破坏测量、布局或生命周期约定List与可变集合要谨慎替换不能向调用方暗中暴露可变行为。检查 LSP 的实用问题Fake 是否为了测试方便返回了线上永远不可能出现的状态某个实现是否对“不支持”的方法直接抛异常子类是否要求比抽象更严格的参数替换实现后调用方是否还要写if (impl is Xxx)如果调用方必须识别具体实现才能正确工作抽象通常已经失效。四、依赖倒置原则让业务规则站在依赖图上层依赖倒置原则Dependency Inversion PrincipleDIP包含两层含义高层策略不应该依赖低层实现两者都应依赖抽象抽象不应该依赖细节细节应该依赖抽象。这里的“高层”是业务规则“低层”是 Retrofit、Room、文件系统、第三方 SDK 等技术细节。直接依赖细节classLoginViewModel:ViewModel(){privatevalapiRetrofit.Builder().baseUrl(BASE_URL).build().create(LoginApi::class.java)funlogin(account:String,password:String){viewModelScope.launch{api.login(LoginRequest(account,password))}}}ViewModel 知道 Retrofit、请求 DTO 和服务地址测试登录规则时也不得不处理网络细节。让基础设施实现业务所需的抽象interfaceAuthRepository{suspendfunlogin(account:String,password:String):LoginResult}classLoginUseCase(privatevalauthRepository:AuthRepository){suspendoperatorfuninvoke(account:String,password:String):LoginResult{require(account.isNotBlank()){账号不能为空}require(password.length8){密码至少 8 位}returnauthRepository.login(account,password)}}classNetworkAuthRepository(privatevalapi:LoginApi):AuthRepository{overridesuspendfunlogin(account:String,password:String):LoginResultapi.login(LoginRequest(account,password)).toDomain()}依赖方向变成ViewModel - LoginUseCase - AuthRepository - NetworkAuthRepository - Retrofit ^ └------ FakeAuthRepository测试Hilt/Dagger 等依赖注入框架负责在应用启动时完成“抽象到实现”的组装但要注意依赖注入是实现 DIP 的手段不等于 DIP 本身。即使手动构造对象只要依赖方向正确也符合原则。Android 中常见的 DIP 场景Domain 层定义 Repository 接口Data 层提供实现业务代码依赖Clock而不是直接调用System.currentTimeMillis()文件上传业务依赖Uploader而不是直接依赖某云厂商 SDK导航逻辑依赖Navigator避免 ViewModel 持有 Activity日志与埋点依赖应用自己的门面接口隔离第三方 SDK。五、接口隔离原则别让调用方被迫依赖无关能力接口隔离原则Interface Segregation PrincipleISP指的是客户端不应该依赖它不需要的方法类之间的依赖应建立在最小接口上。这里的“客户端”不是手机端而是接口的使用方。反例万能媒体接口interfaceMediaController{funplay()funpause()funseekTo(positionMs:Long)funstartRecording()funstopRecording()funtakePhoto()}一个只播放音频的类被迫实现录音、拍照方法最后只能留空或抛异常。这也容易进一步违反 LSP。按使用方需要拆分interfacePlayer{funplay()funpause()funseekTo(positionMs:Long)}interfaceRecorder{funstartRecording()funstopRecording()}interfaceCameraCapture{suspendfuntakePhoto():Uri}页面只注入它实际需要的能力classPodcastViewModel(privatevalplayer:Player):ViewModel()Android 中常见的 ISP 场景将巨大回调接口拆成OnItemClickListener、OnRetryListener等小接口按业务查询拆分 DAO避免所有模块都依赖一个“万能 DAO”权限请求器只暴露当前功能需要的权限能力模块对外只暴露入口路由或用例不泄露内部 Repository、DAO 和 DTOKotlin 中用函数类型替代只有一个方法的回调接口。ISP 关注的是调用方需要什么SRP 关注的是实现方因什么变化。两者经常一起出现但观察角度不同。六、最少知识原则不要穿透对象的内部结构最少知识原则Law of DemeterLoD又称迪米特法则核心可以概括为只与直接朋友通信不要让一个对象了解协作者的内部结构。反例跨越多层取数据valcitysessionManager.currentUser.profile.address.city调用方知道SessionManager - User - Profile - Address的完整结构。任何中间层变化、可空字段或延迟加载策略都可能迫使调用方修改。提供调用方真正需要的能力classUserSession(privatevaluserStore:UserStore){funcurrentCity():String?userStore.currentUser()?.profile?.address?.city}调用方只需要valcityuserSession.currentCity()内部数据如何组织被限制在UserSession内部。Android 中常见的 LoD 场景Fragment 不直接操作 Activity 内部的 View而是通过导航、回调或共享状态协作ViewModel 不持有 Activity/Fragment更不沿着 Context 获取其他组件业务模块不穿透 Repository 直接拿 DAO 或 Retrofit ServiceAdapter 通过回调上报用户意图不直接调用页面的 Repository模块间只通过公开 API 或导航契约通信不访问对方内部类。也不要走向“层层转发”为了遵守 LoD 给每个 getter 再包一层没有真正隐藏结构只会制造大量传话方法。好的门面方法应该表达业务意图例如canPlaceOrder()而不只是把getA().getB().getC()机械改写成三个代理 getter。七、六大原则如何共同落地以图片加载为例假设页面需要显示头像数据可能来自内存、磁盘或网络。一个可演进的最小设计如下interfaceImageSource{suspendfunload(key:String):ByteArray?}classImageLoader(privatevalsources:ListImageSource,privatevaldecoder:ImageDecoder){suspendfunload(key:String):Bitmap?{valbytessources.firstNotNullOfOrNull{it.load(key)}returnbytes?.let(decoder::decode)}}六项原则在这里不是六份独立代码而是互相配合SRP获取字节、解码图片、展示图片分别负责不同变化OCP新增 CDN、资源文件等来源时增加ImageSource实现LSP任意ImageSource都遵守“找不到返回 null”的契约DIPImageLoader依赖ImageSource和ImageDecoder抽象ISP数据源只需提供load不必实现清缓存、预加载等无关能力LoD页面只调用imageLoader.load(url)不知道缓存目录或 HTTP 客户端。这也是为什么设计原则不应该被单独背诵真实设计中一个合理边界往往同时体现多项原则。八、Android 项目中的快速检查清单做 Code Review 或重构前可以用下面的问题快速扫描职责Activity、Fragment、Composable 是否混入请求、缓存和业务规则一个类是否会因为多种互不相关的原因频繁修改扩展增加一种支付、登录或列表类型是否必须修改多个when/if变化方向是否已经稳定到值得建立扩展点契约Fake 和生产实现是否保持相同的成功、失败与空数据语义某些实现是否存在空实现或UnsupportedOperationException依赖ViewModel/UseCase 是否直接依赖 Retrofit、Room 或第三方 SDKDomain 层是否反向引用 Android Framework 类型接口调用方是否被迫实现或依赖自己不用的方法模块的公开 API 是否暴露了内部 DTO、Entity 或工具类协作是否出现a.b.c.d()式的对象链和跨层调用一个模块是否知道另一个模块过多的内部结构如果其中几个问题经常同时出现通常说明边界需要调整而不是简单再加一个 Utils 类。九、原则不是目标可维护性才是六大原则能帮助 Android 项目获得更好的可测试性、可替换性与扩展性但每一层抽象都有成本更多类型、更多跳转、更复杂的依赖图也可能带来理解和构建负担。实际开发中可以遵循三个步骤先写清楚命名准确、流程直接、测试覆盖关键行为识别变化观察哪些代码确实因为不同原因反复修改在变化处抽象只隔离真实存在的易变点不为想象中的未来建框架。判断设计好坏的标准不是用了多少接口、Repository 或 UseCase而是当需求变化时修改是否集中、旧行为是否稳定、新代码是否容易验证。六大原则最后可以浓缩成一句话让稳定的业务规则远离易变的实现细节让每次变化停在它应该停下的地方。参考资料面向对象六大基本原则——网络引擎切换Robert C. Martin,Agile Software Development: Principles, Patterns, and PracticesAndroid 官方架构指南Android 依赖注入与 Hilt

相关推荐

YOLO+CLIP多模态智能视频监控:实时检测与语义检索融合架构
YOLO+CLIP多模态智能视频监控:实时检测与语义检索融合架构

简介:这份资源面向计算机视觉与智能安防方向的学习者,提供一套融合CLIP与YOLO的智能视频监控系统实现方案,重点解决实时目标检测与自然语言检索监控画面的问题。包内共11个文件,以Python脚本、zbak备份文件、zip压缩包为主&#x… · 2026/9/27 23:06:55

C#实战:TSC标签打印机二维码打印源码解析与避坑指南
C#实战:TSC标签打印机二维码打印源码解析与避坑指南

简介:这份C#程序源码面向需要驱动TSC标签打印机输出二维码标签的开发人员,无论是刚接触打印指令的新手,还是希望借鉴成熟实现的经验开发者,都能从中获得可直接参考的完整方案。资源包共79个文件,约2.12MB,以… · 2026/9/27 23:06:54

原生家庭的术语大全的庖丁解牛
原生家庭的术语大全的庖丁解牛

总纲:原生家庭,指一个人从小到大成长、抚养长大的家庭,主要由父母(或抚养人)、兄弟姐妹构成,区别于成年后自己组建的新生家庭。原生家庭塑造早年认知、情绪模式、亲密关系模板,但不决定人的一生… · 2026/9/27 23:06:54

DeepSeek Harness 0.1.6-alpha.2 插件管理与桌面版升级实战
DeepSeek Harness 0.1.6-alpha.2 插件管理与桌面版升级实战

1. 这次 0.1.6-alpha.2 到底改了什么DeepSeek Harness 这个项目,从早期版本一路跟过来的人应该都有个共同感受:功能堆得快,但周边配套一直有点跟不上。0.1.5 那会儿装插件基本靠手动改配置文件,路径写错一个字符就整个加载失败&am… · 2026/9/27 23:44:17

198个C# WinForm实例源码实战:从跑通到改出可用的上位机
198个C# WinForm实例源码实战:从跑通到改出可用的上位机

简介:这是一套面向C#桌面开发者的WinForm实例源码合集,适合初学者入门练手,也适合有经验的开发者查阅参考。内容覆盖窗体设计、控件布局、图像处理、报表打印、系统信息获取、文件读写、网络通信、数据库访问、加密解密以及硬件读写等十余个方… · 2026/9/27 23:44:17

Cocos粒子系统3步出效果:爆炸、雨丝的保姆级参数清单
Cocos粒子系统3步出效果:爆炸、雨丝的保姆级参数清单

Cocos粒子系统3步出效果:爆炸、雨丝的保姆级参数清单 【免费下载链接】cocos-engine Cocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-… · 2026/9/27 23:44:17

缓存不该困在一台服务器里:写给新手的分布式缓存入门指南
缓存不该困在一台服务器里:写给新手的分布式缓存入门指南

👋 Hi,带娃的我热爱 AI 大模型应用落地、意识解码与 AI 开发工具链 。 💡 创业路上,用技术换时间,一起把 AI 变成生产力 🚀 >缓存不该困在一台服务器里:写给新手的分布式缓存入门指南 ① 这份… · 2026/9/27 23:44:11

Pi Agent插件精选:10个高效扩展与MCP配置优化指南
Pi Agent插件精选:10个高效扩展与MCP配置优化指南

1. 为什么“装一堆插件”反而拖慢了你的 Pi Agent刚接触 Pi Agent 的开发者,十有八九会经历一个相同的阶段:看到插件市场里琳琅满目的扩展,恨不得一口气全装上,觉得功能越多越安心。结果往往是启动变慢、响应迟钝、上下文被无关信… · 2026/9/27 23:44:11

个人博客网站注册避坑指南:对比5家平台哪家好,附设计规范与代码
个人博客网站注册避坑指南:对比5家平台哪家好,附设计规范与代码

个人博客网站注册避坑指南:对比5家平台哪家好,附设计规范与代码 别再被那些花里胡哨的模板网站骗了,看着精致,实则丑得让人尴尬。很多创业者第一次搞 个人博客网站注册… · 2026/9/27 23:44:04

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码