Protocol-Oriented Programming

290 阅读2分钟

官方文档Protocol-Oriented Programming in Swift

Swift设计的核心是两个非常强大的理念:面向协议的编程和一流的价值语义。这些概念中的每一个都有益于可预测性、性能和生产力,它们一起可以改变我们对编程的思考方式。了解如何应用这些思想来改进编写的代码。

承前启后

class的弊端

  • Implicit Sharing
    • 重点隐士共享的坏处

  • Inheritance All Up In Your Business

    • 重点单向继承,继承负担,不能反向建模

  • Lost Type Relationships

    • 图示圈出的代码部分类型推断差,不优雅.(后续与协议方式作比较)

更好的抽象机制

进化

面向协议

协议基础示例

基本协议

protocol Ordered {
    func precedes(other: Self) -> Bool
}
struct Number : Ordered {
    var value: Double = 0
    func precedes(other: Number) -> Bool {
        return self.value < other.value
    }
}

泛型约束

func binarySearch<T: Ordered>(sortedKeys: [T], forKey k: T) -> Int {
    var lo = 0
    var hi = sortedKeys.count
    while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(other: k) {
            lo = mid + 1
        } else {
            hi = mid
        }
    }
    return lo
}

好处:后面为协议

绘图

图形统一遵循Drawable协议, Drawable协议绘图方法内包含一个渲染器,此渲染器完成绘制工作.

图形Drawable协议

protocol Drawable {
  func draw(renderer: Renderer)
}

struct Circle : Drawable {
    var center: CGPoint
    var radius: CGFloat
    
    func draw(renderer: Renderer) {
        renderer.arcAt(center: center, radius: radius, startAngle: 0.0, endAngle: CGFloat.pi * 2)
    }
}

struct Polygon : Drawable {
    var corners: [CGPoint] = []
    func draw(renderer: Renderer) {
        renderer.moveTo(p: corners.last!)
        for p in corners {
            renderer.lineTo(p: p)
        }
    }
}
struct Diagram : Drawable {
    var elements: [Drawable] = []
    func draw(renderer: Renderer) {
        for f in elements {
            f.draw(renderer: renderer)
        }
    }
}

Renderer协议

  • extension CGContext :Renderer, 扩展CGContext继承Renderer协议, 于是CGContext完成绘制工作.
protocol Renderer {
    func moveTo(p: CGPoint)
    func lineTo(p: CGPoint)
    func circleAt(center: CGPoint, radius: CGFloat)
    func arcAt(
        center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)
}
extension Renderer {
    func circleAt(center: CGPoint, radius: CGFloat){
        arcAt(center: center, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2)
    }
}

extension CGContext :Renderer {
    func moveTo(p: CGPoint) {
        move(to: p)
    }
    func lineTo(p: CGPoint) {
        addLine(to: p)
    }
    func arcAt(center: CGPoint, radius: CGFloat,
               startAngle: CGFloat, endAngle: CGFloat) {
        addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
    }
}

使用

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        guard let contex = UIGraphicsGetCurrentContext() else {
            return
        }
        let circle = Circle(center: CGPoint(x: 187.5, y: 333.5), radius: 93.75)
        
        let triangle = Polygon(corners: [ CGPoint(x: 187.5, y: 427.25), CGPoint(x: 268.69, y: 286.625), CGPoint(x: 106.31, y: 286.625)])
        
        let diagram = Diagram(elements: [circle, triangle])
        contex.setLineWidth(2)
        contex.setStrokeColor(UIColor.orange.cgColor)
        diagram.draw(renderer: contex)
        contex.closePath()
        contex.strokePath()
    }

协议扩展技巧

Scenes from the standard library and beyond

约束限制1

extension Collection where Iterator.Element: Equatable {
    public func indexOf(element: Iterator.Element) -> Index? {
        for i in self.indices {
            if self[i] == element {
                return i
            }
        }
        return nil
    }
}

约束限制2

protocol Ordered {
   func precedes(other: Self) -> Bool
}
extension Ordered where Self : Comparable {
  func precedes(other: Self) -> Bool { 
     return self < other 
  }
}

泛型美化

美化前

func binarySearch<T: Ordered>(sortedKeys: [T], forKey k: T) -> Int {
    var lo = 0
    var hi = sortedKeys.count
    while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(other: k) {
            lo = mid + 1
        } else {
            hi = mid
        }
    }
    return lo
}

//代码已不适用现在使用

func binarySearch<
    C : Collection>(sortedKeys: C, forKey k: C.Iterator.Element) -> Int where C.Index == RandomAccessIndexType, C.Iterator.Element : Ordered
{
    var lo = 0
    var hi = sortedKeys.count
    while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(other: k) {
            lo = mid + 1
        } else {
            hi = mid
        }
    }
    return lo
}