웹풀스택 공부 중
15 - 7. Java - Design Pattern & SOLID Principle 본문
좋은, 객체 지향적 프로그래밍이란?
- 중복의 최소화
- 코드 변경의 용이성
- 재사용성
- DRY: Don’t Repeat Yourself
- KISS: Keep It Simple and Stupid
- YAGNI: You Ain’t Gonna Need It
- 구현보다 인터페이스에 맞춰서 코딩하자
- 구현은 언제든지 바뀔 수 있다. 인터페이스를 사용해서 유연하게 구현하자!
- 상속 보다는 인터페이스 구성 (Composite)을 사용하자!
- 상속이 아닌 인터페이스 구성 시 원하는 구현을 붙였다 떼었다가 할 수 있다!
SOLID
객체 지향의 정수
“제대로된 분업화”
목표: High Cohesion, Loose Coupling
- Cohesion: 응집도
- 관련된 작업을 하나로 묶는 행위 (높여야한다)
- Coupling: 결합도
- 하나가 바뀌면 다른 하나가 또 바뀌는 현상 (낮춰야한다)
- Cohesion: 응집도
S: Single Responsibility
하나의 모듈은 한가지 책임만 가진다
그래야지만 모듈이 변경되는 이유가 단 한가지일 수 있다
String 대신 StringBuilder를 사용해야하는 이유
불변 (Mutable): String
String은 하나하나마다 메모리를 사용한다 (메모리 효율성이 좋진 않음)
이런식으로 하나 만들때마다 계속 메모리 낭비가 있다
가변 (Immutable): StringBuffer/ StringBuilder
StringBuilder는 빌드를 호출했을때만 메모리를 사용하기에, 한번에 호출하여 메모리를 아낄 수 있다
O: Open-Closed Principle
확장에 열려있고, 수정에 닫혀있다
확장에 열려있다: 요구사항이 변경될 때, 새로운 동작을 추가하여 애플리케이션의 기능을 확장 시킬 수 있다
수정에 닫혀있다: 기존의 코드를 수정하지 않고, 애플리케이션의 동작을 추가하거나 변경이 가능하다
본질적으로 얘기하는 것은 “추상화”이며, 런타임 의존성과 컴파일 타임 의존성에 대한것이다
- 컴파일 타임의 의존성은 Interface에 해당하는 것이다
- 런타임 의존성은 구체 Class에 해당하는 것이다
- 그래서 Bean 객체들을 final로 받아야한다
OCP를 구현할 때, 다형성이 핵심 역할을 한다. 다형성을 사용하면 기존 클래스나 모듈을 변경하지 않고도 새로운 동작을 추가할 수 있다.
L: Liscov Subsitution
“상속 시” 부모 클래스에 대한 가정을 충족 시킨다
- “리스코프 치환”
- “계급제” 에 관련있다
- 부모가 가진 / 제공하는 모든 Method, Field는 자식도 전부 제공해야한다
- 부모 Class가 가진 모든 것을 자식 Class에서도 모두 따라야한다
I: Interface Segregation
인터페이스 분리 - 인터페이스 내에 메소드의 최소 갯수에 관련되어있다
- “인터페이스 내에 메소드는 최소한의 갯수로 만들도록 지향하자
- 하나의 일반적인 Interface보다 여러 개의 구체적인 Interface가 낫다
- ex.) 도메인으로 Interface를 쪼개어 놓으면
- 필요한 구현은 1개인데, 구현체에 나머지 쓰지도 않는 것까지 구현해야한다
- 너무 많은 역할을 구현하게 되면, 비구현하는 Method가 꽤 많이 생긴다
- ex.) 도메인으로 Interface를 쪼개어 놓으면
- 하나의 일반적인 Interface보다 여러 개의 구체적인 Interface가 낫다
- 인터페이스에 비구현 Method를 만들지 말자
- 그래서 Interface를 최대한 더 쪼게보자
- “인터페이스 내에 메소드는 최소한의 갯수로 만들도록 지향하자
D: Dependency Invarsion (의존성 역전)
인터페이스로 구현체를 연결한다
상위 수준의 모듈과 하위 수준의 모듈간의 의존성을 역전시켜 코드의 유연성과 유지보수성을 높이는 것을 목표로 한다
고수준 모듈: 다양한 일을 할 수 있는 Module
- 입력과 출력으로부터 (비지니스와 관련된) 먼 추상화된 모듈이다
- 추상화: 구체적인 구현에 의존하지 않아야한다
- 입력과 출력으로부터 (비지니스와 관련된) 먼 추상화된 모듈이다
저수준 모듈: 한가지 일만 할 수 있는 Module
- 입력과 출력에 가까운 (HTTP, Database, Cache, 등과 관련된) 구현 모듈
고수준 모듈은 저수준 모듈의 구현에 의존해서는 안되며, 저수준 모듈이 고수준 모듈에 의존해야한다
“의존 역전 원칙”이란 결국 비지니스와 관련된 부분이 세부 사항에는 의존하지 않아야하는 설계 원칙이다
즉, 구체적인 Class가 아니라 Interface나 Abstract Class와 같은 추상화된 형태에 의존하도록 설계를 해야한다
예시
의존성 역전 윈칙을 적용하지 않은 코드:
Notification
Class는EmailService
라는 구체적인 클래스에 의존하고 있다. 만약EmailService
가 변경되면Notification
도 수정해야하므로 의존성이 강하게 결합된 상태이다// 구체적인 클래스 의존 (의존성 역전 원칙 위반) class EmailService { sendEmail() { console.log("Sending email..."); } } class Notification { constructor() { this.emailService = new EmailService(); } sendNotification() { this.emailService.sendEmail(); } }
의존성 역전 윈칙을 적용한 코드:
Notification
class가EmailService
와 같은 구체적인 Class에 의존하지 않고,MessageService
라는 추상화된 Class에 의존하게 설계되어 있다.- 이런식으로 한다면 구체적인 구현이 변경되더라도 상위 Class는 수정할 필요가 없다
// 추상화된 인터페이스 class MessageService { sendMessage() { throw new Error("This method should be overridden!"); } } // 구체적인 클래스들은 추상화된 인터페이스를 구현 class EmailService extends MessageService { sendMessage() { console.log("Sending email..."); } } class SMSService extends MessageService { sendMessage() { console.log("Sending SMS..."); } } // 상위 클래스는 추상화된 인터페이스에 의존 class Notification { constructor(messageService) { this.messageService = messageService; // 추상화된 MessageService에 의존 } sendNotification() { this.messageService.sendMessage(); // 구체적인 구현은 알 필요 없음 } } // 사용하는 쪽에서 구체적인 구현을 주입 const emailService = new EmailService(); const notification = new Notification(emailService); notification.sendNotification(); // "Sending email..." 출력
'웹개발 > Java' 카테고리의 다른 글
15 - 8. Java - 익명 클래스 & 익명 구현 객체 (1) | 2024.11.14 |
---|---|
15 - 6. Java - ENUM (0) | 2024.11.04 |
15 - 5. Java - Abstract Class vs Interface (0) | 2024.11.04 |
15 - 4. Java - Generic & 자료 구조 (0) | 2024.11.04 |
15 - 3. Java - Static (0) | 2024.11.04 |