classElectricCar {privateval motor =Motor() // 모든 차가 engine을 가지고 있는 것은 아니다.funstart() { motor.start() }}funmain(args: Array) {val electricCar =ElectricCar() electric.start()}
다형성 (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 클래스만 알고있음 → 클라이언트가 서브시스템에 의존하지 않는다.
변경 가능성이 있는 요소를 적절히 분리 (SRP) & 분리한 요소 사이의 의존성을 체계화 (DIP) 해야한다.
적절한 캡슐화 및 은닉화 필요
9장. LSP (Liskov Substitution) - 리스코프 치환 원칙
부모 클래스의 기능을 무시하지 않고 하위타입 인스턴스로 치환하여 동작할 수 있어야한다.
정사각형 / 직사각형 문제
Square가 Rectangle의 하위 타입으로 적절한가?
Rectangle → 높이와 너비가 독립적으로 변경
Square → 높이와 너비가 반드시 함께 변경
이 코드에서 Square를 생성하면 assert문 실패
Rectangle r =...r.setW(5)r.setH(2)assert(r.area() ==10);
집에서 사용하는 다양한 가구를 모델링 → 공통 특성을 만들 수 있을까?
interface (abstract)로 만들자
abstractclassFurniture { open fun getDescription(): String}classChair : **Furniture** { override fun getDescription(): String {return"일반적인 의자" }}classRotatingChair : **Furniture** { override fun getDescription(): String {return"회전하는 의자." } fun rotate(degrees: Int) {// 의자를 회전시키는 코드 }}
Furniture 인터페이스를 구현하여 모든 가구를 조작 가능
fun main() { val furnitures = mutableListOf<Furniture>()furnitures.add(Chair())furnitures.add(RotatingChair())for (furniture in furnitures) {println(furniture.getDescription()) }}
10장. ISP (Interface Segregation) - 인터페이스 분리 원칙
하나의 일반적인 인터페이스보다 여러개의 구체적인 인터페이스를 만들어야 한다.
각 클라이언트가 필요로 하는 인터페이스들을 분리하여, 상황에 따라 한 역할만을 하게 만들기
ex) User1은 op1(), User2는 op2(), User3는 op3() 만 사용한다고 가정
op2()를 재배포 → User1, User3 까지 다시 재배포해야함
적절한 인터페이스로 분리한다.
🌱 불필요한 기능이 많은 것에 의존하지 말아야 한다.
11장. DIP (Dependency Inversion) - 의존 역전 원칙
상위계층은 하위계층의 변화로부터 독립되어야 한다. (고수준은 저수준에 의존해서는 안되고, 저수준에서 고수준으로의 의존이 이루어져야 한다.)
low level (저수준) - 콘센트, 전등의 위치, 지붕의 크기 등의 기초공사 수준 (세부사항, Detail) → sub class
House → LightSwitch, WaterTap에 대한 의존성을 가진다.
classHouse {private val lightSwitch =LightSwitch()private val waterTap =WaterTap() fun turnOnLightSiwtch() {lightSwitch.turnOn() // 전등을 킨다 } fun turnOnWaterTap() {waterTap.turnOn() // 수도관을 튼다 }}
House 에서의 직접적인 의존을 막는다 → interface를 둠
interfaceSwitch { fun turnOn() fun turnOff()}classLightSwitch : Switch { override fun turnOn() {// 전등을 켜는 코드 } override fun turnOff() {// 전등을 끄는 코드 }}classWaterTap : Switch { override fun turnOn() {// 수도관에서 물을 얻는 코드 } override fun turnOff() {// 수도관에서 물을 끄는 코드 }}classHouse(private val switch: Switch) { fun performAction() {switch.turnOn() // 인터페이스를 통해 하부 구현에 의존// 다른 작업을 수행하는 코드switch.turnOff() // 인터페이스를 통해 하부 구현에 의존 }}
비밀번호 입력값 검증기
// 생년월일 검증 작업 추가 or 아이디 검증 조건 변경?classValidator {funvalidateId(id: String) {// 영문과 숫자조합// 8자 이상// ... }funvalidateNickname(nickname: String) {// 숫자 들어가면 안됨// 8자 이하 }}// 하나의 책임만을 가지기?classIdValidator {funvalidateId(id: String) {// 영문과 숫자조합// 8자 이상// ... }}
빈번한 코드 수정 → 유지보수가 힘들다
리팩토링 ‼️
/*** validator 인터페이스로 확장,* policy로 변화에 대응하자**/interfaceValidator {funvalidate(target: String)}classIdValidator(policy: IdPolicy): Validator {overridefunvalidate(target: String) {// 영문과 숫자 조합// 8자 이상 }}classBirthdayValidator(pocicy: BirthdayPolicy): Validator {overridefunvalidate(target: String) {// 1920년 이상// 1-12월만 }}