import Swift import Foundation
A short cheat-sheet with Xcode 7.3 Playground ( OOD-Principles-In-Swift.playground.zip ).
A class should have one, and only one, reason to change. ( read more )
Example:
protocol CanBeOpened { func open() } protocol CanBeClosed { func close() } // I'm the door. I have an encapsulated state and you can change it using methods. final class PodBayDoor: CanBeOpened, CanBeClosed { private enum State { case Open case Closed } private var state: State = .Closed func open() { state = .Open } func close() { state = .Closed } } // I'm only responsible for opening, no idea what's inside or how to close. class DoorOpener { let door: CanBeOpened init(door: CanBeOpened) { self.door = door } func execute() { door.open() } } // I'm only responsible for closing, no idea what's inside or how to open. class DoorCloser { let door: CanBeClosed init(door: CanBeClosed) { self.door = door } func execute() { door.close() } } let door = PodBayDoor()
⚠ Only the DoorOpener
is responsible for opening the door.
let doorOpener = DoorOpener(door: door) doorOpener.execute()
⚠ If another operation should be made upon closing the door, like switching on the alarm, you don't have to change the DoorOpener
class.
let doorCloser = DoorCloser(door: door) doorCloser.execute()
You should be able to extend a classes behavior, without modifying it. ( read more )
Example:
protocol CanShoot { func shoot() -> String } // I'm a laser beam. I can shoot. final class LaserBeam: CanShoot { func shoot() -> String { return "Ziiiiiip!" } } // I have weapons and trust me I can fire them all at once. Boom! Boom! Boom! final class WeaponsComposite { let weapons: [CanShoot] init(weapons: [CanShoot]) { self.weapons = weapons } func shoot() -> [String] { return weapons.map { $0.shoot() } } } let laser = LaserBeam() var weapons = WeaponsComposite(weapons: [laser]) weapons.shoot()
I'm a rocket launcher. I can shoot a rocket.
final class RocketLauncher: CanShoot { func shoot() -> String { return "Whoosh!" } } let rocket = RocketLauncher() weapons = WeaponsComposite(weapons: [laser, rocket]) weapons.shoot()
Derived classes must be substitutable for their base classes. ( read more )
Example:
let requestKey: NSString = "NSURLRequestKey" // I'm a NSError subclass. I provide additional functionality but don't mess with original ones. class RequestError: NSError { var request: NSURLRequest? { return self.userInfo[requestKey] as? NSURLRequest } } // I fail to fetch data and will return RequestError. func fetchData(request: NSURLRequest) -> (data: NSData?, error: RequestError?) { let userInfo: [NSObject:AnyObject] = [ requestKey : request ] return (nil, RequestError(domain:"DOMAIN", code:0, userInfo: userInfo)) } // I don't know what RequestError is and will fail and return a NSError. func willReturnObjectOrError() -> (object: AnyObject?, error: NSError?) { let request = NSURLRequest() let result = fetchData(request) return (result.data, result.error) } let result = willReturnObjectOrError() // Ok. This is a perfect NSError instance from my perspective. let error: Int? = result.error?.code // But hey! What's that? It's also a RequestError! Nice! if let requestError = result.error as? RequestError { requestError.request }
Make fine grained interfaces that are client specific. ( read more )
Example:
// I have a landing site. protocol LandingSiteHaving { var landingSite: String { get } } // I can land on LandingSiteHaving objects. protocol Landing { func landOn(on: LandingSiteHaving) -> String } // I have payload. protocol PayloadHaving { var payload: String { get } } // I can fetch payload from vehicle (ex. via Canadarm). final class InternationalSpaceStation {
⚠ Space station has no idea about landing capabilities of SpaceXCRS8.
func fetchPayload(vehicle: PayloadHaving) -> String { return "Deployed /(vehicle.payload) at April 10, 2016, 11:23 UTC" } } // I'm a barge - I have landing site (well, you get the idea). final class OfCourseIStillLoveYouBarge: LandingSiteHaving { let landingSite = "a barge on the Atlantic Ocean" } // I have payload and can land on things having landing site. // I'm a very limited Space Vehicle, I know. final class SpaceXCRS8: Landing, PayloadHaving { let payload = "BEAM and some Cube Sats"
⚠ CRS8 knows only about the landing site information.
func landOn(on: LandingSiteHaving) -> String { return "Landed on /(on.landingSite) at April 8, 2016 20:52 UTC" } } let crs8 = SpaceXCRS8() let barge = OfCourseIStillLoveYouBarge() let spaceStation = InternationalSpaceStation() spaceStation.fetchPayload(crs8) crs8.landOn(barge)
Depend on abstractions, not on concretions. ( read more )
Example:
protocol TimeTraveling { func travelInTime(time: NSTimeInterval) -> String } final class DeLorean: TimeTraveling { func travelInTime(time: NSTimeInterval) -> String { return "Used Flux Capacitor and travelled in time by: /(time)s" } } final class EmmettBrown { private let timeMachine: TimeTraveling
⚠ Emmet Brown is given the DeLorean
as a TimeTraveling
device, not the concrete class DeLorean
.
init(timeMachine: TimeTraveling) { self.timeMachine = timeMachine } func travelInTime(time: NSTimeInterval) -> String { return timeMachine.travelInTime(time) } } let timeMachine = DeLorean() let mastermind = EmmettBrown(timeMachine: timeMachine) mastermind.travelInTime(-3600 * 8760)