在前文中荡灾,我們?cè)O(shè)計(jì)了一種基于 UIStackView 的界面布局方案卧晓,實(shí)現(xiàn)了樣式聲明與事件響應(yīng)的分離。我們首先使用 UIStackView 來創(chuàng)建了一個(gè)登錄界面逼裆,通過對(duì)代碼進(jìn)行重構(gòu),實(shí)現(xiàn)了界面聲明與實(shí)現(xiàn)的分離耀怜,這不僅提高了代碼的解耦性桐愉,還增加了代碼的可復(fù)用性。最后从诲,我們?cè)谛碌募軜?gòu)上更進(jìn)一步,增加了一個(gè)界面樣式俊性,使得界面更加豐富多樣描扯。在本文中,我們將引入面向協(xié)議編程范式和依賴注入設(shè)計(jì)模式典徊,進(jìn)一步優(yōu)化代碼架構(gòu)恩够。
能用但不好用
在現(xiàn)有的代碼中,我們已經(jīng)實(shí)現(xiàn)了一些接口导绷。在 View Controller 中屎飘,我們利用這些接口完成界面的構(gòu)建,包括控件的添加钦购、樣式的設(shè)定和事件的綁定等。而在 generator 中 葵萎,我們實(shí)現(xiàn)這些接口以完成具體功能唱凯,例如繪制 UI、處理用戶輸入磕昼、響應(yīng)事件等票从。
為了評(píng)估接口的可用性,我們?cè)谕唤缑嬲故玖藘蓚€(gè)“登錄 / 注冊(cè)”模塊峰鄙,并修改代碼以檢查:
- 在構(gòu)建兩個(gè)相同功能的界面時(shí)是否存在代碼冗余?
- 使用我們的接口構(gòu)建“登錄 / 注冊(cè)”界面是否足夠方便魁蒜?
新增一種類型的 generator吩翻,為了與原有界面有所區(qū)別,我們會(huì)在界面樣式上做一點(diǎn)點(diǎn)修改[1]铣减。
// FILE: AnotherElementGenerator.swift
struct AnotherElementGenerator {
private(set) weak var containerView: UIStackView?
func elementView(from element: ElementType) -> UIView {
switch element {
case let .centeredText(title: title):
return createSingleLineText(title)
// ....
}
}
func addArrangedElements(_ elements: [ElementType]) {
for element in elements {
let subview = elementView(from: element)
containerView.addArrangedSubview(subview)
configureView(subview, for: element)
}
}
func configureView(_ view: UIView, for element: ElementType) {
switch element {
case let .spacer(height: height):
view.snp.makeConstraints { make in
make.height.equalTo(height)
}
default: break
}
}
}
private extension AnotherElementGenerator {
func createSingleLineText(_ title: String) -> UILabel {
// ....
}
// ....
}
此時(shí) View Controller 中的調(diào)用方式如下:
// FILE: StackViewController.swift
class StackViewController: UIViewController {
lazy var stackView = {
// ....
return stackView
}()
lazy var generator: ConcreteElementGenerator = {
return ConcreteElementGenerator(base: stackView)
}()
// 1
lazy var anotherStackView = {
// ....
return stackView
}()
lazy var anotherGenerator: AnotherElementGenerator = {
return AnotherElementGenerator(base: anotherStackView)
}()
override func viewDidLoad() {
// ....
// 2
view.addSubview(stackView)
view.addSubview(anotherStackView)
}
func loginElementList()-> [EType] {
return [
.segment(items: ["登錄", "注冊(cè)"], defaultIndex: 0, onTapped: nil),
.spacer(height: 15),
.commonInput(label: "User Name:", placeHolder: "Email/Phone/ID", onTextChanged: { text in
print("User Name: \(String(describing: text))")
}),
.spacer(height: 15),
.commonInput(label: "Password:", placeHolder: "Password", onTextChanged: { text in
print("Password: \(String(describing: text))")
}),
.spacer(height: 10),
.checker(title: "記住用戶名", checked: false, onTapped: { checked in
print("checked: \(checked)")
}),
.spacer(height: 10),
.button(title: "登錄", onTapped: nil)
]
}
func setupSubviews() {
let elementList = loginElementList()
generator.addArrangedElements(elementList)
// 3
anotherGenerator.addArrangedElements(elementList)
}
}
- 新增一個(gè) UIStackView 及其使用的 generator
- 添加 UIStackView 到界面
- 為新增的 UIStackView 添加子控件
代碼運(yùn)行結(jié)果如下:
那么問題來了葫哗。
接口實(shí)現(xiàn)是否存在冗余
兩個(gè) generator 代碼結(jié)構(gòu)幾乎一模一樣球涛,大概可以分為兩部分,第一部分 struct AnotherElementGenerator
主要用來添加子控件捺典,第二部分 extension AnotherElementGenerator
中定義了子控件的實(shí)際創(chuàng)建過程从祝。
在第一部分中引谜,addArrangedElements
函數(shù)沒有直接使用 ElementType
類型的枚舉值擎浴,因此可以抽出公共代碼。
在第二部分的代碼中贝室,為了方便仿吞,我們直接復(fù)制了 ConcreteElementGenerator
。在實(shí)際需求中唤冈,不同的 generator 可能會(huì)生成非常不同的子控件,因此這部分的代碼幾乎沒有冗余凉当。即使有部分子控件的樣式相同售葡,我們也可以通過抽象出工廠方法來解決。
調(diào)用接口是否足夠方便
在 View Controller 中楼雹,generator 的 addArrangedElements
函數(shù)與 UIStackView 的 addArrangedSubview
函數(shù)功能和參數(shù)相似尖阔,因此我們可以考慮將 addArrangedElements
函數(shù)遷移到 UIStackView。這樣介却,通過調(diào)用 UIStackView 的 addArrangedElements
函數(shù)來添加子控件齿坷,更符合接口使用者的習(xí)慣。
為了創(chuàng)建兩個(gè)登錄界面永淌,我們需要聲明兩個(gè) view 和兩個(gè) generator,這在只創(chuàng)建一個(gè)登錄界面時(shí)不明顯谭跨,但在多個(gè)界面時(shí)顯得繁瑣蛮瞄。因此裕坊,我們需要在 View Controller 的屬性選擇中做出決定,是保留 view 還是 generator苗缩?考慮到 generator 的接口只在調(diào)用 addArrangedElements
時(shí)使用酱讶,并且我們已經(jīng)決定將 addArrangedElements
函數(shù)遷移到 view泻肯,View Controller 將不再直接調(diào)用 generator 的函數(shù)灶挟,除了創(chuàng)建 generator稚铣。同時(shí)惕医,View Controller 持有 view 是合理的算色,因?yàn)闊o論如何灾梦,View Controller 都需要寫 addSubview
斥废。
總的來說牡肉,我們的代碼修改將主要關(guān)注 view 和 generator 之間的依賴關(guān)系。當(dāng)前的實(shí)現(xiàn)是 generator 弱引用 view炭庙,但在接下來的重構(gòu)中焕蹄,我們將改為 view 引用 generator腻脏。
我們的目標(biāo)是將接口的調(diào)用方式改為:
// FILE: StackViewController.swift
class StackViewController: UIViewController {
lazy var stackView = {
// ....
return stackView
}()
override func viewDidLoad() {
// ....
setupSubviews()
}
func setupSubviews() {
// ....
stackView.addArrangedElements(elementList)
}
}
而 view 如何引用 generator 將放在下一個(gè)章節(jié)討論永品。
解決問題
使用協(xié)議
在對(duì)比兩個(gè) generator 的代碼后,我們發(fā)現(xiàn)了一些共同的邏輯更振。我們可以通過定義協(xié)議來描述這些共性的代碼執(zhí)行邏輯肯腕,并為該協(xié)議添加默認(rèn)實(shí)現(xiàn)遵蚜。
// FILE: ElementGenerator.swift
// 1
protocol ElementGenerator {
/// 向 stack view 添加子控件
/// - Parameters:
/// - elements: 子控件列表
/// - stackView: stack view
func addArrangedElements(_ elements: [ElementType], to stackView: UIStackView)
/// 根據(jù)子控件類型描述生成子控件
/// - Parameter element: 子控件類型描述
/// - Returns: 子控件
func elementView(from element: ElementType) -> UIView
/// 在子控件添加到 stack view 之后吭净,繼續(xù)設(shè)置子控件的屬性
/// - Parameters:
/// - view: 子控件
/// - element: 子控件描述
func configureView(_ view: UIView, for element: ElementType)
}
extension ElementGenerator {
// 2
func addArrangedElements(_ elements: [ElementType], to stackView: UIStackView) -> Void {
for element in elements {
let subview = elementView(from: element)
stackView.addArrangedSubview(subview)
configureView(subview, for: element)
}
}
}
-
ElementGenerator
聲明了生成并添加子控件的流程,整個(gè)流程分為三個(gè)函數(shù)友扰。 -
ElementGenerator
的擴(kuò)展中定義了addArrangedElements
的默認(rèn)實(shí)現(xiàn)村怪。因?yàn)?addArrangedElements
函數(shù)沒有直接使用ElementType
的具體值甚负,且內(nèi)部調(diào)用了elementView(from:) -> UIView
和configureView(_:for:)
函數(shù)梭域,因此可以在協(xié)議中直接寫成默認(rèn)實(shí)現(xiàn)病涨。
觀察協(xié)議代碼赎懦,我們發(fā)現(xiàn) ElementGenerator
協(xié)議和 ElementType
仍然緊密耦合铲敛。在實(shí)際需求中,generator 和子控件類型通常沒有如此緊密的聯(lián)系工三,generator 生成其他類型的子控件也是合理的迁酸。比如,我們?cè)?demo 中實(shí)現(xiàn)了一個(gè)登錄界面俭正,下一個(gè)需求可能是實(shí)現(xiàn)一個(gè)信息流列表頁奸鬓,所使用的子控件類型會(huì)有很大的不同。
當(dāng)協(xié)議 ElementGenerator
和 ElementType
緊密相關(guān)時(shí)掸读,它們之間的耦合性較高,這意味著更改其中一個(gè)可能會(huì)影響到另一個(gè)儿惫。在實(shí)際開發(fā)中澡罚,generator 應(yīng)該具有更高的靈活性,能夠生成各種類型的子控件肾请,而不僅僅限于一種特定的 ElementType
留搔。為了降低耦合性,可以使用泛型來設(shè)計(jì) ElementGenerator
協(xié)議铛铁。這樣隔显,生成器可以指定生成任意類型的子控件。Swift 支持在協(xié)議中使用關(guān)聯(lián)類型(associated types)來實(shí)現(xiàn)類似泛型的功能[2]饵逐。
// FILE: ElementGenerator.swift
protocol ElementGenerator {
// 1
associatedtype EType
/// 向 stack view 添加子控件
/// - Parameters:
/// - elements: 子控件列表
/// - stackView: stack view
func addArrangedElements(_ elements: [EType], to stackView: UIStackView)
/// 根據(jù)子控件類型描述生成子控件
/// - Parameter element: 子控件類型描述
/// - Returns: 子控件
func elementView(from element: EType) -> UIView
/// 在子控件添加到 stack view 之后括眠,繼續(xù)設(shè)置子控件的屬性
/// - Parameters:
/// - view: 子控件
/// - element: 子控件描述
func configureView(_ view: UIView, for element: EType)
}
extension ElementGenerator {
func addArrangedElements(_ elements: [EType], to stackView: UIStackView) -> Void {
for element in elements {
let subview = elementView(from: element)
stackView.addArrangedSubview(subview)
configureView(subview, for: element)
}
}
}
- 通過引入關(guān)聯(lián)類型,可以定義一個(gè)可以使用任意類型元素
EType
的ElementGenerator
協(xié)議倍权。
此時(shí)只需對(duì) ConcreteElementGenerator
和 AnotherElementGenerator
稍加改造掷豺,使其遵守 ElementGenerator
協(xié)議,即可自動(dòng)獲得 addArrangedElements
函數(shù)。
// FILE: ConcreteElementGenerator.swift
struct ConcreteElementGenerator: ElementGenerator {
// 1
typealias EType = ElementType
// ....
//2
func elementView(from element: EType) -> UIView {
switch element {
// ....
}
}
}
- 設(shè)置類型別名萌业,用
EType
表示ElementType
坷襟。 - 實(shí)現(xiàn)協(xié)議方法時(shí)就可以直接使用
EType
,此時(shí)類型推斷系統(tǒng)會(huì)正確地把協(xié)議中的關(guān)聯(lián)類型EType
推斷為ElementType
生年。
通過這種方式婴程,ElementGenerator
協(xié)議就不再與任何特定的 ElementType
耦合,提高了代碼的可維護(hù)性和可擴(kuò)展性抱婉。進(jìn)而確保 ElementGenerator
協(xié)議的通用性和靈活性档叔,使其能夠適應(yīng)不同的開發(fā)需求,同時(shí)減少了代碼間的依賴蒸绩,便于后續(xù)的擴(kuò)展和維護(hù)衙四。
使用泛型
接下來考慮 UIStackView 的改造。前面章節(jié)已提到患亿,我們要讓 UIStackView 持有一個(gè) generator传蹈,并且為 UIStackView 添加一個(gè)函數(shù) addArrangedElements
。我們添加這個(gè)函數(shù)的目的是使得添加子控件更符合開發(fā)者的習(xí)慣步藕,因?yàn)橹苯油ㄟ^ view 添加子控件惦界,比通過 generator 完成相同功能,更易于開發(fā)者理解咙冗。
func addArrangedElements<E>(_ elements: [E]) -> Void where E == EType {
elementGenerator.addArrangedElements(elements, to: self)
}
其中:
-
EType
表示子控件類型沾歪,對(duì)應(yīng) demo 中的ElementType
。 -
E
表示函數(shù)入?yún)⒌念愋臀硐_@里給函數(shù)加了約束灾搏,入?yún)⒌淖涌丶愋停?code>E)必須與EType
相同。
接下來立润,我們需要考慮如何讓 UIStackView 持有一個(gè) generator狂窑。新建一個(gè) ElementStackView
繼承自 UIStackView
,并增加一個(gè) elementGenerator 屬性范删。
// FILE: StackViewExtention.swift
class ElementStackView<T: ElementGenerator>: UIStackView {
// 1
typealias EType = T.EType
// 2
let elementGenerator: T
// 3
init(elementGenerator: T) {
self.elementGenerator = elementGenerator
super.init(frame: CGRectZero)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addArrangedElements<E>(_ elements: [E]) -> Void where E == EType {
elementGenerator.addArrangedElements(elements, to: self)
}
}
在 View Controller 中使用如下方式調(diào)用 ElementStackView
:
// FILE: StackViewController.swift
class StackViewController: UIViewController {
lazy var stackView = {
// 4
let stackView = ElementStackView<ConcreteElementGenerator>(elementGenerator: ConcreteElementGenerator())
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.alignment = .fill
stackView.backgroundColor = .lightGray.withAlphaComponent(0.1)
return stackView
}()
// ....
func setupSubviews() {
let elementList = loginElementList()
// 5
stackView.addArrangedElements(elementList)
}
}
-
EType
表示子控件類型蕾域,對(duì)應(yīng) demo 中的ElementType
。 - 新增的 generator 屬性到旦,這里使用泛型指定 generator 的類型旨巷。
-
ElementStackView
的構(gòu)造器。 - 生成 stackView添忘。
- 調(diào)用
ElementStackView
的函數(shù)添加子控件采呐。
我們注意到,在創(chuàng)建 stackView
的代碼中搁骑,我們指定了 ConcreteElementGenerator
泛型類型并創(chuàng)建了一個(gè)此類型的結(jié)構(gòu)體斧吐,這種使用方式較為繁瑣又固。理想情況下,ConcreteElementGenerator
應(yīng)只出現(xiàn)一次煤率,既聲明了泛型類型仰冠,又能生成此類型的結(jié)構(gòu)體。這里有兩種實(shí)現(xiàn)方式:一種是將 2
處改為 lazy
屬性蝶糯,根據(jù)傳入的泛型類型創(chuàng)建 generator 結(jié)構(gòu)體洋只;另一種是在 3
處的構(gòu)造器上加入默認(rèn)參數(shù),創(chuàng)建一個(gè) generator 結(jié)構(gòu)體昼捍。無論選擇哪種方案识虚,我們都需要為 ElementGenerator
協(xié)議添加一個(gè)構(gòu)造器 [3]。
// FILE: ElementGenerator.swift
protocol ElementGenerator {
associatedtype EType
init()
/// 向 stack view 添加子控件
/// - Parameters:
/// - elements: 子控件列表
/// - stackView: stack view
func addArrangedElements(_ elements: [EType], to stackView: UIStackView)
/// 根據(jù)子控件類型描述生成子控件
/// - Parameter element: 子控件類型描述
/// - Returns: 子控件
func elementView(from element: EType) -> UIView
/// 在子控件添加到 stack view 之后妒茬,繼續(xù)設(shè)置子控件的屬性
/// - Parameters:
/// - view: 子控件
/// - element: 子控件描述
func configureView(_ view: UIView, for element: EType)
}
在 ElementStackView
中使用 lazy 屬性創(chuàng)建 elementGenerator
:
// FILE: StackViewExtention.swift
class ElementStackView<T: ElementGenerator>: UIStackView {
typealias EType = T.EType
lazy var elementGenerator: T = T()
func addArrangedElements<E>(_ elements: [E]) -> Void where E == EType {
elementGenerator.addArrangedElements(elements, to: self)
}
}
或給 ElementStackView
的構(gòu)造器加上默認(rèn)參數(shù):
class ElementStackView<T: ElementGenerator>: UIStackView {
typealias EType = T.EType
let elementGenerator: T
init(elementGenerator: T = T()) {
self.elementGenerator = elementGenerator
super.init(frame: CGRectZero)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addArrangedElements<E>(_ elements: [E]) -> Void where E == EType {
elementGenerator.addArrangedElements(elements, to: self)
}
}
通過對(duì)比很容易發(fā)現(xiàn)担锤,使用 lazy 屬性創(chuàng)建 elementGenerator
這種實(shí)現(xiàn)方式更加簡潔。因?yàn)樗苊饬顺跏蓟瘯r(shí)的復(fù)雜設(shè)置乍钻,將創(chuàng)建邏輯保持在屬性的訪問邏輯中肛循。同時(shí) lazy 屬性可以提高性能,因?yàn)樗鼈儍H在需要時(shí)才創(chuàng)建团赁,這避免了不必要的計(jì)算和內(nèi)存使用育拨。然而,開發(fā)者需要根據(jù)具體場景考慮是否需要立即初始化屬性欢摄,以及多線程環(huán)境下的線程安全問題。在那些不需要立即使用屬性或者初始化開銷較大的場景中笋粟,lazy 屬性是一個(gè)很好的選擇怀挠。
使用依賴注入(Dependency Injection)
在上一章節(jié)中,我們?yōu)?ElementGenerator
協(xié)議添加了一個(gè)構(gòu)造器害捕,以滿足 ElementStackView
中創(chuàng)建 generator 的需求绿淋。但在協(xié)議中聲明一個(gè)構(gòu)造器顯得有些突兀,我們希望找到一種方案尝盼,能移除協(xié)議中的構(gòu)造器吞滞,同時(shí)不影響 ElementStackView
使用 generator。
因此盾沫,我們將使用依賴注入(Dependency Injection)技術(shù)來繼續(xù)完善解決方案裁赠。依賴注入是一種設(shè)計(jì)模式,它允許將依賴(如服務(wù)或?qū)ο螅﹤鬟f給使用它們的組件赴精,而不是讓組件自己構(gòu)建依賴佩捞。這樣做的好處是可以增加組件的可測試性、可維護(hù)性和模塊化蕾哟。在 ElementGenerator
協(xié)議中聲明構(gòu)造器可能會(huì)導(dǎo)致實(shí)現(xiàn)該協(xié)議的類型必須實(shí)現(xiàn)特定的構(gòu)造器一忱,這限制了類型的靈活性莲蜘。通過使用依賴注入雇庙,我們可以移除協(xié)議中的構(gòu)造器聲明益咬,而是在需要使用 ElementGenerator
的地方(如 ElementStackView
)將其作為參數(shù)傳遞進(jìn)去赞草。
Resolver 是一個(gè)輕量級(jí)的依賴注入框架荆责,它為 Swift 應(yīng)用程序提供了服務(wù)定位和依賴注入的功能书释。使用 Resolver 可以幫助開發(fā)者管理對(duì)象的生命周期和依賴關(guān)系[4]:
// FILE: StackViewController.swift
import Resolver
extension Resolver: ResolverRegistering {
public static func registerAllServices() {
// 1
register {
ConcreteElementGenerator()
}.scope(.application)
}
}
// FILE: StackViewExtension.swift
class ElementStackView<T: ElementGenerator>: UIStackView {
typealias EType = T.EType
// 2
@LazyInjected var elementGenerator: T
func addArrangedElements<E>(_ elements: [E]) -> Void where E == EType {
elementGenerator.addArrangedElements(elements, to: self)
}
}
- 將
ConcreteElementGenerator
的實(shí)現(xiàn)類注冊(cè)到 Resolver 的容器中咒循。 - 通過 Resolver 來解析依賴的 generator
利用 Resolver 庫偿短,我們可以方便地將依賴注入到需要它們的對(duì)象中澄峰,而不必在協(xié)議中定義構(gòu)造器薯鼠。這種方法簡化了 ElementStackView 和 ElementGenerator 的實(shí)現(xiàn)择诈,使得它們之間的關(guān)系更加靈活和松耦合。通過 Resolver出皇,開發(fā)者可以更容易地管理和配置依賴關(guān)系羞芍,同時(shí)保持代碼的清晰和可維護(hù)性,同時(shí)也便于單元測試郊艘,因?yàn)榭梢院苋菀椎貫?ElementStackView 提供模擬的 ElementGenerator 實(shí)現(xiàn)荷科。使用依賴注入庫如 Resolver,可以讓我們的應(yīng)用架構(gòu)更加模塊化纱注,易于測試和擴(kuò)展畏浆。
總結(jié)
優(yōu)化基于UIStackView的界面布局方案
在本文中我們繼續(xù)對(duì)基于 UIStackView 的界面布局方案進(jìn)行優(yōu)化,引入了面向協(xié)議編程范式和依賴注入設(shè)計(jì)模式狞贱。
引入面向協(xié)議編程范式:面向協(xié)議編程(Protocol-Oriented Programming, POP)是 Swift 語言的核心范式之一刻获,它強(qiáng)調(diào)了在設(shè)計(jì)接口和交互時(shí)使用協(xié)議來定義藍(lán)圖,并通過擴(kuò)展來提供默認(rèn)實(shí)現(xiàn)瞎嬉。在布局方案中應(yīng)用POP可以使得各個(gè)組件的職責(zé)更加明確蝎毡,同時(shí)增加了代碼的復(fù)用性和可維護(hù)性。
應(yīng)用依賴注入設(shè)計(jì)模式:依賴注入(Dependency Injection, DI)是一種設(shè)計(jì)模式氧枣,用于減少代碼之間的耦合關(guān)系沐兵。通過這種方式,一個(gè)對(duì)象的依賴關(guān)系是由外部傳入便监,而不是由對(duì)象自己創(chuàng)建扎谎。在布局方案中,通過依賴注入可以動(dòng)態(tài)地向
UIStackView
中的ElementStackView
提供ElementGenerator
實(shí)例烧董,這樣做可以降低模塊間的直接依賴毁靶,提高模塊的可測試性和靈活性。移除冗余代碼:利用依賴注入和面向協(xié)議的范式解藻,我們能夠移除一些在
ElementStackView
和ElementGenerator
之間硬編碼的構(gòu)造器調(diào)用老充,從而減少冗余代碼。這種優(yōu)化使得代碼更加簡潔螟左,降低了出錯(cuò)的可能性啡浊,并且使得未來的維護(hù)和擴(kuò)展變得更加容易觅够。降低模塊間耦合:通過將
ElementGenerator
的創(chuàng)建和配置從ElementStackView
中分離出來,我們降低了這兩個(gè)模塊之間的耦合度巷嚣。使用 Resolver 庫作為依賴注入的工具喘先,進(jìn)一步抽象了對(duì)象創(chuàng)建的過程,使得ElementStackView
不再依賴于具體的ElementGenerator
實(shí)現(xiàn)廷粒,而是依賴于一個(gè)能夠產(chǎn)生ElementGenerator
的抽象窘拯。提升接口的易用性:在這個(gè)優(yōu)化過程中,
ElementStackView
的使用者不再需要關(guān)心如何創(chuàng)建ElementGenerator
坝茎,只需要關(guān)注如何使用它涤姊。通過簡化接口,使得其他開發(fā)者能夠更加容易地使用和集成ElementStackView
嗤放,無需深入了解其內(nèi)部實(shí)現(xiàn)細(xì)節(jié)思喊。
結(jié)論
- 通過引入面向協(xié)議編程范式和依賴注入設(shè)計(jì)模式,我們優(yōu)化了基于
UIStackView
的界面布局方案次酌。 - 這些改進(jìn)不僅減少了代碼的冗余恨课,還降低了模塊間的耦合,同時(shí)提高了整體代碼的易用性和可維護(hù)性岳服。
- 這種靈活的設(shè)計(jì)使得界面組件更加通用和可配置剂公,為后續(xù)的功能擴(kuò)展和維護(hù)奠定了良好的基礎(chǔ)。
衡量模塊“好用”性的判斷標(biāo)準(zhǔn)
在衡量一個(gè)模塊是否“好用”的過程中吊宋,我們提出了兩點(diǎn)判斷標(biāo)準(zhǔn)纲辽,即在完成兩個(gè)相同功能時(shí)模塊內(nèi)部是否存在代碼冗余,以及使用模塊對(duì)外公開的接口是否足夠方便贫母。
代碼冗余程度
- 代碼冗余是指在模塊內(nèi)部進(jìn)行功能開發(fā)時(shí)文兑,相同或相似的代碼被重復(fù)編寫的情況。這不僅會(huì)導(dǎo)致項(xiàng)目體積增大腺劣,還會(huì)增加維護(hù)成本和出錯(cuò)的風(fēng)險(xiǎn)。
- 一個(gè)“好用”的模塊應(yīng)該最大限度地減少代碼冗余因块。通過函數(shù)復(fù)用橘原、面向協(xié)議編程、設(shè)計(jì)模式等技術(shù)手段涡上,可以有效地避免冗余代碼的產(chǎn)生趾断。
接口的便利性
- 接口的便利性涉及到模塊對(duì)外提供的 API 是否簡潔明了,是否能夠讓使用者容易理解和使用吩愧,以及是否能夠方便地與其他模塊或系統(tǒng)集成芋酌。
- 一個(gè)“好用”的模塊應(yīng)該提供清晰、文檔化良好的公共接口雁佳,隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié)脐帝,減少使用者的學(xué)習(xí)成本同云,使得接口的使用直觀且容易。
結(jié)論
- 衡量模塊“好用”性的兩個(gè)重要指標(biāo)是內(nèi)部的代碼冗余程度和對(duì)外公開接口的便利性堵腹。
- 優(yōu)秀的模塊設(shè)計(jì)應(yīng)該力求在這兩個(gè)方面都做到最優(yōu)炸站,以提供高效、簡潔疚顷、易于維護(hù)和擴(kuò)展的代碼旱易,確保模塊的高可用性和良好的用戶體驗(yàn)。