美文网首页
面向对象与面向协议

面向对象与面向协议

作者: 伽蓝香 | 来源:发表于2017-03-13 09:38 被阅读71次

选自猫神的文章 https://onevcat.com/2016/11/pop-cocoa-1/

Swift协议初识

Protocol

Swift 标准库中有 50 多个复杂不一的协议,几乎所有的实际类型都是满足若干协议的。protocol 是 Swift 语言的底座,语言的其他部分正是在这个底座上组织和建立起来的。

所谓协议,就是一组属性和/或方法的定义,而如果某个具体类型想要遵守一个协议,那它需要实现这个协议所定义的所有这些内容。协议实际上做的事情不过是“关于实现的约定”。

面向对象

class Animal {
    var leg: Int { return 2 }
    func eat() {
        print("eat food.")
    }
    func run() {
        print("run with \(leg) legs")
    }
}

class Tiger: Animal {
    override var leg: Int { return 4 }
    override func eat() {
        print("eat meat.")
    }
}

let tiger = Tiger()
tiger.eat() // "eat meat"
tiger.run() // "run with 4 legs"

我们看到 TigerAnimal 共享了一部分代码,这部分代码被封装到了父类中,而除了 Tiger的其他的子类也能够使用 Animal 的这些代码。这其实就是 OOP 的核心思想 - 使用封装和继承,将一系列相关的内容放到一起。

这套理念有一些缺陷。虽然我们努力用这套抽象和继承的方法进行建模,但是实际的事物往往是一系列特质的组合,而不单单是以一脉相承并逐渐扩展的方式构建的。

下面有几个解决方案:

  • Copy & Paste

    这是一个比较糟糕的解决方案,但是演讲现场还是有不少朋友选择了这个方案,特别是在工期很紧,无暇优化的情况下。这诚然可以理解,但是这也是坏代码的开头。我们应该尽量避免这种做法。

  • 引入 BaseViewController

    在一个继承自 UIViewControllerBaseViewController 上添加需要共享的代码,或者干脆在 UIViewController 上添加 extension。看起来这是一个稍微靠谱的做法,但是如果不断这么做,会让所谓的 Base 很快变成垃圾堆。职责不明确,任何东西都能扔进 Base,你完全不知道哪些类走了 Base,而这个“超级类”对代码的影响也会不可预估。

  • 依赖注入

    通过外界传入一个带有 myMethod 的对象,用新的类型来提供这个功能。这是一个稍好的方式,但是引入额外的依赖关系,可能也是我们不太愿意看到的。

  • 多继承

    当然,Swift 是不支持多继承的。不过如果有多继承的话,我们确实可以从多个父类进行继承,并将 myMethod 添加到合适的地方。有一些语言选择了支持多继承 (比如 C++),但是它会带来 OOP 中另一个著名的问题:菱形缺陷。

    菱形缺陷

    上面的例子中,如果我们有多继承,那么 ViewControllerAnotherViewController 的关系可能会是这样的:

    imgimg

    在上面这种拓扑结构中,我们只需要在 ViewController 中实现 myMethod,在 AnotherViewController 中也就可以继承并使用它了。看起来很完美,我们避免了重复。但是多继承有一个无法回避的问题,就是两个父类都实现了同样的方法时,子类该怎么办?我们很难确定应该继承哪一个父类的方法。因为多继承的拓扑结构是一个菱形,所以这个问题又被叫做菱形缺陷 (Diamond Problem)。像是 C++ 这样的语言选择粗暴地将菱形缺陷的问题交给程序员处理,这无疑非常复杂,并且增加了人为错误的可能性。而绝大多数现代语言对多继承这个特性选择避而远之。

动态派发安全性

ViewController *v1 = ...
[v1 myMethod];

AnotherViewController *v2 = ...
[v2 myMethod];

NSObject *v3 = [NSObject new]
// v3 没有实现 `myMethod`

NSArray *array = @[v1, v2, v3];
for (id obj in array) {
    [obj myMethod];
}

// Runtime error:
// unrecognized selector sent to instance blabla

编译依然可以通过,但是显然,程序将在运行时崩溃。Objective-C 是不安全的,编译器默认你知道某个方法确实有实现,这是消息发送的灵活性所必须付出的代价。而在 app 开发看来,用可能的崩溃来换取灵活性,显然这个代价太大了。虽然这不是 OOP 范式的问题,但它确实在 Objective-C 时代给我们带来了切肤之痛。

OOP的三大困境

  • 动态派发安全
  • 横切关注点
  • 菱形缺陷

首先,在 OC 中动态派发让我们承担了在运行时才发现错误的风险,这很有可能是发生在上线产品中的错误。其次,横切关注点让我们难以对对象进行完美的建模,代码的重用也会更加糟糕。

