接着来学习后半部分
本章的主题有:
可以定义属性和方法,如果是值类型遵守协议,方法涉及到修改自身属性记得要加 mutating
关键字
protocol FullName { var firstName: String {get set} var lastName: String {get set} static var typeProperty: String {get} mutating func changeName() }
我们用 @objc
关键字修饰协议和相关的可选属性和方法
@objc protocol Phone { var phoneNumber: String {get set} optional var emailAddress: String {get set} func dialNumber() optional func getEmail() }
注意,只有类 Class 可以遵守用 @objc
修饰了的协议
协议不但能够继承,还能够多继承,这是类继承所做不到的,注意实现的时候需要实现所有的协议方法。
protocol ProtocolThree: ProtocolOne, ProtocolTwo { // Add requirements here }
协议组合更像是将单一功能的模块定义为单个协议,然后需要什么功能就部署什么功能,毕竟协议支持多部署(可以组合起来使用)
struct MyStruct: ProtocolOne, ProtocolTwo, Protocolthree { // implementation here }
我们可以将协议作为函数参数和返回值使用
protocol PersonProtocol { var firstName: String {get set} var lastName: String {get set} var birthDate: NSDate {get set} var profession: String {get} init (firstName: String, lastName: String, birthDate: NSDate) } func updatePerson(person: PersonProtocol) -> PersonProtocol { var newPerson: PersonProtocol // Code to update person goes here return newPerson }
还可以在集合中当做类型使用:
var personArray = [PersonProtocol]() var personDict = [String: PersonProtocol]()
但是协议不能实例化,我们只能使用遵循协议的具体类型来实例化一个对象
var myPerson: PersonProtocol myPerson = SwiftProgrammer(firstName: "Jon", lastName: "Hoffman", birthDate: bDateProgrammer) myPerson = FootballPlayer(firstName: "Dan", lastName: "Marino", birthDate: bDatePlayer)
Swift 并不关心具体类型是什么(类、结构体、枚举),只关心是否遵守了 PersonProtocol
协议。
多态可以让我们使用单独的一个接口与多种类型进行交互,这个单独统一的接口通常来自于父类或协议中的定义。
在下面的例子中 SwiftProgrammer
和 FootballPlayer
都遵循了 PersonProtocol
协议,所以可以用 people 实例(遵守 PersonProtocol
协议的数组)来添加 append
不同的类型,即所谓单一接口处理多种类型的能力。
var programmer = SwiftProgrammer(firstName: "Jon", lastName: "Hoffman", birthDate: bDateProgrammer) var player = FootballPlayer(firstName: "Dan", lastName: "Marino", birthDate: bDatePlayer) var people: [PersonProtocol] = [] people.append(programmer) people.append(player)
Swift 中自省用 is
判断, as
进行转换,这里注意 is 可以在 switch 中使用
for person in people { switch (person) { case is SwiftProgrammer: print("/(person.firstName) is a Swift Programmer") case is FootballPlayer: print("/(person.firstName) is a Football Player") default: print("/(person.firstName) is an unknown type") } }
可以配合 where 做过滤
for person in people where person is SwiftProgrammer { print("/(person.firstName) is a Swift Programmer") }
associated type
实际提供了一个占位符,最终实际类型要等到部署时才确定
protocol QueueProtocol { associatedtype QueueType mutating func addItem(item: QueueType) mutating func getItem() -> QueueType? func count() -> Int }
最终实现协议时要指明白 QueueType
的类型,我们也可以用泛型在 runtime 时再确定具体的类型:
class GenericQueue<T>: QueueProtocol { var items = [T]() func addItem(item: T) { items.append(item) } func getItem() -> T? { if items.count > 0 { return items.removeAtIndex(0) } else { return nil } } func count() -> Int { return items.count } }
代理模式与 OC 中的代理实现起来是一样的,就不啰嗦了。
我们可以扩展结构体、类、枚举对象以及协议,扩展里可以添加的类型有以下几种:
扩展是用来增加功能,而不是改写其原有功能的。还有不能添加存储属性。扩展一个 String
extension String { func getFirstChar() -> Character? { guard self.characters.count > 0 else { return nil } return self[startIndex] } subscript (r: Range<Int>) -> String { get { return substringWithRange(Range( start: startIndex.advancedBy(r.startIndex), end: startIndex.advancedBy(r.endIndex))) } } }
可以为一些协议添加默认的实现,需要确保我们添加的实现满足所有遵循协议的类型。比如我们可以限制扩展方法生效的范围:必须满足 ArrayLiteralConvertible
,就把字典对象排除掉了
extension CollectionType where Self: ArrayLiteralConvertible { //Extension code here }
《Design Patterns: Elements of Reusable Object-Oriented Software》 这本书提到 23 种设计模式,可以分为三类:创造,结构和行为。
我们需要注意设计模式并不是银弹,要去理解背后所要解决的问题。
主要处理对象的创建,其背后有两个核心思想:一是封装具体类型信息,二是隐藏实例的创建细节,一共有五种广为人知的创建设计模式,具体介绍三种
适用于需要集中在一个地方管理内外部资源,维护一个状态,比如保持蓝牙的连接,每个页面都能获取到单例,而不用去重新创建并维护。
单例的生命周期通常和应用是相同的
由于单例的特性,它只能由引用类型(类)实现
class MySingleton { static let sharedInstance = MySingleton() var number = 0 private init() {} }
上面的例子不需要实例化这个类就能使用静态常量,而且通过 private
限制了其他代码创建这个类的实例
不使用设计模式的结构体初始化函数很复杂,调用时需要写很多参数:
struct BurgerOld { var name: String var patties: Int var bacon: Bool var cheese: Bool var pickles: Bool var ketchup: Bool var mustard: Bool var lettuce: Bool var tomato: Bool } // Create Hamburger var burgerOld = BurgerOld(name: "Hamburger", patties: 1, bacon: false, cheese: false, pickles: false, ketchup: false, mustard: false, lettuce: false, tomato: false) // Create Cheeseburger var burgerOld = BurgerOld(name: "Cheeseburger", patties: 1, bacon: false, cheese: false, pickles: false, ketchup: false, mustard: false, lettuce: false, tomato: false)
我们通过定义一个协议来封装这些初始化需要的参数
protocol BurgerBuilder { var name: String {get} var patties: Int {get} var bacon: Bool {get} var cheese: Bool {get} var pickles: Bool {get} var ketchup: Bool {get} var mustard: Bool {get} var lettuce: Bool {get} var tomato: Bool {get} }
改写初始化方法:
struct Burger { var name: String var patties: Int var bacon: Bool var cheese: Bool var pickles: Bool var ketchup: Bool var mustard: Bool var lettuce: Bool var tomato: Bool init(builder: BurgerBuilder) { self.name = builder.name self.patties = builder.patties self.bacon = builder.bacon self.cheese = builder.cheese self.pickles = builder.pickles self.ketchup = builder.ketchup self.mustard = builder.mustard self.lettuce = builder.lettuce self.tomato = builder.tomato }
早先初始化需要 9 个参数,现在只需要一个遵守 BurgerBuilder
协议的对象就好了,我们可以创建这个对象,然后在 Burger
初始化时当做参数传递进去
struct CheeseBurgerBuilder: BurgerBuilder { let name = "CheeseBurger" let patties = 1 let bacon = false let cheese = true let pickles = true let ketchup = true let mustard = true let lettuce = false let tomato = false } // Create Cheeseburger with tomatos var myCheeseBurgerBuilder = CheeseBurgerBuilder() var myCheeseBurger = Burger(builder: myCheeseBurgerBuilder)
除了使用协议,创建多个 builder types,我们还可以不使用协议,只用一个 builder type 来实现。首先创建一个 builder type,然后为所有的属性设置默认值,然后一一添加 set
方法,方便随后进行配置修改:
struct BurgerBuilder { var name = "Burger" var patties = 1 var bacon = false var cheese = false var pickles = true var ketchup = true var mustard = true var lettuce = false var tomato = false mutating func setPatties(choice: Int) {self.patties = choice} mutating func setBacon(choice: Bool) {self.bacon = choice} mutating func setCheese(choice: Bool) {self.cheese = choice} mutating func setPickles(choice: Bool) {self.pickles = choice} mutating func setKetchup(choice: Bool) {self.ketchup = choice} mutating func setMustard(choice: Bool) {self.mustard = choice} mutating func setLettuce(choice: Bool) {self.lettuce = choice} mutating func setTomato(choice: Bool) {self.tomato = choice} func buildBurgerOld(name: String) -> BurgerOld { return BurgerOld(name: name, patties: self.patties, bacon: self.bacon, cheese: self.cheese, pickles: self.pickles, ketchup: self.ketchup, mustard: self.mustard, lettuce: self.lettuce, tomato: self.tomato) } }
使用起来也就很方便,随时可以修改相关属性
var burgerBuilder = BurgerBuilder() burgerBuilder.setCheese(true) burgerBuilder.setBacon(true) var jonBurger = burgerBuilder.buildBurgerOld("Jon's Burger")
如果你自定义一个类型的初始化方法太繁琐,可以考虑 builder pattern 模式
工厂方法模式是指在实例化对象时不指定具体类型,而在等到 runtime 阶段才指定。这种模式只暴露接口或基本的类给调用者,并不提供具体的细节。比如很多类型都遵守同一个协议,我们需要在运行时选择一个合适的类型。
protocol Person { var name: String {get} var age: Int {get} } class Man: Person { static let sharedInstance = Man() private init(){} let name = "way" let age = 18 } class Woman: Person { static let sharedInstance = Woman() private init(){} let name = "TuanTuan" let age = 17 }
我们可以使用一个工厂方法来返回一个遵守 Person
协议的对象,具体是 Man
还是 Woman
对象,我们可以通过工厂方法的参数进行控制:
func getPeople(isMan: Bool) -> Person { if isMan { retrun Man.sharedInstance } else { return Woman.sharedInstance } }
我们只需要指定相关参数即可,具体的初始化细节交给工厂方法去做
结构型模式主要有以下七种,还是详细介绍其中三种
桥接模式顾名思义,主要用来解耦,做中间的抽象层。如果有这么两个协议,一个用来存储和准备消息 MessageProtocol
,另一个用来发送消息 SenderProtocol
,后者会在发送消息前进行验证
protocol MessageProtocol { var messageString: String {get set} init(messageString: String) func prepareMessage() } protocol SenderProtocol { var message: MessageProtocol? {get set} func sendMessage(message: MessageProtocol) func verifyMessage() } class PlainTextMessage: MessageProtocol { var messageString: String required init(messageString: String) { self.messageString = messageString } func prepareMessage() { // Nothing to do } } class AESEncryptedMessage: MessageProtocol { var messageString: String required init(messageString: String) { self.messageString = messageString } func prepareMessage() { self.messageString = "AES: " + self.messageString } } class EmailSender: SenderProtocol { var message: MessageProtocol? func sendMessage() { print("Sending through E-Mail:") print(" /(message!.messageString)") } func verifyMessage() { print("Verifying E-Mail message") } } class SMSSender: SenderProtocol { var message: MessageProtocol? func sendMessage() { print("Sending through SMS:") print(" /(message!.messageString)") } func verifyMessage() { print("Verifying SMS message") } }
观察到上面的协议 SenderProtocol
中包含了 MessageProtocol
,如果要手动调用的话需要注意调用的顺序
var message = PlainTextMessage(messageString: "Plain Text Message") message.prepareMessage() var sender = SMSSender() sender.message = message sender.verifyMessage() sender.sendMessage()
我们可以把内部的交互逻辑封装起来统一放到桥接类型中
struct MessageingBridge { static func sendMessage(message: MessageProtocol, var sender: SenderProtocol) { message.prepareMessage() sender.message = message sender.verifyMessage() sender.sendMessage() } }
如此以来调用就变得很简单
var myMessage = PlainTextMessage(messageString: "Plain Text Message") var myEncMessage = AESEncryptedMessage(messageString: "Encrypted Message") MessageingBridge.sendMessage(myMessage, sender: SMSSender()) MessageingBridge.sendMessage(myMessage, sender: EmailSender()) MessageingBridge.sendMessage(myEncMessage, sender: EmailSender())
如果你有一组 API 要紧密地结合起来完成一项工作,可以考虑使用外观模式,它隐藏了与这些 APIs 进行交互的复杂性,并对外提供了一个简单的接口。
举例有三个 APIs: HotelBooking
, FlightBooking
, RentalCarBooks
(订酒店、订机票、租车)我们创建一个 TravelFacade
结构来集中封装这些 API 的调用。首先构造数据模型和模拟操作
struct Hotel { //Information about hotel room } struct HotelBooking { static func getHotelNameForDates(to: NSDate, from: NSDate) -> [Hotel]? { let hotels = [Hotel]() //logic to get hotels return hotels } static func bookHotel(hotel: Hotel) { // logic to reserve hotel room } } struct Flight { //Information about flights } struct FlightBooking { static func getFlightNameForDates(to: NSDate, from: NSDate) -> [Flight]? { let flights = [Flight]() //logic to get flights return flights } static func bookFlight(fight: Flight) { // logic to reserve flight } } struct RentalCar { //Information about rental cars } struct RentalCarBooking { static func getRentalCarNameForDates(to: NSDate, from: NSDate) -> [RentalCar]? { let cars = [RentalCar]() //logic to get flights return cars } static func bookRentalCar(rentalCar: RentalCar) { // logic to reserve rental car } }
接着用 TravelFacade 来封装这些 APIs,今后只需通过 TravelFacade 就可以实现订酒店、订机票、租车的操作
class TravelFacade { var hotels: [Hotel]? var flights: [Flight]? var cars: [RentalCar]? init(to: NSDate, from: NSDate) { hotels = HotelBooking.getHotelNameForDates(to, from: from) flights = FlightBooking.getFlightNameForDates(to, from: from) cars = RentalCarBooking.getRentalCarNameForDates(to, from: from) } func bookTrip(hotel: Hotel, flight: Flight, rentalCar: RentalCar) { HotelBooking.bookHotel(hotel) FlightBooking.bookFlight(flight) RentalCarBooking.bookRentalCar(rentalCar) } }
代理模式中,通常一个类型需要扮演接口的角色。比如在 API 和自定义代码间创建一个抽象层。以后即使修改 API 也不需要重构我们的代码,修改抽象层就可以了。还有种可能,我们需要修改 API,但没有代码或没有权限,也只能通过这种方式来搞定。
举例,我们有一栋楼房 House,每层都有不同的房间 FloorPlanProtocol
protocol FloorPlanProtocol { var bedRooms: Int {get set} var utilityRooms: Int {get set} var bathRooms: Int {get set} var kitchen: Int {get set} var livingRooms: Int {get set} } struct FloorPlan: FloorPlanProtocol { var bedRooms = 0 var utilityRooms = 0 var bathRooms = 0 var kitchen = 0 var livingRooms = 0 } class House { var stories = [FloorPlanProtocol]() func addStory(floorPlan: FloorPlanProtocol) { stories.append(floorPlan) } } var ourHouse = House() var basement = FloorPlan(bedRooms: 0, utilityRooms: 1, bathRooms: 1, kitchen: 0, livingRooms: 1) var firstStory = FloorPlan(bedRooms: 1, utilityRooms: 0, bathRooms: 2, kitchen: 1, livingRooms: 1) var secondStory = FloorPlan(bedRooms: 2, utilityRooms: 0, bathRooms: 1, kitchen: 0, livingRooms: 1) var additionalStory = FloorPlan(bedRooms: 1, utilityRooms: 0, bathRooms: 1, kitchen: 1, livingRooms: 1) print(ourHouse.addStory(basement)) print(ourHouse.addStory(firstStory)) print(ourHouse.addStory(secondStory)) print(ourHouse.addStory(additionalStory))
但是如果我们想要限制每层楼房的高度,比如三层,这时候除了修改 House,我们还可以将 addStory
逻辑抽离出来封装到一个代理对象中
class HouseProxy { var house = House() func addStory(floorPlan: FloorPlanProtocol) -> Bool { if house.stories.count < 3 { house.addStory(floorPlan) return true } else { return false } } }
这样只需要修改 ourHouse
为 HouseProxy
对象即可
var ourHouse = HouseProxy()
代理模式非常适用于当你不能改变类本身的情况下,向类添加一些功能或进行错误检查。
Behavioral design patterns 主要处理类型之间的交互,有下面几种模式:
同样是挑三种来详细介绍下
将需要执行的命令封装到一个类型中
使用命令模式创建各种开关灯的命令,然后在 Light 类型中使用这些命令,同样是利用协议
protocol Command { func execute() } struct RockerSwitchLightOnCommand: Command { func execute() { print("Rocker Switch: Turning Light On") } } struct RockerSwitchLightOffCommand: Command { func execute() { print("ocker Switch: Turning Light Off") } } struct PullSwitchLightOnCommand: Command { func execute() { print("Pull Switch: Turning Light On") } } struct PullSwitchLightOffCommand: Command { func execute() { print("Pull Switch: Turning Light Off") } }
这四条命令都遵守 Command
协议,然后我们就能构造 Light 类使用这些命令
class Light { private var lightOnCommand: Command private var lightOffCommand: Command init(lightOnCommand: Command, lightOffCommand: Command) { self.lightOnCommand = lightOnCommand self.lightOffCommand = lightOffCommand } func turnOnLight() { self.lightOnCommand.execute() } func turnOffLight() { self.lightOffCommand.execute() } }
这样在执行时就可以随时切换命令
var on = PullSwitchLightOnCommand() var off = PullSwitchLightOffCommand() var light = Light(lightOnCommand: on, lightOffCommand: off) light.turnOnLight() light.turnOffLight()
我们把执行命令的细节都提前封装到了具体的命令类型中
这种模式主要针对算法,它定义了一系列算法,将每一个算法封装起来,并让它们可以相互替换。 策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。
还是来看例子,设计一个压缩算法协议
protocol CompressionStrategy { func compressFiles(filePaths: [String]) }
可以处理 Zip 和 Rar,具体的压缩算法封装在这里
struct ZipCompressionStrategy: CompressionStrategy { func compressFiles(filePaths: [String]) { print("Using Zip Compression") } } struct RarCompressionStrategy: CompressionStrategy { func compressFiles(filePaths: [String]) { print("Using RAR Compression") } }
我们接着使用 CompressContent 对算法进行二次封装
class CompressContent { var strategy: CompressionStrategy init(strategy: CompressionStrategy) { self.strategy = strategy } func compressFiles(filePaths: [String]) { self.strategy.compressFiles(filePaths) } }
这样就能根据实际需求动态地配置压缩算法
var filePaths = ["file1.txt", "file2.txt"] var zip = ZipCompressionStrategy() var rar = RarCompressionStrategy() var compress = CompressContent(strategy: zip) compress.compressFiles(filePaths) compress.strategy = rar compress.compressFiles(filePaths)
可以使用 Cocoa 和 Cocoa-Touch 提供的 NSNotificationCenter
来实现。但本章的主题是面向协议编程,我们可以用协议来实现。首先定义一个协议,想要收到通知必须遵守该协议。
protocol ZombieObserverProtocol { func turnLeft() func turnRight() func seesUs() }
接着定义观察者
class MyObserver: ZombieObserverProtocol { func turnLeft() { print("Zombie turned left, we move right") } func turnRight() { print("Zombie turned right, we move left") } func seesUs() { print("Zombie sees us, RUN!!!!") } }
最后实现 Zombie 类型,当僵尸转向或咬人时,它会给观察者发送通知
struct Zombie { var observer: ZombieObserverProtocol func turnZombieLeft() { //Zombie turns left //Notify observer observer.turnLeft() } func turnZombieRight() { //Zombie turns right //Notify observer observer.turnRight() } func spotHuman() { //Zombie sees us //Notify observer observer.seesUs() } }
可以测试一下效果
var observer = MyObserver() var zombie = Zombie(observer: observer) zombie.turnZombieLeft() zombie.spotHuman()
观察者模式通常用在 UI 发生变化时,如果有多个观察者,我们可以把这些观察者放入一个数组,发生变化时,需要遍历数组去通知这些观察者。所以还是使用 NSNotificationCenter 来的方便,它替我们完成了这些工作。
最后我们来实现一个属性变化的订阅,当属性发生变化时,我们需要收到一个通知。还是先定义一个协议:
protocol PropertyObserverProtocol { func propertyChanged(propertyName: String, newValue: Any) }
然后定义我们的观察者
class MyObserverType: PropertyObserverProtocol { func propertyChanged(propertyName: String, newValue: Any) { print("----changed----") print("Property Name: /(propertyName)") print("New Value: /(newValue)") } }
接着定义一个类型,当属性发生变化时通知观察者并传递相关参数
struct PropertyObserver { var observer: PropertyObserverProtocol var property1: String { didSet{ observer.propertyChanged("property1", newValue: property1) } willSet(newValue) { print("Property Changing") } } }
最后搞几个实例对象验证一下:
var myObserver = MyObserverType() var p = PropertyObserver(observer: myObserver, property1: "Initial String") p.property1 = "My String"