OOD-Principles-In-Swift

๐Ÿ’Ž The Principles of OOD (SOLID) based on Uncle Bob articles.

GPL-3.0 License

Stars
1.9K

๊ฐ์ฒด์ง€ํ–ฅ ์„ค๊ณ„ ์›์น™ in Swift 5

A short cheat-sheet with Playground (OOD-Principles-In-Swift-ko-KR.playground.zip).

๐Ÿ‘ท Project maintained by: @nsmeme (Oktawian Chojnacki)

๐Ÿ‡ฐ๐Ÿ‡ท Translated by: jwonyLee (JiWon Lee)

S.O.L.I.D.

๐Ÿ” ํด๋ž˜์Šค์—๋Š” ๋‹จ ํ•œ ๊ฐ€์ง€ ๋ณ€๊ฒฝ ์ด์œ ๋งŒ ์กด์žฌํ•ด์•ผ ํ•œ๋‹ค. (์ž์„ธํžˆ)

์˜ˆ์‹œ:


protocol Openable {
    mutating func open()
}

protocol Closeable {
    mutating func close()
}

// ๋ฌธ. ์บก์Šํ™”๋œ ์ƒํƒœ๋ฅผ ๊ฐ–๊ณ  ์žˆ์œผ๋ฉฐ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.
struct PodBayDoor: Openable, Closeable {

    private enum State {
        case open
        case closed
    }

    private var state: State = .closed

    mutating func open() {
        state = .open
    }

    mutating func close() {
        state = .closed
    }
}

// ์—ฌ๋Š” ์ผ๋งŒ ๋‹ด๋‹นํ•˜๋ฉฐ ์•ˆ์— ๋ฌด์—‡์ด ๋“ค์–ด์žˆ๋Š” ์ง€, ์–ด๋–ป๊ฒŒ ๋‹ซ๋Š” ์ง€ ๋ชจ๋ฅธ๋‹ค.
final class DoorOpener {
    private var door: Openable

    init(door: Openable) {
        self.door = door
    }

    func execute() {
        door.open()
    }
}

// ๋‹ซ๋Š” ์ผ๋งŒ ๋‹ด๋‹นํ•˜๋ฉฐ ์•ˆ์— ๋ฌด์—‡์ด ๋“ค์–ด์žˆ๋Š” ์ง€, ์–ด๋–ป๊ฒŒ ์—ฌ๋Š” ์ง€ ๋ชจ๋ฅธ๋‹ค.
final class DoorCloser {
    private var door: Closeable

    init(door: Closeable) {
        self.door = door
    }

    func execute() {
        door.close()
    }
}

let door = PodBayDoor()

// โš ๏ธ `DoorOpeneer`๋งŒ์ด ๋ฌธ์„ ์—ฌ๋Š” ์ฑ…์ž„์ด ์žˆ๋‹ค.
let doorOpener = DoorOpener(door: door)
doorOpener.execute()

// โš ๏ธ ๋ฌธ์„ ๋‹ซ์€ ํ›„ ๋‹ค๋ฅธ ์ž‘์—…์„ ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ,
// ์•Œ๋žŒ์„ ์ผœ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ `DoorOpener` ํด๋ž˜์Šค๋ฅผ ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
let doorCloser = DoorCloser(door: door)
doorCloser.execute()

โœ‹ The Open Closed Principle (๊ฐœ๋ฐฉ ํ์‡„ ์›์น™)

ํด๋ž˜์Šค์˜ ๋™์ž‘์„ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ , ํ™•์žฅํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค. (์ž์„ธํžˆ)

์˜ˆ์‹œ:


protocol Shooting {
    func shoot() -> String
}

// ๋ ˆ์ด์ € ๋น”. ์  ์ˆ˜ ์žˆ๋‹ค.
final class LaserBeam: Shooting {
    func shoot() -> String {
        return "Ziiiiiip!"
    }
}

