接着来学习后半部分
本章的主题有:
可以定义属性和方法,如果是值类型遵守协议,方法涉及到修改自身属性记得要加 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"