3부. 설계 원칙
[CleanArchitecture] Chapter3. 설계원칙
Overview
객체 지향 프로그래밍 (Object-oriented Programming)

💡 데이터를 **객체로 추상화**시켜, 객체 내부의 메서드 및 필드를 호출하며 **서로간의 상호작용**을 통해 로직을 구성하는 방법
vs 절차적 프로그래밍 (Procedural Programming)
물이 위에서 아래로 흐르는 것처럼 순차적인 처리를 중심으로 코드를 연결하는 프로그래밍 기법
AOS 부서 소속 Sio가 휴가를 신청하려고 하는데,,
객체 (Object)
object와 class
코틀린에서 클래스를 정의하는 키워드는
class코틀린에서
new키워드를 쓰지 않는다는 것은,,new가 생겨난 배경을 보면,, C++의new와deleteBut, Java 및 Kotlin → 메모리 제어의 권한이 프로그래머에게 있지 않다.✨
object키워드로 선언하게 되면 Eager Singleton으로 구현하는 것과 같다.클래스가 로드될 때 딱 한번만 생성 (vs Lazy Singleton)
companion object
💡 class를 인스턴스화 하지 않아도 접근할 수 있는 방법
Employee.ofEmpty()
클래스 종속적이지 않은 메서드 및 프로퍼티 (ex. 클래스 내부 필드가 필요없다.)
클래스 인스턴스가 아닌 클래스와 연결 된 메서드 및 프로퍼티
인 경우 companion object 내에서 선언할 수 있다.
data class
💡 데이터 보관 목적의 클래스
OOP 특징
추상화 (Abstaction)
복잡한 데이터 및 시스템으로부터 핵심적인 개념이나 기능을 간추려 내는 것 → 개략화
중요 성질을 추출하여 필드 및 메서드를 정의하기
공통 성질을 추출하여 부모 클래스 선정하기
캡슐화 (Encapsulation)
객체의 속성과 메소드를 하나로 묶고 일부를 외부로부터 감추어 은닉하는 것
API Response가 다음과 같이 떨어진다고 해보자
클래스로 추상화 해보자
Sio의 휴가 시작 날짜에 접근하기 위해서는
employee.dayOffInfos.period.start.date로 연속적인 체이닝 접근 필요
캡슐화를 적용해본다면?
전/후 비교
✅ DayOff의 내부 구현을 외부로부터 감춘다.
✅ startDate를 사용하는 위치에서는 Date의 내부에 변경이 있어도 영향을 받지 않게한다.
private,immutable활용 (backing property)
상속 (Inheritance)
상위 클래스의 특성을 하위 클래스가 이어받아 재사용하거나 확장 및 추가하는 것
코틀린에서 상속 및 오버라이딩이 가능하려면
open
다만 ,,
부모가 커진다 → 거대한 BaseXXX 클래스 및 불필요한 함수 존재
부모의 변경 → 자식의 변경
부모 클래스의 메소드, constraint 등 모든 것을 가져옴
💡 객체의 계층 구조를 나타낼 땐 **상속**, 일부만 재사용 하고싶다면 **컴포지션**
컴포지션 (Composition) ✨
우리가 원하는 행위만 가져다 쓰고싶다!
💡 객체를 property로 갖고, 함수를 호출하는 형태로 재사용성 ⬆
다형성 (Polymorphism)
하나의 메서드나 클래스가 다양한 방법으로 동작하는 것
오버로딩 - 같은 이름을 가진 메소드를 여러개 두고 메소드 타입, 매개변수 유형 및 개수로 구분
오버라이딩 - 상위 클래스로부터 상속받은 메소드를 하위 클래스에서 재정의
abstract
is kind of : 상위 클래스의 특성을 자신의 특징에 맞게 재사용 및 확장
interface
is able to : 각 클래스의 행위를 명세 및 구현
SOLID
5가지 객체지향 설계 원칙
7장. SRP(Single Responsibility) - 단일 책임 원칙
하나의 Module은 오직 하나의 Actor만 책임진다 → 하나의 Class / Method는 하나의 책임만을 가져야한다.
Employee 클래스는 3개의 액터에 영향을 미친다.
calculatePay → CFO (재무 관리자)
reportHours() → COO (업무 운영 책임자)
saveEmployee() → CTO (기술 경영자)
🌱 서로 다른 액터가 의존하는 코드를 분리한다.
3개의 클래스로 나누고, EmployeeData라는 클래스가 공유하도록 한다.
단, EmployeeData에서 3가지 클래스를 인스턴스화 하여 가지고 있어야 하고 계속 추적해야 하는데,,