协议拓展和面向协议编程

  • [x] 动态派发安全

    ```swift
    protocol Greetable {
        var name: String { get }
        func greet()
    }
    struct Person: Greetable {
        let name: String
        func greet() {
            print("你好 \(name)")
        }
    }
    struct Cat: Greetable {
        let name: String
        func greet() {
            print("meow~ \(name)")
        }
    }
    //以协议作为类型
    //实现动态派发
    let array: [Greetable] = [
          Person(name: "Wei Wang"), 
          Cat(name: "onevcat")]
    for obj in array {
      obj.greet()
    }
    //❌
    struct Bug: Greetable {
        let name: String
    }
    
    // Compiler Error: 
    // 'Bug' does not conform to protocol 'Greetable'
    // protocol requires function 'greet()'
    ```
    
  • [ ] 横切关注点

    配合协议拓展实现
    
    > 协议拓展 Swift2 实现 WWDC2015提出
    
    ```swift
    protocol P {
        func myMethod()
    }
    //拓展协议 P
    extension P 
      //提供默认实现
        func myMethod() {
            doWork()
        }
    }
    extension ViewController: P { }
    extension AnotherViewController: P { }
    
    viewController.myMethod()
    anotherViewController.myMethod()
    ```
    
    总结:
    
    - 协议定义
      - 提供实现的入口
      - 遵循协议的类型需要对其进行实现
    - 协议扩展
      - 为入口提供默认实现
      - 根据入口提供额外实现
    
  • [ ] 菱形缺陷

    ```swift
    protocol Nameable {
        var name: String { get }
    }
    
    protocol Identifiable {
        var name: String { get }
        var id: Int { get }
    }
    struct Person: Nameable, Identifiable {
        let name: String 
        let id: Int
    }
    
    // `name` 属性同时满足 Nameable 和 Identifiable 的 name
    ```
    
    如果对协议添加了默认实现
    
    ```swift
    extension Nameable {
        var name: String { return "default name" }
    }
    
    struct Person: Nameable, Identifiable {
      //name 使用了 Nameable 的 name 属性
        //let name: String 
        let id: Int
    }
    ```
    
    这样的编译是可以通过的,虽然 `Person` 中没有定义 `name`,但是通过 `Nameable` 的 `name` (因为它是静态派发的),`Person` 依然可以遵守 `Identifiable`。不过,当 `Nameable` 和 `Identifiable` 都有 `name` 的协议扩展的话,就无法编译了:
    
    ```swift
    extension Nameable {
        var name: String { return "default name" }
    }
    
    extension Identifiable {
        var name: String { return "another default name" }
    }
    
    struct Person: Nameable, Identifiable {
        // let name: String 
        let id: Int
    }
    
    // 无法编译,name 属性冲突
    ```
    
    这种情况下,`Person` 无法确定要使用哪个协议扩展中 `name` 的定义。在同时实现两个含有同名元素的协议,**并且**它们都提供了默认扩展时,我们需要在**具体的类型中明确地提供实现**。这里我们将 `Person` 中的 `name` 进行实现就可以了:
    
    ```swift
    struct Person: Nameable, Identifiable {
        let name: String 
        let id: Int
    }
    
    Person(name: "onevcat", id: 123).name // onevcat
    ```
    
    这里的行为看起来和菱形问题很像,但是有一些本质不同。首先,这个问题出现的前提条件是同名元素**以及**同时提供了实现,而协议扩展对于协议本身来说并不是必须的。其次,我们在具体类型中提供的实现一定是安全和确定的。当然,菱形缺陷没有被完全解决,**Swift 还不能很好地处理多个协议的冲突**,这是 Swift 现在的不足。

相关文章

  • 面向对象与面向协议

    选自猫神的文章 https://onevcat.com/2016/11/pop-cocoa-1/ Swift协议初...

  • 面向协议编程思想

    面向协议编程思想1、开店的例子——面向过程(过程),面向对象(哪些对象)2、面向协议编程考虑的重点是协议,一般思路...

  • 面向对象与面向协议编程

    面向对象(Object Oriented Programming,简称 OOP)面向协议(Protocol Ori...

  • Swift 中的面向协议编程

    Swift 中面向协议编程的应用,与面向对象不同的编程模式。在 Swift 这个现代化的编程语言中,感受面向协议带...

  • Java学习day-07:面向对象

    一、面向过程和面向对象 1.面向对象与面向过程的区别: 面向对象具有三大特征;封装,继承,多态;面向对象与面向过程...

  • Swift--协议

    协议概念 协议定义和遵从 协议方法 协议属性 面向协议编程 协议概念 几何图形这种类在面向对象分析与设计方法学中称...

  • Swift和OC的区别

    一、编程范式 Swift可以面向协议编程、面向函数编程、面向对象编程。 OC主要是面向对象编程。 二、类型安全 S...

  • swift注意事项

    一、编程范式 Swift 可以面向协议编程(POP)、函数式编程、面向对象编程。 Object-C 以面向对象编程...

  • Swift Day 21 面向协议编程 POP (重要)

    一、对 POP 的基础认知 1. 什么是面向协议编程?会取代面向对象编程吗? 面向协议编程(Protocol Or...

  • swift面向协议编程(一)翻译

    第一章.面向对象与面向协议编程 本书是关于面向协议编程。当苹果2015年的开发者大会上发布了Swift2,他们也宣...

网友评论

      本文标题:面向对象与面向协议

      本文链接:https://www.haomeiwen.com/subject/dflfnttx.html