I came across the Protocol-Oriented programming concept recently and I found it quite fascinating, so I decided to rewrite my previous story with protocols and value-based approaches.
The first thing I need to do is layout all the requirements:
I want to model all the creatures in the world including animals and plants. Basically all creatures have names. The difference between animals and plants is whether they can move, generally speaking. All the moving animals have their own ‘Escape Strategy’. But how do they execute their strategies varies. //: Create the base protocol 'Creature'
public protocol Creature : CustomStringConvertible {
static var entityName : String { get }
var name : String { get }
var canMove : Bool { get }
}
//: Protocol 'Animal' inherit from 'Creature' protocol and it has a 'escapeStrategy', can 'performEscape'
public protocol Animal : Creature {
var escapeStrategy : EscapeStrategy { get }
func performEscape ()
}
//: 'Plant' also conforms to 'Creature' protocol
public protocol Plant : Creature {
}
//: It's a wonder that we can actually extend a protocol and have it carry out the default implementation of other protocols. This case, all animals can 'performEscape'
extension Animal {
public func performEscape (){
print ( "\(name) about to execute a Escape Plan" )
escapeStrategy . executeEscapePlan ()
print ( "\(name) successfully escaped" )
}
}
//: Here is the key. All animals have 'EscapeStrategy', but how they implement will be different. Have a protocol enables the runtime execution to be different
public protocol EscapeStrategy {
func executeEscapePlan ()
}
public class EscapeByFlying : EscapeStrategy {
public func executeEscapePlan () {
print ( "Escape by flying" )
}
public init () {}
}
public class EscapeByRunning : EscapeStrategy {
public func executeEscapePlan () {
print ( "Escape by running" )
}
public init () {}
}
public class EscapeByOther : EscapeStrategy {
public func executeEscapePlan () {
print ( "Mysteriously escape" )
}
public init () {}
}
//: All animals can move
extension Moveable where Self : Animal {
public var canMove : Bool { return true }
}
The next step is to write some concrete struct / class to actually conform to the protocols. The power of protocol inheritance and composition makes it quite easy and flexible to do so.
//: Cat
public struct Cat : Animal , Moveable {
public static var entityName : String { return "Cat" }
public var name : String
public static func randomMove () { print ( "I am moving randomly" ) }
public var movingSpeed = 12.1
public var escapeStrategy : EscapeStrategy
public init ( name : String , movingSpeed : Double , escapeStrategy : EscapeStrategy ){
self . name = name
self . movingSpeed = movingSpeed
self . escapeStrategy = escapeStrategy
}
}
//:Rose
public struct Rose : Plant {
public static var entityName : String { return "Rose" }
public var name : String
public var canMove : Bool { return false }
public init ( name : String ){
self . name = name
}
}
public protocol Aged {
var age : Int { get }
}
public protocol FullyNamed {
var fullName : String { get }
}
//: 'Person' also confirms to 'Animal' protocol
public struct Person : Animal , FullyNamed , Moveable , Aged {
public static var entityName : String { return "Person" }
public var name : String
public var fullName : String
public static func randomMove () { print ( "I am moving randomly" ) }
public var movingSpeed = 15.1
public var age : Int { return 20 }
public var escapeStrategy : EscapeStrategy
public init ( name : String , fullName : String , movingSpeed : Double , escapeStrategy : EscapeStrategy ){
self . name = name
self . fullName = fullName
self . movingSpeed = movingSpeed
self . escapeStrategy = escapeStrategy
}
}
//: protocol composition
public func showCreatureAge ( creature : protocol & lt ; Creature , Aged > ) {
print ( "\(creature.name) is \(creature.age) years \' old" )
}
Finally test the code:
var rose = Rose ( name : "Pretty Rose Flower" )
var person = Person ( name : "Mia" , fullName : "Mia Zhu" , movingSpeed : 2121 , escapeStrategy : EscapeByFlying () )
var cat = Cat ( name : "Kitty" , movingSpeed : 10 , escapeStrategy : EscapeByRunning ())
var creatures :[ Creature ] = [ cat , rose , person ]
creatures . filter { $ 0 is Animal }. map { ( $ 0 as ! Animal ). performEscape ()}