引言
在现代软件开发中,模块化开发已经成为一种重要的设计模式,它能够帮助团队管理复杂性、提高开发效率并提升代码的可维护性。模块化开发的核心思想是将系统拆分成多个独立的模块,每个模块负责系统的一部分功能,这样不仅可以实现功能的复用,还能简化系统的扩展和维护。
以直播间应用为例,它通常涉及多个复杂的功能模块,例如视频播放、实时聊天、用户管理以及礼物系统等。每个模块都有其独特的需求和实现方式,将这些功能模块化,可以使得开发过程更加高效,且各个模块之间的依赖关系也更加明确。在实际开发中,模块化不仅能够帮助团队更好地分工合作,还能在项目迭代中更容易地进行功能的扩展和更新。
本文将探讨如何在实际开发中应用模块化开发,以直播间为例,详细介绍模块化开发的概念、优势及其在直播间应用中的具体实现方法。我们将从直播间的业务需求入手,逐步拆解系统功能,展示如何将复杂的应用拆分成多个可管理的模块,并探讨在模块化开发中遇到的挑战和解决方案。
核心概念
模块就是一个功能单一的,具有明确接口的单元代码,每个模块封装了特定了功能和业务逻辑,相对独立。这种方式适合较为复杂的业务场景和多人团队,它不仅可以提高代码的可复用性,还能大大提升了代码的可维护和可扩展性,提升开发效率和代码质量。
直播间的业务需求
直播间业务相对其它业务而言有一个明显的特性,功能较为集中,这样当我们进行需求开发时团队中的所有人都在做直播间需求向同一个视图控制器添加代码,不仅会导致这个视图控制器代码臃肿,在进行代码合并和代码梳理时也将会是一个难题。
而且直播间通常会伴随着主播观众等不同的角色,还有语音,视频,电台,密码等等不同的房间类型,如果每个角色每个房间类型都需要有的功能如果我们针对每个房间类型,每个角色都写一遍这个功能那明显是白白浪费大量时间,且当需求发生变更时,维护起来也将及其困难。
但是如果将功能进行模块划分,比如分出用户卡片功能,礼物系统,用户管理三个模块,我们将这三个模块针对不同的用户和房间类型进行模块注册,达到复用,而每个功能的代码就只需要在自己的模块内实现,模块之间互不影响,达到解耦。
模块化
定义模块抽象基类
为了实现模块化,首先我们需要为模块定义统一的接口,通过创建父类的形式,让其它所有模块都继承自该基类,然后重写父类的接口方法。
open class PHModule: NSObject {
/// 模块标识
private(set) var moduleIdentifier: String!
/// 模块描述
private(set) var moduleDescription: String!
/// 所属视图控制器
private(set) public weak var ownerController: UIViewController!
/// 加载状态
var isLoaded: Bool = false
/// 初始化模块
/// - Parameters:
/// - moduleIdentifier: 模块标识
/// - moduleDescription: 模块描述
/// - ownerController: 所属视图控制器
/// - isLoaded: 是否加载
required public init(moduleIdentifier: String, moduleDescription: String, ownerController: UIViewController, isLoaded: Bool) {
self.moduleIdentifier = moduleIdentifier
self.moduleDescription = moduleDescription
self.ownerController = ownerController
self.isLoaded = isLoaded
}
/// 模块开始加载
open func moduleRun() {
// 子类实现
}
/// 模块加载完成
open func moduleDidLoad() {
// 子类实现
}
/// 模块销毁
open func moduleDestroy() {
// 子类实现
}
}
基类中一个最重要的属性就是模块标识,它是模块的身份证。
另外我们还定义了三个生命周期方法,分别为模块开始注册,模块所依赖的视图控制器环境已经准备完成,模块卸载。
子类模块只需要在这些已经定义好的方法中进行业务处理。
模块管理
定义好模块的基类之后我们还需要一个模块管理类,用来注册,获取,和删除模块,并管理所有模块的生命周期。
初始化
定义了一个ownerController属性,用来执行模块管理器所属的视图控制,在初始化赋值给
使用字典来存储所有已注册的模块,这样在一定程度上能够提高模块的查询速度。
public class PHModuleManager: NSObject {
/// 已注册 模块总表
private var moduleTable: [String:PHModule] = [:]
/// 所属视图控制器
private(set) weak var ownerController: UIViewController!
/// 创建模块管理器
/// - Parameter ownerController: 所属视图控制器
/// - Returns: 模块管理器
init(ownerController: UIViewController) {
super.init()
self.ownerController = ownerController
}
....
}
模块加载注册
定义对方方法,加载所有需要注册的模块进行模块注册。
/// 加载所有模块
/// - Parameter modules: 模块模型数组
func loadModules(modules: [PHModuleModel]) {
/// 模块排序
let sortedModules = modules.sorted { (module1, module2) -> Bool in
return module1.moduleIndex < module2.moduleIndex
}
for module in sortedModules {
registerModule(module: module)
}
}
/// 注册模块
/// - Parameter module: 模块模型
func registerModule(module: PHModuleModel) {
guard let moduleIdentifier = module.moduleIdentifier else {
assert(false, "模块标识不能为空")
return
}
guard moduleTable[moduleIdentifier] == nil else {
assert(false, "模块标识已存在")
return
}
guard let moduleClassString = module.moduleClassString else {
assert(false, "模块类名不能为空")
return
}
guard let moduleDescription = module.moduleDescription else {
assert(false, "模块描述不能为空")
return
}
let className = "ShortVideo.\(moduleClassString)"
guard let moduleClass = NSClassFromString(className) as? PHModule.Type else {
assert(false, "模块类不存在")
return
}
let moduleInstance = moduleClass.init(moduleIdentifier: moduleIdentifier, moduleDescription: moduleDescription, ownerController: ownerController, isLoaded: false)
moduleInstance.moduleRun()
moduleTable[moduleIdentifier] = moduleInstance
}
其中PHModuleModel为自定义的数据模型,它主要定义了模块的标识,描述以及模块类名等信息,代码如下:
public class PHModuleModel: NSObject {
/// 模块标识
public var moduleIdentifier: String?
/// 模块序号(用来排序)
public var moduleIndex: Int = 0
/// 模块描述
public var moduleDescription: String?
/// 模块类字符串
public var moduleClassString: String?
/// 模块接收的消息
public var receiverMessage: [String] = []
}
模块获取
根据模块id获取指定模块,方便必要时模块之间的相互调用。
/// 获取指定模块
/// - Parameter moduleIdentifier: 模块标识
/// - Returns: 模块
func module(moduleIdentifier: String) -> PHModule {
guard let module = moduleTable[moduleIdentifier] else {
assert(false, "模块不存在")
return PHModule(moduleIdentifier: "", moduleDescription: "", ownerController: ownerController, isLoaded: false)
}
return module
}
模块移除
根据模块标识删除指定模块,将一次性的功能模块释放掉,释放内存。比如在整个直播间生命周期内只出现一次的签到弹窗,首充奖励之类的功能。
/// 移除指定模块
/// - Parameter moduleIdentifier: 模块标识
/// - Returns: 是否移除成功
func removeModule(moduleIdentifier: String) -> Bool {
guard let module = moduleTable[moduleIdentifier] else {
return false
}
module.moduleDestroy()
guard let _ = moduleTable.removeValue(forKey: moduleIdentifier) else {
assert(false, "模块移除失败")
return false
}
return true
}
当直播间销毁时,或者是切换直播间时,有时候我们需要销毁所有的模块,在新的主播间重新注册,已避免两个直播间的数据混淆。
/// 移除所有模块
func removeAllModules() {
for (_,module) in moduleTable {
module.moduleDestroy()
}
moduleTable.removeAll()
}
使用示例
这样一个最简的模块化管理框架我们就搭建完成了,下面我就来使用它在模拟的场景中来创建一个用户卡片模块以及礼物展示模块。
创建模块
首先我们继承自PHModule创建两个模块
用户卡片模块:
//
// PHUserCardModule.swift
// PHRoomModuleManager_Example
//
// Created by 王国松 on 2024/8/16.
// Copyright © 2024 CocoaPods. All rights reserved.
//
import UIKit
import PHRoomModuleManager
class PHUserCardModule: PHModule {
/// 用户卡
var userCard = UIView()
override func moduleRun() {
print("用户卡片模块开始加载")
}
override func moduleDidLoad() {
print("用户卡片模块加载完成")
self.ownerController.view.addSubview(userCard)
userCard.frame = CGRect(x: 100, y: 200, width: 100, height: 100)
userCard.backgroundColor = .red
}
override func moduleDestroy() {
print("用户卡片模块销毁")
}
deinit {
print("用户卡片模块释放")
}
}
礼物展示模块:
class PHGiftShowModule: PHModule {
/// 礼物展示视图
var giftShowView = UIView()
override func moduleRun() {
print("礼物展示模块开始加载")
}
override func moduleDidLoad() {
print("礼物展示模块加载完成")
self.ownerController.view.addSubview(giftShowView)
giftShowView.frame = CGRect(x: 100, y: 500, width: 100, height: 100)
giftShowView.backgroundColor = .green
}
override func moduleDestroy() {
print("礼物展示模块销毁")
}
deinit {
print("礼物展示模块销毁")
}
}
使用一个PHRoomModuleBuilder类统一管理模块的创建工作:
class PHRoomModuleBuilder: NSObject {
/// 模块列表
private(set) var modules: [PHModuleModel] = []
/// 创建所有模块
func buildModules() {
// 用户卡片
addModule(moduleIdentifier: PHUserCardModuleIdentifier, moduleDescription: "用户卡片模块",moduleClassString: "PHUserCardModule", moduleIndex: 0)
// 礼物展示模块
addModule(moduleIdentifier: PHGiftShowModuleIdentifier, moduleDescription: "礼物展示模块",moduleClassString: "PHGiftShowModule", moduleIndex: 1)
}
/// 添加模块
/// - Parameters:
/// - moduleIdentifier: 模块标识
/// - moduleDescription: 模块描述
/// - moduleClassString: 模块类
/// - moduleIndex: 模块索引
/// - isLoaded: 是否加载
private func addModule(moduleIdentifier: String, moduleDescription: String,moduleClassString:String, moduleIndex: Int) {
let module = PHModuleModel()
module.moduleIdentifier = moduleIdentifier
module.moduleDescription = moduleDescription
module.moduleClassString = moduleClassString
module.moduleIndex = moduleIndex
modules.append(module)
}
}
初始化模块管理器
在直播间的控制器内创建模块管理器,并加载已添加的两个模块。
class PHRoomViewController: UIViewController {
/// 模块管理器
private var moduleManager: PHModuleManager!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
setupModuleManager()
moduleManager.allModuleDidLoad()
}
/// 初始化模块管理器
private func setupModuleManager() {
let builder = PHRoomModuleBuilder()
builder.buildModules()
moduleManager = PHModuleManager(ownerController: self)
moduleManager.loadModules(modules: builder.modules)
}
deinit {
moduleManager.removeAllModules()
print("房间控制器销")
}
}
运行代码,点击弹出直播间视图,再返回首页,控制台log如下:
可以看到所有的模块的生命周期方法都正常执行了。
我们再来看一下直播间的UI:
用户卡片的视图,和礼物展示的视图也都被添加到直播间上了。
那么接下来新的业务需求,也都可以通过创建新模块的方式来注册到直播间中。
一个最简版的模块化雏形就完成了。
结语
现在我们已经有了最基础的模块化结构,我们可以在不同的模块中实现不同的业务功能,也可以根据用户类型或者房间类型来确定需要加载哪些模块,注册哪些模块。每个模块的功能都非常独立,可复用,可移植,这是个很好的开端。
但是我们在实际使用时,仍然会面临一些问题,比如现在有一个用户任务的业务功能,但是请求这个数据的前提时需要先获取到主播数据,这样就涉及到模块间的先后顺序问题,已经模块间的消息通信和数据传递问题,下一篇博客我们就来介绍一下关于模块间的数据通讯。