UIStackView 的另類玩法(二)

前文中荡灾,我們?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)
    }
}
  1. 新增一個(gè) UIStackView 及其使用的 generator
  2. 添加 UIStackView 到界面
  3. 為新增的 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)
        }
    }
}
  1. ElementGenerator 聲明了生成并添加子控件的流程,整個(gè)流程分為三個(gè)函數(shù)友扰。
  2. ElementGenerator 的擴(kuò)展中定義了 addArrangedElements 的默認(rèn)實(shí)現(xiàn)村怪。因?yàn)?addArrangedElements 函數(shù)沒有直接使用 ElementType 的具體值甚负,且內(nèi)部調(diào)用了 elementView(from:) -> UIViewconfigureView(_: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é)議 ElementGeneratorElementType 緊密相關(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)
        }
    }
}
  1. 通過引入關(guān)聯(lián)類型,可以定義一個(gè)可以使用任意類型元素 ETypeElementGenerator 協(xié)議倍权。

此時(shí)只需對(duì) ConcreteElementGeneratorAnotherElementGenerator 稍加改造掷豺,使其遵守 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 {
            // ....
        }
    }
}
  1. 設(shè)置類型別名萌业,用 EType 表示 ElementType坷襟。
  2. 實(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)
    }

}
  1. EType 表示子控件類型蕾域,對(duì)應(yīng) demo 中的 ElementType
  2. 新增的 generator 屬性到旦,這里使用泛型指定 generator 的類型旨巷。
  3. ElementStackView 的構(gòu)造器。
  4. 生成 stackView添忘。
  5. 調(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)
    }
}
  1. ConcreteElementGenerator的實(shí)現(xiàn)類注冊(cè)到 Resolver 的容器中咒循。
  2. 通過 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é)議的范式解藻,我們能夠移除一些在ElementStackViewElementGenerator之間硬編碼的構(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)。

  1. https://gist.github.com/ltryee/b21a41c0c62a8dacfb1dc8f5a03ff31e ?

  2. https://gist.github.com/ltryee/333d41118a5c3450b2bdf8739eef14b4 ?

  3. https://gist.github.com/ltryee/08a22ed5f8be4635ea112c52ba6ca200 ?

  4. https://gist.github.com/ltryee/932f9bb617e059155911a637acfe4ce0 ?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腿堤,一起剝皮案震驚了整個(gè)濱河市阀坏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笆檀,老刑警劉巖忌堂,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異误债,居然都是意外死亡浸船,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門寝蹈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來李命,“玉大人,你說我怎么就攤上這事箫老》庾郑” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵耍鬓,是天一觀的道長阔籽。 經(jīng)常有香客問我,道長牲蜀,這世上最難降的妖魔是什么笆制? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮涣达,結(jié)果婚禮上在辆,老公的妹妹穿的比我還像新娘。我一直安慰自己度苔,他們只是感情好匆篓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寇窑,像睡著了一般鸦概。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上甩骏,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天窗市,我揣著相機(jī)與錄音先慷,去河邊找鬼。 笑死谨设,一個(gè)胖子當(dāng)著我的面吹牛熟掂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扎拣,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赴肚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了二蓝?” 一聲冷哼從身側(cè)響起誉券,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刊愚,沒想到半個(gè)月后踊跟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸥诽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年商玫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牡借。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拳昌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钠龙,到底是詐尸還是另有隱情炬藤,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布碴里,位于F島的核電站沈矿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏咬腋。R本人自食惡果不足惜羹膳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望根竿。 院中可真熱鬧溜徙,春花似錦、人聲如沸犀填。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽九巡。三九已至,卻和暖如春蹂季,著一層夾襖步出監(jiān)牢的瞬間冕广,已是汗流浹背疏日。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撒汉,地道東北人沟优。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像睬辐,于是被迫代替她去往敵國和親挠阁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容