🌱 Facade 패턴!
Facade → 건물의 정면
커다란 코드 시스템(건물 뒷부분)을 간략화된 인터페이스(건물 정면, 출입구)로 제공해주는 디자인 패턴
즉, 하위 시스템을 보다 쉽게 사용할 수 있는 고급 인터페이스
ex) 클라이언트는 오직 Facade 클래스만 알고있음 → 클라이언트가 서브시스템에 의존하지 않는다.
사용자는 동작 버튼 하나만 누르면 → 세탁, 행굼, 탈수 과정을 자동으로 진행

SubSystem
Washer (Facade)
8장. OCP (Open-Closed) - 개방-폐쇄 원칙
유지보수 할 땐 개방하여 쉽게 확장 / 수정할 땐 닫혀 있어야 한다.
아키텍처가 훌륭하다면 변경되는 코드의 양이 최소화 될 것이다.
변경 가능성이 있는 요소를 적절히 분리 (SRP) & 분리한 요소 사이의 의존성을 체계화 (DIP) 해야한다.
적절한 캡슐화 및 은닉화 필요
9장. LSP (Liskov Substitution) - 리스코프 치환 원칙
부모 클래스의 기능을 무시하지 않고 하위타입 인스턴스로 치환하여 동작할 수 있어야한다.
정사각형 / 직사각형 문제
Square가 Rectangle의 하위 타입으로 적절한가?
Rectangle → 높이와 너비가 독립적으로 변경
Square → 높이와 너비가 반드시 함께 변경

이 코드에서 Square를 생성하면 assert문 실패
집에서 사용하는 다양한 가구를 모델링 → 공통 특성을 만들 수 있을까?
interface (abstract)로 만들자
Furniture 인터페이스를 구현하여 모든 가구를 조작 가능
10장. ISP (Interface Segregation) - 인터페이스 분리 원칙
하나의 일반적인 인터페이스보다 여러개의 구체적인 인터페이스를 만들어야 한다.
각 클라이언트가 필요로 하는 인터페이스들을 분리하여, 상황에 따라 한 역할만을 하게 만들기
ex) User1은 op1(), User2는 op2(), User3는 op3() 만 사용한다고 가정
op2()를 재배포 → User1, User3 까지 다시 재배포해야함

적절한 인터페이스로 분리한다.

🌱 불필요한 기능이 많은 것에 의존하지 말아야 한다.
11장. DIP (Dependency Inversion) - 의존 역전 원칙
상위계층은 하위계층의 변화로부터 독립되어야 한다. (고수준은 저수준에 의존해서는 안되고, 저수준에서 고수준으로의 의존이 이루어져야 한다.)
high level (고수준)- 집의 형태, 외관, 공간, 방의 배치 (정책, Policy) → abstract class, interfacelow level (저수준)- 콘센트, 전등의 위치, 지붕의 크기 등의 기초공사 수준 (세부사항, Detail) → sub classHouse → LightSwitch, WaterTap에 대한 의존성을 가진다.
House 에서의 직접적인 의존을 막는다 → interface를 둠
비밀번호 입력값 검증기
빈번한 코드 수정 → 유지보수가 힘들다
리팩토링 ‼️
validator 조합
실제로 접근하는 클래스
Last updated