// ๋ฌด๊ธฐ๊ฐ€ ์žˆ๊ณ  ๋ชจ๋“  ๊ฑธ ํ•œ ๋ฒˆ์— ๋ฐœ์‚ฌํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋ฏฟ๋Š”๋‹ค. ๋นต์•ผ! ๋นต์•ผ! ๋นต์•ผ!
final class WeaponsComposite {

    let weapons: [Shooting]

    init(weapons: [Shooting]) {
        self.weapons = weapons
    }

    func shoot() -> [String] {
        return weapons.map { $0.shoot() }
    }
}

let laser = LaserBeam()
var weapons = WeaponsComposite(weapons: [laser])

weapons.shoot()

// ๋กœ์ผ“ ๋Ÿฐ์ฒ˜. ๋กœ์ผ“์„ ์  ์ˆ˜ ์žˆ๋‹ค.
// โš ๏ธ ๋กœ์ผ“ ๋Ÿฐ์ฒ˜๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ๊ธฐ์กด ํด๋ž˜์Šค์—์„œ ์•„๋ฌด๊ฒƒ๋„ ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
final class RocketLauncher: Shooting {
    func shoot() -> String {
        return "Whoosh!"
    }
}

let rocket = RocketLauncher()

weapons = WeaponsComposite(weapons: [laser, rocket])
weapons.shoot()

๐Ÿ‘ฅ The Liskov Substitution Principle (๋ฆฌ์Šค์ฝ”ํ”„ ์น˜ํ™˜ ์›์น™)

ํŒŒ์ƒ๋œ ํด๋ž˜์Šค๋Š” ๊ธฐ๋ณธ ํด๋ž˜์Šค๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค. (์ž์„ธํžˆ)

์˜ˆ์‹œ:


let requestKey: String = "NSURLRequestKey"

// NSError ์„œ๋ธŒํด๋ž˜์Šค. ์ถ”๊ฐ€์ ์ธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ง€๋งŒ ์›๋ž˜ ๊ธฐ๋Šฅ์„ ์—‰๋ง์œผ๋กœ ๋งŒ๋“ค์ง„ ์•Š๋Š”๋‹ค.
class RequestError: NSError {

    var request: NSURLRequest? {
        return self.userInfo[requestKey] as? NSURLRequest
    }
}

// ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ•˜๋ฉด RequestError๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
func fetchData(request: NSURLRequest) -> (data: NSData?, error: RequestError?) {

    let userInfo: [String:Any] = [requestKey : request]

    return (nil, RequestError(domain:"DOMAIN", code:0, userInfo: userInfo))
}

// RequestError๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๋ชจ๋ฅด๊ณ  ์‹คํŒจํ•  ๊ฒƒ์ด๋ฉฐ, NSError๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
func willReturnObjectOrError() -> (object: AnyObject?, error: NSError?) {

    let request = NSURLRequest()
    let result = fetchData(request: request)

    return (result.data, result.error)
}

let result = willReturnObjectOrError()

// โš ๏ธ ํ™•์ธ. ์ด๊ฒƒ์€ ๋‚ด ๊ด€์ ์—์„œ ์™„๋ฒฝํ•œ NSError ์ธ์Šคํ„ด์Šค์ด๋‹ค.
let error: Int? = result.error?.code

// โš ๏ธ ํ•˜์ง€๋งŒ ์ด๋ด! ์ด๊ฒŒ ๋ฌด์Šจ ์ผ์ด์ฃ ? RequestError์ด๊ธฐ๋„ ํ•˜๋‹ค! ๋Œ€๋‹จํ•ด!
if let requestError = result.error as? RequestError {
    requestError.request
}

๐Ÿด The Interface Segregation Principle (์ธํ„ฐํŽ˜์ด์Šค ๋ถ„๋ฆฌ ์›์น™)

ํด๋ผ์ด์–ธํŠธ๋ณ„๋กœ ์„ธ๋ถ„ํ™”๋œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. (์ž์„ธํžˆ)

์˜ˆ์‹œ:


// ๋ฐฉ๋ฌธ ์‚ฌ์ดํŠธ๊ฐ€ ์žˆ๋‹ค.
protocol LandingSiteHaving {
    var landingSite: String { get }
}

