2025. 2. 10. 07:00ㆍ코딩 도구/백엔드 개발 (Backend Development)
좋은 객체 지향 설계의 5가지 원칙 (SOLID)
김영한님의 "스프링 핵심 원리 - 기본편"을 수강하고 정리했습니다.
1. SOLID 원칙이란?
SOLID 원칙은 클린 코드의 창시자로 유명한 로버트 마틴(Robert C. Martin)이 정리한 좋은 객체 지향 설계의 5가지 원칙이다. 이 원칙을 따르면 유지보수성과 확장성이 뛰어난 코드를 작성할 수 있다.
SOLID는 다음과 같은 다섯 가지 원칙의 약어다.
- SRP (Single Responsibility Principle): 단일 책임 원칙
- OCP (Open/Closed Principle): 개방-폐쇄 원칙
- LSP (Liskov Substitution Principle): 리스코프 치환 원칙
- ISP (Interface Segregation Principle): 인터페이스 분리 원칙
- DIP (Dependency Inversion Principle): 의존관계 역전 원칙
2. 단일 책임 원칙 (SRP: Single Responsibility Principle)
"한 클래스는 하나의 책임만 가져야 한다."
핵심 개념
- 클래스는 단 하나의 책임(변경의 이유)만 가져야 한다.
- 책임이 많아지면, 변경이 발생할 때 여러 기능에 영향을 미쳐 유지보수가 어렵다.
적용 예시
잘못된 예시: UI 변경과 비즈니스 로직이 섞여 있는 클래스
public class ReportGenerator {
public void generateReport() {
// 데이터 수집
// 데이터 처리
// UI에 출력 (HTML, PDF 등)
}
}
개선된 예시: UI 로직과 데이터 처리 로직을 분리
public class ReportDataCollector { /* 데이터 수집 관련 코드 */ }
public class ReportProcessor { /* 데이터 처리 관련 코드 */ }
public class ReportRenderer { /* UI 출력 관련 코드 */ }
3. 개방-폐쇄 원칙 (OCP: Open/Closed Principle)
"소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다."
핵심 개념
- 새로운 기능을 추가할 때 기존 코드를 변경하지 않고 확장할 수 있어야 한다.
- 다형성을 활용하여 구현한다.
적용 예시
잘못된 예시: 새로운 저장소를 추가하려면 기존 코드를 수정해야 한다.
public class MemberService {
private MemberRepository memberRepository = new MemoryMemberRepository();
}
개선된 예시: 인터페이스를 활용하여 확장 가능하도록 변경
public class MemberService {
private MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
이제 MemoryMemberRepository 대신 JdbcMemberRepository를 주입하면 새로운 기능을 추가할 수 있다.
4. 리스코프 치환 원칙 (LSP: Liskov Substitution Principle)
"하위 클래스는 기반 클래스(부모 클래스)의 기능을 깨뜨리지 않으면서 대체 가능해야 한다."
핵심 개념
- 부모 클래스의 규약을 하위 클래스가 준수해야 한다.
- 하위 클래스가 부모 클래스의 동작을 변경하면 예기치 않은 오류가 발생할 수 있다.
적용 예시
잘못된 예시: 원과 사각형이 같은 방식으로 동작하지 않는다.
class Rectangle {
int width, height;
void setWidth(int width) { this.width = width; }
void setHeight(int height) { this.height = height; }
}
class Square extends Rectangle {
void setWidth(int width) { this.width = width; this.height = width; }
void setHeight(int height) { this.width = height; this.height = height; }
}
위 코드는 Rectangle을 사용하는 코드에서 Square로 바꿀 경우 의도한 동작이 깨질 수 있다.
개선된 예시: 별도의 인터페이스 도입
interface Shape { int getArea(); }
class Rectangle implements Shape { /* 구현 */ }
class Square implements Shape { /* 구현 */ }
이렇게 하면 Shape을 사용하는 코드에서 Rectangle과 Square를 안전하게 교체할 수 있다.
5. 인터페이스 분리 원칙 (ISP: Interface Segregation Principle)
"특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다."
핵심 개념
- 하나의 커다란 인터페이스보다 작고 구체적인 인터페이스 여러 개로 분리하는 것이 좋다.
- 클라이언트가 필요하지 않은 기능을 강제로 구현하지 않도록 한다.
적용 예시
잘못된 예시: 범용 인터페이스로 인해 불필요한 메서드를 구현해야 한다.
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() { /* 작업 수행 */ }
public void eat() { /* 로봇은 먹을 수 없음! */ }
}
개선된 예시: 역할에 따라 인터페이스를 분리한다.
interface Workable { void work(); }
interface Eatable { void eat(); }
class Human implements Workable, Eatable {
public void work() { /* 작업 수행 */ }
public void eat() { /* 식사 */ }
}
class Robot implements Workable {
public void work() { /* 작업 수행 */ }
}
6. 의존관계 역전 원칙 (DIP: Dependency Inversion Principle)
"구체적인 구현이 아닌 추상화(인터페이스)에 의존해야 한다."
핵심 개념
- 구체 클래스에 의존하지 않고 인터페이스(추상 클래스)에 의존해야 한다.
- 이를 통해 유연하고 확장 가능한 설계를 할 수 있다.
적용 예시
잘못된 예시: 구현 클래스에 직접 의존
class MemberService {
private MemberRepository repository = new MemoryMemberRepository();
}
개선된 예시: 인터페이스를 활용하여 의존성 주입
class MemberService {
private final MemberRepository repository;
public MemberService(MemberRepository repository) {
this.repository = repository;
}
}
이제 MemoryMemberRepository뿐만 아니라 JdbcMemberRepository도 쉽게 교체할 수 있다.
'코딩 도구 > 백엔드 개발 (Backend Development)' 카테고리의 다른 글
[Spring] Spring Boot DevTools로 HTML 파일 수정 시 서버 재시작 없이 반영하기 (0) | 2025.02.05 |
---|---|
[MySQL] MacOS PID 파일 오류 해결하기 (0) | 2025.01.28 |
MySQL : SELECT 쿼리 결과의 1000개 행 제한을 푸는 방법 (2) | 2024.07.15 |