티스토리 뷰
📌 목차
1️⃣ SOLID
2️⃣ SRP, 단일 책임 원칙
3️⃣ OCP, 개방-폐쇄 원칙
4️⃣ LSP, 리스코프 치환 원칙
5️⃣ ISP, 인터페이스 분리 원칙
6️⃣ DIP, 의존 역전 원칙
1️⃣ SOLID
객체지향 프로그래밍의 목표는 결합도는 낮고, 응집도는 높은 프로그램을 만드는 것!
그렇게 하기 위한 가이드가 바로 SOLID
SRP : Single Responsibility Principle
OCP : Open Closed Principle
LSP : Lskov Substituion Principle
ISP : Interface Segregation Principle
DIP : Dependency Inversion Principle
🖐🏻 객체지향 4대 원칙 - 객체지향 프로그래밍의 근간을 이루는 원칙들
캡슐화 / 상속 / 추상화 / 다형성
✔️ 캡슐화
- 클래스가 가진 모든 필드를 노출 ❌ (외부 클래스와의 결합도↓)
- 대신, 클래스의 역할(할 수 있는 일)인 메서드를 노출 ⭕ (클래스 내의 응집도↑)
✔️ 상속
- 다른 클래스의 필드(메서드X)를 확장하기 위해 상속을 함 (private 필드 제외)
✔️ 추상화
- 추상적인 존재에 의존하여 생기는 장점들(ex. 런타임 시점에 구현체 변경, 클라이언트 코드에 변화없이 확장 가능 - OCP)
✔️ 다형성
- 똑같은 클라이언트의 코드로 안에 들어있는 구현체에 따라 다른 동작이 수행되는 것 (대체가능성 + 내부동질성)
2️⃣ SRP, 단일 책임 원칙
하나의 클래스는 하나의 책임(변경하려는 이유)만을 가져야 한다.
🔎 SRP가 깨진 예시
(1)
// In Service.class
public void method() {
if(유효성 검사 실패)
throw new CustomException("유효성 검사 실패", HttpStatus.BAD_REQUEST); // Service가 Controller를 알고 있음!!
}
- 캡슐화가 깨져서 나타나는 문제 (Controller만 알고 있어야 하는 것을 Service도 알고 있어서)
- 위의 상황에서 Controller가 변경되면 Service도 변경해야 함 (Service의 변경 이유가 2개)
(2)
public class Service {
private UserRepository userRepository;
private ArticleRepository articleRepository;
// User
public void createUser(...) {...}
public User findUser(...) {...}
// Article
public void createArtucle(...) {...}
public Article findArticle(...) {...}
}
- 코드에 응집력이 없어서 발생한 문제
- Service의 변경 이유가 2개 (User, Article)
3️⃣ OCP, 개방-폐쇄 원칙
소프트웨어는 확장에는 열려 있고, 변경에는 닫혀 있어야 한다 (변경 사항이 다른 코드에 영향X)
❗인터페이스를 통해 달성하기 쉽지만, 오직 인터페이스만으로 달성할 수 있는 원칙은 아님! (ex. Enum, 몇몇 디자인 패턴 , 이벤트 기반 프로그래밍)
🔎 OCP가 깨진 예시
- 고수준 컴포넌트가 저수준 컴포넌트에 의존하고 있을 때 (Service가 Repository에 의존하고 있을 때)
4️⃣ LSP, 리스코프 치환 원칙
파생 클래스는 기반 클래스를 대체할 수 있어야 한다.
→ 부모 클래스가 할 수 있는 행동은 자식 클래스도 할 수 있어야 한다.
🔎 LSP가 깨진 예시
- 자식 클래스에서 제약이 강화되는 경우 (ex. 부모 클래스에서 public인 메서드를 자식 클래스에서 private으로 변경)
→ 클라이언트는 당연히 부모에서 public인 메서드가 동작할 것이라고 생각! (구현체가 부모 클래스인지, 자식 클래스인지 상관❌)
➡️ 즉, 사전 조건(ex. 호출하는 곳에서 넘겨주는 파라미터)은 자식 클래스에서 더 강해지면 안된다 (계약에 의한 설계)
5️⃣ ISP, 인터페이스 분리 원칙
클라이언트별로 세분화된 인터페이스를 만들어야 한다
🙋🏻♀️ SRP와 비슷한 것 같아요!
💡 결과적으로 보면 방향성은 같지만, SRP는 변경의 이유였던 책임을 하나로 만들어야 한다는 원칙 but ISP는 불필요한 노출 그 자체에 의의를 두고 있음! (캡슐화를 지키기 위해서 인 것 같다!)
🔎 ISP가 깨진 예시
public interface Repository {
void createUser();
User findUserById(Long id);
void createArticle();
Article findArticleById(Long id);
}
이런 인터페이스가 있다면 UserRepository라는 구상체를 만들 때
- 불필요한 메서드를 구현(오버라이딩)해야함
- 해당 메서드를 사용하는 클라이언트(ex. UserService)들에게 불필요한 메서드 노출 (난 createArticle()이라는 메서드가 필요 없는데..?)
6️⃣ DIP, 의존 역전 원칙
고수준 컴포넌트는 저수준 컴포넌트에 의존하지 않아야 한다
- 컴포넌트 : 여러 개의 프로그램 함수들을 모아 하나의 특정한 기능을 수행할 수 있도록 구성한 작은 기능적 단위 (ex. 클래스)
- 고수준 : 구체적인 기술에 종속❌ (x. Service 계층)
- 저수준 : 구체적인 기술에 종속⭕ (ex. Repository 계층)
🙋🏻♀️ OCP와 비슷한 것 같아요!
💡두 원칙 모두 구현 클래스에 의존하지 말고 중간에 인터페이스를 추가하여 문제를 해결할 수 있지만, OCP는 인터페이스가 되었든 아니든 확장하거나 변경할 때 다른 코드들이 영향을 받지 않도록 하는 것 but OCP는 고수준 컴포넌트가 저수준 컴포넌트에 의존하지 않도록 하는데 의의가 있음!
🔎 간접적으로 DIP가 깨진 예시
- Service에서 Repository로부터 발생된 예외를 처리하려면 어떤 예외를 발생시키는지 (ex. RecordNotFoundException)를 알아야 함 → 간접적으로 Repository 구현체를 알고 있어야 함! (구현체의 예외에 의존)
💡 구현체별로 서로 다른 예외를 던지지 말고, 특정 구현체에 상관없이 범용적으로 사용되는 이름을 가진 예외를 던지자! (추상적인 정보만을 노출)
'데브코스 > 1주차 - 프레임워크를 위한 JAVA' 카테고리의 다른 글
Chapter06 구현된 걸 사용하게 되는 패턴 (0) | 2023.09.25 |
---|---|
Chapter05 직접 구현하게 되는 패턴 (0) | 2023.09.25 |
Chapter03 의존 (0) | 2023.09.22 |
Chapter02 객체지향적인 코드 작성 (0) | 2023.09.22 |
Chapter01 객체지향 관련 문법 정리 (0) | 2023.09.20 |