第7章:协议在跨平台开发中的应用
7.2 跨平台业务逻辑的协议化实现
在跨平台开发中,业务逻辑是应用的核心部分,通常需要在多个平台上保持一致。通过协议化编程(POP),我们可以将业务逻辑抽象为与平台无关的接口,并通过协议定义依赖关系,从而实现跨平台的复用和解耦。本节将探讨如何使用协议设计和实现跨平台的业务逻辑,分析其实现步骤,并通过示例展示其实际应用。
为什么需要协议化业务逻辑?
业务逻辑(如用户认证、数据处理)通常是平台无关的,但其依赖的服务(如网络、存储)因平台而异。直接将业务逻辑与平台实现耦合会导致重复代码和维护困难。协议化方法通过以下方式解决问题:
- 抽象依赖:将业务逻辑与平台特定服务分离。
- 统一接口:为跨平台逻辑提供一致的调用方式。
- 复用性:一套逻辑代码适配多个平台。
设计跨平台业务逻辑的步骤
- 定义业务协议:
- 提取业务逻辑的核心行为,设计通用接口。
- 抽象依赖服务:
- 使用协议定义业务逻辑所需的外部服务。
- 实现平台特定依赖:
- 为每个平台提供具体实现。
- 注入依赖:
- 通过依赖注入将平台实现传入业务逻辑。
示例:用户认证系统
假设我们要实现一个跨平台的用户认证系统,支持登录和用户信息获取。
第一步:定义业务协议
protocol AuthenticationService {
func login(username: String, password: String) -> Bool
func getUserInfo() -> User?
}
struct User {
let id: String
let name: String
}
AuthenticationService定义了认证业务的核心功能。
第二步:抽象依赖服务
业务逻辑依赖网络和存储服务,用协议抽象:
protocol NetworkClient {
func request(endpoint: String, parameters: [String: String]) -> Data?
}
protocol UserStore {
func saveUser(id: String, name: String)
func loadUser() -> User?
}
第三步:实现跨平台业务逻辑
创建一个业务模块,通过协议依赖外部服务:
class AuthManager: AuthenticationService {
private let network: NetworkClient
private let store: UserStore
init(network: NetworkClient, store: UserStore) {
self.network = network
self.store = store
}
func login(username: String, password: String) -> Bool {
let params = ["username": username, "password": password]
if let data = network.request(endpoint: "/login", parameters: params) {
let user = parseUser(from: data)
if let user = user {
store.saveUser(id: user.id, name: user.name)
return true
}
}
return false
}
func getUserInfo() -> User? {
return store.loadUser()
}
private func parseUser(from data: Data) -> User? {
// 模拟解析,实际应使用 JSONDecoder
return User(id: "u001", name: "TestUser")
}
}
AuthManager是跨平台业务逻辑,只依赖协议接口。
第四步:实现平台特定依赖
为 iOS 和模拟环境提供实现:
struct iOSNetworkClient: NetworkClient {
func request(endpoint: String, parameters: [String: String]) -> Data? {
// 模拟 iOS 网络请求
print("iOS request to \(endpoint) with \(parameters)")
return "mock data".data(using: .utf8)
}
}
struct iOSUserStore: UserStore {
func saveUser(id: String, name: String) {
UserDefaults.standard.set(name, forKey: "user_\(id)")
}
func loadUser() -> User? {
if let name = UserDefaults.standard.string(forKey: "user_u001") {
return User(id: "u001", name: name)
}
return nil
}
}
struct MockNetworkClient: NetworkClient {
func request(endpoint: String, parameters: [String: String]) -> Data? {
return "mock data".data(using: .utf8)
}
}
struct MockUserStore: UserStore {
private var user: User?
mutating func saveUser(id: String, name: String) {
user = User(id: id, name: name)
}
func loadUser() -> User? {
return user
}
}
iOSNetworkClient和iOSUserStore为 iOS 提供实现。MockNetworkClient和MockUserStore用于测试或模拟。
使用业务逻辑
let iOSAuth = AuthManager(network: iOSNetworkClient(), store: iOSUserStore())
let success = iOSAuth.login(username: "alice", password: "123")
print("Login \(success ? "succeeded" : "failed")") // 输出: Login succeeded
if let user = iOSAuth.getUserInfo() {
print("User: \(user.name)") // 输出: User: TestUser
}
- 业务逻辑无需修改即可适配不同平台。
测试跨平台逻辑
为 AuthManager 添加单元测试:
import XCTest
class AuthManagerTests: XCTestCase {
func testLoginSuccess() {
var mockStore = MockUserStore()
let auth = AuthManager(network: MockNetworkClient(), store: mockStore)
let success = auth.login(username: "bob", password: "456")
XCTAssertTrue(success)
XCTAssertEqual(auth.getUserInfo()?.name, "TestUser")
}
func testLoginFailure() {
struct FailingNetwork: NetworkClient {
func request(endpoint: String, parameters: [String: String]) -> Data? { nil }
}
var mockStore = MockUserStore()
let auth = AuthManager(network: FailingNetwork(), store: mockStore)
let success = auth.login(username: "bob", password: "456")
XCTAssertFalse(success)
XCTAssertNil(auth.getUserInfo())
}
}
- 测试验证了成功和失败路径,隔离了平台依赖。
跨平台实现的优势
- 复用性:
AuthManager的逻辑跨平台共享,只需替换依赖实现。
- 解耦性:
- 业务逻辑不依赖具体网络或存储,易于维护。
- 测试性:
- Mock 对象模拟依赖,测试无需真实平台环境。
- 一致性:
- 协议确保所有平台实现行为一致。
扩展到其他平台
为 Android 添加实现(假设 Swift 可跨平台编译):
struct AndroidNetworkClient: NetworkClient {
func request(endpoint: String, parameters: [String: String]) -> Data? {
print("Android request to \(endpoint)")
return "android data".data(using: .utf8)
}
}
struct AndroidUserStore: UserStore {
func saveUser(id: String, name: String) {
print("Android save: \(id) - \(name)")
}
func loadUser() -> User? {
return User(id: "u001", name: "AndroidUser")
}
}
let androidAuth = AuthManager(network: AndroidNetworkClient(), store: AndroidUserStore())
androidAuth.login(username: "charlie", password: "789")
- 只需提供 Android 实现,业务逻辑保持不变。
注意事项
- 协议设计:保持接口通用,避免平台特定假设。
- 条件编译:使用
#if os()区分平台实现。 - 性能:动态分派可能影响性能,可优化为静态调用。
小结
通过协议化实现跨平台业务逻辑,我们将核心逻辑与平台依赖分离,实现了代码复用和解耦。本案例展示了设计和测试的全过程,为跨平台开发提供了实用模板。下一节将探讨协议在 UI 层跨平台中的应用,进一步扩展其用途。
