
캡슐화 (172p)
- 변수나 함수는 되도록이면 숨기는 것이 좋다. ⭐️⭐️
- 캡슐화를 풀어주는 결정은 언제나 최후의 수단이다.
클래스는 작아야 한다 (172p)
- 함수와 마찬가지로 클래스도 크기가 작아야 함. ⭐️⭐️
- 그렇다면 클래스는 얼마나 작아야 하는가?
- 클래스는 클래스가 맡은 책임을 센다. ⭐️
- 한 클래스가 맡은 책임이 너무 많다면 해당 클래스는 크기가 큰 클래스이다.
- 클래스가 얼마나 작아야 하냐면 클래스가 하나의 책임만 가지고 있을 정도로 작아야 한다.
- 클래스의 이름은 해당 클래스가 가진 책임을 기술하면 된다. ⭐️⭐️
단일 책임 원칙 (SRP) (175p)
- 하나의 클래스는 하나의 기능만 담당하여 책임지도록 한다.
- 여러 가지 기능을 책임지는 클래스를 여러 개의 단일 책임 클래스로 쪼개라.
응집도 (177p)
- 응집도는 한 모듈 내의 구성 요소 간의 밀접한 정도를 뜻한다.
- 응집도가 높다는 것은 다음과 같은 기준을 만족시키는 것을 의미함.
- 클래스는 인스턴스 변수 수가 일단 적어야 하며,
- 클래스가 하나의 기능(책임)만을 갖고 있어야 하며,
- 메소드가 인스턴스 변수를 많이 사용할수록 응집도가 더 높다.
- 일반적으로 이렇게 응집도가 가장 높은 클래스는 (모든 인스턴스 변수를 메소드마다 사용하는 클래스) 가능하지도 바람직하지도 않다. 하지만 응집도가 높은 클래스를 만드는 것은 매우 중요하다.
class Calculator {
var result: Int = 0
func add(_ value: Int) {
result += value
}
func subtract(_ value: Int) {
result -= value
}
func reset() {
result = 0
}
}
- 몇 몇 메소드만 사용하고 있는 인스턴스 변수가 클래스에 많아지고 있다면, 새로운 클래스로 쪼개야 한다는 신호이다.
OCP (188p)
- OCP란 기존의 코드를 변경하지 않고 기능을 추가할 수 있도록 클래스를 설계해야 한다는 원칙을 의미한다.
- 기능을 추가하기 위해 기존의 코드를 변경해야 한다면 코드를 유지보수하기가 굉장히 어려워지기 때문이다.
- 이상적인 시스템이라면 새 기능을 추가할 때 시스템을 확장할 뿐 기존 코드를 변경하지는 않는다. ⭐️⭐️
final class HelloMan {
func greet(name: String) {
print("안녕하세요, \(name)님!")
}
}
final class HelloMan {
func greet(name: String, language: String) {
if language == "Korean" {
print("안녕하세요, \(name)님!")
} else if language == "English" {
print("Hello, \(name)!")
}
}
}
protocol Greeting {
func greet(name: String)
}
class KoreanGreeting: Greeting {
func greet(name: String) {
print("안녕하세요, \(name)님!")
}
}
class EnglishGreeting: Greeting {
func greet(name: String) {
print("Hello, \(name)!")
}
}
final class HelloMan {
private let greeting: Greeting
init(greeting: Greeting) {
self.greeting = greeting
}
func greet(name: String) {
greeting.greet(name: name)
}
}
let englishGreeting = EnglishGreeting()
let koreanGreeting = KoreanGreeting()
let hellomanSpeaksKorean = HelloMan(greeting: koreanGreeting)
let hellomanSpeaksEnglish = HelloMan(greeting: englishGreeting)
hellomanSpeaksKorean.greet(name: "홍필")
hellomanSpeaksEnglish.greet(name: "Marigold")
변경으로부터 격리 (DIP) (189p) ⇒ OCP와 결을 같이하는 느낌임
- DIP란 클래스가 상세한 구현이 아니라 추상화에 의존해야 한다는 원칙이다.
- 구체적인 클래스와 추상 클래스의 특징은 다음과 같다.
- 구체적인 클래스
- 상세한 구현을 포함한다.
- 구현이 바뀌면 문제가 발생한다.
- 테스트 코드를 작성하기 어렵다
- 추상 클래스
- 개념만 포함한다.
- 구현이 바뀌어도 변경으로부터 격리되었기 때문에 문제가 발생하지 않는다.
- 테스트 코드를 작성하기 쉽다.
protocol StockExchange {
func currentPrice(symbol: String) -> Double
}
class Portfolio {
private let exchange: StockExchange
init(exchange: StockExchange) {
self.exchange = exchange
}
func getCurrentPrice(symbol: String) {
exchange.currentPrice(symbol: String)
}
}
final class FixedStockExchangeStub: StockExchange {
private var prices = [String: Double]()
func fix(symbol: String, price: Double) {
prices[symbol] = price
}
func currentPrice(symbol: String) -> Double {
return prices[symbol] ?? 0
}
}
import XCTest
@testable import Portfolio
class PortfolioTests {
var exchange: FixedStockExchangeStub!
var portfolio: Portfolio!
override func setUpWithError() throws {
try super.setUpWithError()
}
override func tearDownWithError() throws {
try super.tearDownWithError()
}
func setup() {
exchange = FixedStockExchangeStub()
exchange.fix(symbol: "MSFT", price: 500)
portfolio = Portfolio(exchange: exchange)
}
func test_GivenFiveMSFTTotalShouldBe500() {
setup()
XCTAssertEqual(portfolio.value(), 500)
}
}