5. 자율적인 객체
상태(state)와 행동(behavior)을 가지고 스스로 판단하는 자율적인 존재
특징
캡슐화
- 객체 내부의 세부 사항을 감추는 것
- 행동을 외부에 공개하여 메시지를 통해 상호작용
응집도가 높은 객체
- 스스로 자신의 데이터를 처리하는 자율적인 존재
- 밀접하게 연관된 작업만을 수행하고 연관성이 없는 작업은 다른 객체에게 위임
메세지를 통해서만 협력하는 자율적인 객체들의 공동체 만들기
5
7. 자판기 속 객체지향
자판기는 아래와 같은 과정을 가집니다
캡슐화
1. 소비자가 돈을 넣는다
2. 넣은 돈으로 구매할 수 있는 메뉴에 LED가 켜진다
3. 소비자가 메뉴를 선택한다
4. 자판기 내부에서 종이컵을 외부로 내보낸다
5. 뜨거운 물을 적정량 붓는다
6. 원료를 종이컵에 투입한다
7. 컵을 회전시켜 내용물을 섞는다
8. 소비자는 컵을 가져간다
7
8. 자판기 속 객체지향
👤
소비자 객체
자판기 객체
☕ 🍵
아메리카노 객체
라떼 객체
소비자는 자판기의
메뉴를 선택할 수 있다
소비자는 자판기에
돈을 넣는다
음료를 제조한다
MENU
JDF!아메리카노!
IPU!아메리카노!
JDF!라떼!
IPU!라떼
메뉴판 객체
메뉴 항목
객체들
자율적인 객체들이 모여 협력을 이루는 모습
8
9. 역할, 책임, 협력
역할
- 어떤 협력에 참여하는 특정한 사람이 협력 안에서 차지하는 책임이
나 의무를 의미
- 소비자라는 역할을 맡은 사람은 음료를 구매해야 함
- 자판기는 주문된 음료를 제조하거나 재고에서 가져와 배출하는 책
임이 있음
9
10. 역할, 책임, 협력
책임
- 어떤 객체가 요청에 대해 대답해줄 수 있거나, 적절한 행동을 할 의
무가 있는 경우 해당 객체가 책임을 가진다고 한다
- 객체에 의해 정의되는 응집도 있는 행위의 집합
10
11. 역할, 책임, 협력
크레이그 라만 (Craig Larman)
"객체지향 개발에서 가장 중요한 능력은 책임을
능숙하게 소프트웨어 객체에 할당하는 것"
11
12. 역할, 책임, 협력
🙋
하는 것(doing)
🧑🎓
아는 것(knowing)
•객체를 생성하거나 계산을 하는 등의
스스로 하는 것
•다른 객체의 행동을 시작시키는 것
•다른 객체의 활동을 제어하고 조절하
는 것
•개인적인 정보에 관해 아는 것
•관련된 객체에 관해 아는 것
•자신이 유도하거나 계산할 수 있는 것
에 관해 아는 것
객체의 책임은 두 가지로 구성된다!
12
13. 역할, 책임, 협력
협력
- 다수의 요청과 응답으로 구성되며 전체적으로 협력은 다수의 연쇄
적인 요청과 응답의 흐름으로 구성
- 요청과 응답은 협력에 참여하는 객체가 수행할 책임을 정의
소비자 자판기
음료를 선택한다
음료를 제조한다
요청
응답13
14. 역할, 책임, 협력
👤
소비자 객체 자판기 객체
☕🧃
음료 객체
MENU
메뉴판 객체
객체
역할 소비자는 음료를 구매한다 음료 주문을 받는다
소비자에게
메뉴를 전달한다
(자판기의 LED)
책임
소비자는 자판기에
돈을 넣는다
소비자는 자판기의
메뉴를 선택한다
음료를 제조한다
음료를 배출한다
소비자의 선택을 받는다
협력의 결과
자율적인 객체들이 모여 협력을 이루는 모습
14
15. 15
객체지향 설계 SOLID 원칙
“유지 보수와 확장이 쉬운 소프트웨어를 만들기 위한 객체 지향 설계 원칙”
- Single Responsibility Principle
- Open Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
16. Single Responsibility Principle
로버트 마틴은 모듈의 응집도가 변경과 연관이 있다는 것을 강조하기 위해 SRP 제시
“클래스는 단 한가지의 변경 이유만 가져야 한다.”
16
코드의 가독성 향상
유지보수 용이
책임을 적절히 분배함으로써 ✅
✅
단일 책임 원칙
17. class InstagramManager {
fileprivate func request() -> Data {
// Instagram API에 대한 요청
return Data()
}
fileprivate func convertJsonToModel(with data: Data) -> [AnyObject] {
// JSON을 모델로 변환한다.
return [AnyObject]()
}
fileprivate func saveInCoreData(with models: [AnyObject]) {
// 코어 데이터에 저장한다.
}
public func create() {
let data = request()
let model = convertJsonToModel(with: data)
saveInCoreData(with: model)
}
}
변경 전
17
18. class RequestManager {
func request() -> Data{
// Instagram API에 대한 요청
return Data()
}
}
class ParseManager {
func convertJsonToModel(with data: Data) -> [AnyObject] {
// JSON을 모델로 변환한다.
return [AnyObject]()
}
}
class CoreDataManager {
fileprivate func saveInCoreData(with models: [AnyObject]) {
// 코어 데이터에 저장한다.
}
}
class InstagramManager {
fileprivate let requestManager: RequestManager
fileprivate let parse: ParseManager
fileprivate let coreManager: CoreDataManager
init(requestManager: RequestManager, parseManager: ParseManager, coreManager: CoreDataManager) {
self.requestManager = requestManager
self.parse = parseManager
self.coreManager = coreManager
}
func create() {
let data = self.requestManager.request()
let objects = self.parse.convertJsonToModel(with: data)
coreManager.saveInCoreData(with: objects)
}
}
변경 후
18
19. 변경의 이유를 파악하는 2가지
응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 생성자를 통해 초기화한다.
부분적으로 초기화되지 않는 속성이 있다면 초기화되는 속성을 기준으로 코드를 분리해야 한다.
1. 인스턴스 변수의 초기화 시점
모든 메서드가 모든 속성을 사용한다면 응집도가 높다고 할 수 있다.
하지만 메서드들이 사용하는 속성에 따라 그룹이 나뉜다면 응집도가 낮다고 볼 수 있으며
메서드가 사용하는 속성 그룹을 기준으로 코드를 분리해야 한다.
2. 메서드들이 인스턴스 변수를 사용하는 방식
19
20. SRP 위반의 악취
- 여러 원인에 의한 변경 (Divergent change)
- 산탄총 수술 (Shotgun surgery)
응집성을 높이는 작업으로 산발적으로 여러 곳에 분포된 책임들을 한 곳에 모
으면서 설계를 깨끗하게 하는 것
하나의 클래스에 여러 책임이 혼재되어 있어 각각의 개별 클래스로 분할하는 것
분리된 클래스간의 관계에 복잡도를 줄이도록 설계한다. 만약 분리된 클래스
가 유사하고 비슷한 책임을 중복해서 갖고 있다면 슈퍼클래스를 사용할 수 있
다.
이럴만한 클래스가 없다면 새로운 클래스를 만들어 해결
21. Open Close Principle
21
“클래스는 확장에 열려있어야 하고, 수정에 대해서는 닫혀있어야 한다.”
요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야 하
며, 기존 구성요소를 쉽게 확장해 재사용할 수 있어야 한다.
OCP 중요 메커니즘 추상화
다형성
개방 폐쇄 원칙
✅
✅
22. 22
변경 전
class Dog {
func cryingSound() -> String {
return "멍멍"
}
}
class Cat {
func cryingSound() -> String {
return "야옹"
}
}
func printCryingSound(of animal: Any) {
if let dog = animal as? Dog {
print(dog.cryingSound())
} else if let cat = animal as? Cat {
print(cat.cryingSound())
}
}
변경 후
protocol Cryable {
func cryingSound() -> String
}
class Dog: Cryable {
func cryingSound() -> String {
return "멍멍"
}
}
class Cat: Cryable {
func cryingSound() -> String {
return "야옹"
}
}
func printCryingSound(of animal: Cryable) {
print(animal.cryingSound())
}
23. 23
Liskov Substitution Principle
“서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다.”
특징
자식 클래스는 부모 클래스에서 가능한 행위를 수행할 수 있어야 한다.✅
부모 클래스의 책임을 재정의하고 확장을 수행해야 한다.✅
리스코프 치환 원칙
24. 24
class Rectangle {
var width: Float = 0
var length: Float = 0
var area: Float {
return width * length
}
}
class Square: Rectangle {
override var width: Float {
didSet {
length = width
}
}
}
func printArea(of rectangle: Rectangle) {
rectangle.width = 2
rectangle.length = 5
print(rectangle.area)
}
let rectangle = Rectangle()
printArea(of: rectangle)
let square = Square()
printArea(of: square)
변경 전 변경 후
protocol Polygon {
var area: Float { get }
}
class Rectangle: Polygon {
var width: Float
var length: Float
var area: Float {
return width * length
}
init(width: Float, length: Float) {
self.width = width
self.length = length
}
}
class Square: Polygon {
var sideLength: Float
var area: Float {
return sideLength * sideLength
}
init(sideLength: Float) {
self.sideLength = sideLength
}
}
25. 25
Interface Segregation Principle
“한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.”
특징
어떤 클래스의 특정 부분집합만을 이용한다면, 이들을 따로 인터페이스로 분리한다.✅
하나의 일반적인 인터페이스 보다는 여러 개의 구체적인 인터페이스가 낫다.✅
SRP가 클래스의 단일책임을 강조한다면 ISP는 인터페이스의 단일책임을 강조한다.✅
인터페이스 분리 법칙
26. 26
변경 전 변경 후
protocol GestureProtocol {
func didTap()
func didDoubleTap()
func didLongPress()
}
class MyButton: GestureProtocol {
func didTap() {
// 탭 액션
}
func didDoubleTap() {
// 더블 탭 액션
}
func didLongPress() {
// 롱 프레스 액션
}
}
protocol TapProtocol {
func didTap()
}
protocol DoubleTapProtocol {
func didDoubleTap()
}
protocol LongPressProtocol {
func didLongPress()
}
class MyButton: TapProtocol, DoubleTapProtocol,
LongPressProtocol {
func didTap() {
// 탭 액션
}
func didDoubleTap() {
// 더블 탭 액션
}
func didLongPress() {
// 롱 프레스 액션
}
}
27. 27
Dependency Inversion Principle
의존관계 역전 원칙
특징
변화하기 쉬운 구체적인 클래스 보다 변화하기 어려운 추상 클래스 혹은 인터페이스에 의존한다.✅
“상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.”
DIP를 만족하면 ‘의존성 주입’ 이라는 기술로 변화에 유연한 설계를 할 수 있다.✅
28. 28
Dependency Injection
의존성 주입
- 의존성
- 주입
의존 관계를 가지고 있다. 예시) A 클래스 내부 변수로 B 클래스를 가지고 있다.
내부가 아니라 외부에서 객체를 생성해서 넣어 준다.
+ 의존성 분리
의존관계 역전 원칙을 통해 의존관계 분리
상위 계층이 하위 계층에 의존하고 있는 것을 반전시켜 하위 계층의 구현으로 부터 독립시킨
다.
의존성을 주입할 때 구체 클래스가 아닌 프로토콜을 사용해 의존관계를 독립시킨다.
29. 29
변경 전
class Handler {
let fm = FilesystemManager()
func handle(string: String) {
fm.save(string: string)
}
}
class FilesystemManager {
func save(string: String) {
// 파일을 연다.
// 파일에 문자열을 저장한다.
// 파일을 닫는다.
}
}
변경 후
protocol Storage {
func save(string: String)
}
class Handler {
let storage: Storage
init(storage: Storage) {
self.storage = storage
}
func handle(string: String) {
storage.save(string: string)
}
}
class FilesystemManager: Storage {
func save(string: String) {
// 파일을 연다.
// 파일에 문자열을 저장한다.
// 파일을 닫는다.
}
}