// LandingSiteHaving ๊ฐ์ฒด์— ์ฐฉ๋ฅ™ํ•  ์ˆ˜ ์žˆ๋‹ค.
protocol Landing {
    func land(on: LandingSiteHaving) -> String
}

// ํŽ˜์ด๋กœ๋“œ๊ฐ€ ์žˆ๋‹ค.
protocol PayloadHaving {
    var payload: String { get }
}

// ์ฐจ๋Ÿ‰์—์„œ ํŽ˜์ด๋กœ๋“œ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค (์˜ˆ. Canadaarm์„ ํ†ตํ•ด).
protocol PayloadFetching {
    func fetchPayload(vehicle: PayloadHaving) -> String
}

final class InternationalSpaceStation: PayloadFetching {

    // โš ๏ธ ์šฐ์ฃผ ์ •๊ฑฐ์žฅ์€ SpaceXCRS8์˜ ์ฐฉ๋ฅ™ ๋Šฅ๋ ฅ์— ๋Œ€ํ•ด ์ „ํ˜€ ๋ชจ๋ฅธ๋‹ค.
    func fetchPayload(vehicle: PayloadHaving) -> String {
        return "Deployed \(vehicle.payload) at April 10, 2016, 11:23 UTC"
    }
}

// ๋ฐ”์ง€์„  - ์ฐฉ๋ฅ™ ์ง€์ ์ด ์žˆ๋‹ค (well, you get the idea).
final class OfCourseIStillLoveYouBarge: LandingSiteHaving {
    let landingSite = "a barge on the Atlantic Ocean"
}

// ํŽ˜์ด๋กœ๋“œ๊ฐ€ ์žˆ๊ณ  ์ฐฉ๋ฅ™ ์ง€์ ์ด ์žˆ๋Š” ๊ณณ์— ์ฐฉ๋ฅ™ํ•  ์ˆ˜ ์žˆ๋‹ค.
// ๋งค์šฐ ์ œํ•œ๋œ ์šฐ์ฃผ ๋น„ํ–‰์ฒด๋ผ๋Š” ๊ฒƒ์„ ์•ˆ๋‹ค.
final class SpaceXCRS8: Landing, PayloadHaving {

    let payload = "BEAM and some Cube Sats"

    // โš ๏ธ CRS8 ์€ ์ฐฉ๋ฅ™์ง€ ์ •๋ณด๋งŒ ์•Œ๊ณ  ์žˆ๋‹ค.
    func land(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(vehicle: crs8)
crs8.land(on: barge)

๐Ÿ” The Dependency Inversion Principle (์˜์กด๊ด€๊ณ„ ์—ญ์ „ ์›์น™)

๊ตฌ์ฒดํ™”์— ์˜์กดํ•˜์ง€ ๋ง๊ณ  ์ถ”์ƒํ™”์— ์˜์กดํ•˜๋ผ. (์ž์„ธํžˆ)

์˜ˆ์‹œ:


protocol TimeTraveling {
    func travelInTime(time: TimeInterval) -> String
}

final class DeLorean: TimeTraveling {
	func travelInTime(time: TimeInterval) -> String {
		return "Used Flux Capacitor and travelled in time by: \(time)s"
	}
}

final class EmmettBrown {
	private let timeMachine: TimeTraveling


    // โš ๏ธ Emmet Brown์€ `DeLorean`์„ ๊ตฌ์ฒด์ ์ธ ํด๋ž˜์Šค์ธ `DeLorean`์ด ์•„๋‹Œ, `TimeTraveling` ์žฅ์น˜๋กœ ๋ฐ›๋Š”๋‹ค.
	init(timeMachine: TimeTraveling) {
		self.timeMachine = timeMachine
	}

	func travelInTime(time: TimeInterval) -> String {
		return timeMachine.travelInTime(time: time)
	}
}

let timeMachine = DeLorean()

let mastermind = EmmettBrown(timeMachine: timeMachine)
mastermind.travelInTime(time: -3600 * 8760)

Info

๐Ÿ“– Descriptions from: The Principles of OOD by Uncle Bob

Related Projects