一、对 POP 的基础认知
1. 什么是面向协议编程?会取代面向对象编程吗?
-
面向协议编程(Protocol Oriented Programming),简称
POP
。是 Swift 的一种编程范式
,Apple 于 2015 年在 WWDC 提出,在 Swift 的标准库中能见到大量 POP 的影子。
-
同时,Swift 也是一门面向对象的编程语言(
Object Oriented Programming,简称 OOP
)。在 Swift 开发中,OOP 和 POP 是相辅相成的
,任何一方并不能取代另一方。
POP 的出现能弥补 OOP 一些设计上的不足。
2. 回顾 OOP 的三大特性是什么?并说一下继承的经典使用场合
?
- OOP 的三大特性:继承、封装、多态
-
继承的经典使用场合:当多个类(比如 A、B、C 类)具有很多
共性时
,可以将这些共性抽取
到一个父类
中(比如 D 类),最后 A、B、C类继承
D 类
3. 传统 OOP 面临的问题?以及它们的解决方案? (重要)
4. 如何解决上述 OOP 无法很好解决的问题?
- Swift 中可以
扩展
一个协议的具体实现
,很好的解决了上述问题。
5. POP 的注意点?(说两点即可)
- 优先考虑创建协议,而不是父类(基类)
- 优先考虑值类型(struct、enum),而不是引用类型(class)
- 巧用协议的扩展功能
- POP 和 OOP 是相辅相成的,不要强行 POP
二、优雅的前缀
1. 优雅前缀版本一:如何给系统类(String)扩展自定义(numberCount)方法?
extension String {
func numberCount() -> Int {
self.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
}
print("122abcde10294".numberCount()) // 输出:8
- 上面的办法虽然实现了
numberCount
方法扩展,但是如何保证不和系统的办法冲突呢?如果有一天 Apple 也给 String 添加了 numberCount方法且含义不同怎么办?
2. 优雅前缀版本二:不和系统冲突
- 从 OC 的经验来看,我们可以给
numberCount
添加前缀,这样就能保证不和别人冲突了。实现如下:
extension String {
var mj_numberCount: Int {
self.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
}
print("122abcde10294".mj_numberCount) // 输出:8
- 但是上面做法
不够 Swift 风格
,我们希望如下调用该方法
var str = "122abcde10294"
print(str.mj.numberCount)
- 具体实现如下:给
String
扩展一个 mj 计算属性
struct MJ {
var str = ""
init(_ str: String) {
self.str = str
}
var numberCount: Int {
str.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
}
extension String {
var mj: MJ {
MJ(self)
}
}
var str = "122abcde10294"
print(str.mj.numberCount) // 输出:8
- 上面代码依然存储问题,①如果我们需要给 Array 也扩展 mj 呢?②如果我们需要给 Array 也扩展 numberCount 方法呢?③如果我们需要给 String 扩展 characterCount 方法呢?
- 上面的代码就将造成我们无法很好解决上述问题,也无法有很好的扩展性
3. 优雅前缀版本三:扩展类方法、使用泛型
struct MJ<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
struct Person {
var age: Int
var name: String
}
//给 String 扩展
extension String {
var mj: MJ<String> {MJ(self)}
static var mj: MJ<String>.Type {MJ.self}
}
extension MJ where Base == String {
var numberCount: Int {
base.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
static func sayHello() { print("Hello String: ", Base.self)}
}
//给 Person 扩展
extension Person {
var mj: MJ<Person> {MJ(self)}
static var mj: MJ<Person>.Type {MJ.self}
}
extension MJ where Base == Person {
var numberCount: Int {
return base.age
}
static func sayHello() { print("Hello Person: ", Base.self)}
}
var str = "122abcde10294"
print(str.mj.numberCount) // 输出:8
String.mj.sayHello() // 输出:Hello String: String
var person = Person(age: 20, name: "carrot")
print(person.mj.numberCount) // 输出:20
Person.mj.sayHello() // 输出:Hello Person: Person
- 上述代码就解决了给不同 类型扩展 mj 的需求,也能很好的扩展各种方法。
- 但是依然存在问题①
var mj: MJ<Person> {MJ(self)}; static var mj: MJ<Person>.Type {MJ.self}
这部分代码是重复的代码,可有办法解决?②如果要扩展 mutating
修饰的方法可以吗?
4. 优雅前缀版本四: 使用协议(最终版)
// 定义基础类,也是扩展前缀
struct MJ<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
protocol MJCompatible {}
// 在这里扩充 MJCompatible 的方法实现
extension MJCompatible{
static var mj: MJ<Self>.Type {
get {MJ<Self>.self}
set {} // 加 set 适配 mutating 方法
}
var mj: MJ<Self> {
get {MJ(self)}
set {}
}
}
struct Person {
var age: Int
var name: String
}
//给 String 扩展
extension String: MJCompatible {}
extension MJ where Base == String {
var numberCount: Int {
base.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
static func sayHello() { print("Hello String: ", Base.self)}
}
//给 Person 扩展
extension Person: MJCompatible {}
extension MJ where Base == Person {
var numberCount: Int {
return base.age
}
static func sayHello() { print("Hello Person: ", Base.self)}
mutating func modifyAge(age: Int) {
base.age = age
print("enter modifyAge", age, base.age)
}
}
var str = "122abcde10294"
print(str.mj.numberCount) // 输出:8
String.mj.sayHello() // 输出:Hello String: String
var person = Person(age: 20, name: "carrot")
print(person.mj.numberCount) // 输出:20
person.mj.modifyAge(age: 10)
// (下面没有输出 10,因为MJ(self)值传递,如果把 Person 从 struct 改成 class,就会输出 10)
print(person.age) // 输出:20
Person.mj.sayHello() // 输出:Hello Person: Person
5. 利用协议实现类型判断(了解)