参阅:Composition vs. Inheritance: code architecture solutions explained in Swift

组合和承继是在面向目标编程语言中作业时的根本编程技术。你很或许现已在你的代码中运用了这两种形式,虽然你或许并不知道它们是什么意思。

在过去的十年里,我经常在我的代码中运用承继。虽然运用承继往往效果很好,但也有不少比如,我期望运用不同的办法,因为代码变得难以保护和测验——这足以让我深入研究组合与承继的细节。

什么是承继?

承继意味着运用父类的默许办法和特点完成。你能够把承继描绘为 “对父类进行子类化”。一个子类能够覆写特定的特点或办法来改动默许行为。

最常见的比如是定义你的自定义 UIViewController

class BlogViewController: UIViewController {
    // Implementation details..
}

在这个比如中,BlogViewController 承继了 UIViewController 默许完成的所有功用。咱们能够经过在办法或特点前运用 override 关键字来掩盖默许的完成:

class BlogViewController: UIViewController {
    override var prefersStatusBarHidden: Bool {
        true
    }
    override func viewDidLoad() {
        // 重用默许完成
        super.viewDidLoad()
        // 增加自定义逻辑
        view.backgroundColor = .red
    }
}

你能够看到,咱们现在总是躲藏状态栏,并将视图的背景色彩设置为赤色。咱们确保经过调用 super 上的办法来重新运用父类 viewDidLoad 办法的默许完成。运用 super 拜访器,你能够拜访父类的非私有的默许完成。

无限承继的风险

由于承继具有传递性,一个类能够承继另一个类,而另一个类又承继另一个类,依此类推。

// Inherits from `BlogViewController` which inherits from `UIViewController`
class SwiftLeeBlogViewController: BlogViewController {
    // 当状态栏可见时,或许不简单找到原因。
    override var prefersStatusBarHidden: Bool {
        false
    }
}

理论上,你或许会在引擎盖下重复运用很多的默许完成,这或许不会马上被注意到。调试和测验代码或许会变得很困难,因为代码变得不那么显着,也不那么简单拜访。

Struct 类型不支持承继

由于承继是关于重用父类的默许完成,所以咱们不能运用结构体类型来完成承继。你能够把默许完成的协议一致性看作是对承继的一种替代,但这是别的一种技术。另一方面,结构体在组合上非常好用,并且只获益于与值类型的作业。

什么是组合?

组合归根结底是将多个部分结合起来,创造一个新的成果。你能够看到一个运用很多结构的应用程序是将结构组合在一起的成果。咱们能够将组合定义为一个实例经过运用另一个目标来供给其部分或全部功用

我最近最常运用的比如是运用组合式布局完成现代的调集视图。这个名字本身现已标明是苹果默许的 API 中的一个组合比如。看看下面的代码比如,咱们能够看到布局是由 itemsgroupssections 组合而成的。

let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
                                     heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                      heightDimension: .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
                                                 subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)

运用结构体和枚举进行组合的优点

与承继不同,咱们能够很简单地将组合与结构和枚举等值类型结合起来运用。正如我在 Swift中的结构与类 一文中解说的那样。值类型有很多优点,比如功能和内存安全。由于承继需求子类,咱们只能在与类的结合中运用它。

承继与组合,解说两者的区别

为了更好地描绘承继和组合之间的区别,运用一个代码示例会很好。

以下面这个标签装修器的比如为例,咱们期望有一个粗体赤色标签作为成果。假如咱们运用承继,代码看起来如下:

class RedLabelDecorator {
    func decorate(_ label: UILabel) {
        label.textColor = .red
    }
}
class BoldRedLabelDecorator: RedLabelDecorator {
    override func decorate(_ label: UILabel) {
        super.decorate(label)
        label.font = .boldSystemFont(ofSize: label.font.pointSize)
    }
}

在这种情况下,运用承继会带来一些弊端:

  • 咱们需求运用引用类型(reference type classes)
  • BoldRedLabelDecorator 承继自 RedLabelDecorator,所以它能够被传递到一个只期望有 RedLabelDecorator 的办法中,或许会对该办法形成副作用。
  • 在创立单一职责的装修器方面,有一种缺失的或许性,答应在其他当地重复运用它们。例如,一个大胆的装修器能够在多个当地重复运用。

为了更好地解说这些差异,咱们能够看一下用组合编写的同样的代码示例:

struct RedLabelDecorator {
    func decorate(_ label: UILabel) {
        label.textColor = .red
    }
}
struct BoldLabelDecorator {
    func decorate(_ label: UILabel) {
        label.font = .boldSystemFont(ofSize: label.font.pointSize)
    }
}
struct BoldRedLabelDecorator {
    func decorate(_ label: UILabel) {
        RedLabelDecorator().decorate(label)
        BoldLabelDecorator().decorate(label)
    }
}

成果将与承继的比如相同,但咱们运用的是组合,它有以下优点:

  • 结构体答应更好的功能和内存安全;
  • RedLabelDecoratorBoldLabelDecorator 都能够被其他装修器重用;
  • 每个装修器都能够被阻隔测验;

当然,咱们能够经过利用装修器协议来优化这段代码,比如说:

我能够组合运用承继和组合吗?

我相信不存在单一的解决计划。在我的项目中,我经常结合许多形式,如 MVVM,MVC,以及组合和承继。我一直致力于创造可测验的、可重用的、易于了解的代码。

有趣的是,虽然我写了五年的文章,开发了超越十年的应用程序,但在写代码计划时,组合依然不是我的首选计划。回忆我的项目中那些形成最多麻烦的代码比如,我能够说,组合会带来更好的可保护性代码。

总结

承继和组合在苹果的 SDK 中经常被运用,需求咱们用不同的思想方式去考虑。**组合的优点是运用值类型,往往能发生更好的可重复运用的代码。**虽然,承继有时或许是不可避免的,但你不应该以为这是一件坏事。你能够在一个项目中结合两种形式,人们应该挑选最适合问题的正确解决计划。

假如你喜欢学习更多关于 Swift 的技巧,请查看 Swift 调集页面。假如你有任何其他建议或反应,请随时联系我或在 Twitter 上推送我。

谢谢!