객체 지향 설계 원칙: SOLID
객체 지향 설계에서 SOLID 원칙은 유지보수성과 확장성이 높은 소프트웨어를 개발하기 위해 지켜야 할 다섯 가지 기본 원칙을 의미합니다. 이 글에서는 SOLID의 각 원칙을 자세히 설명하고, 이러한 원칙이 왜 중요한지 살펴보겠습니다.
SOLID란 무엇인가?
SOLID는 객체 지향 설계의 다섯 가지 원칙을 정의한 약어로, 다음과 같은 원칙들로 구성됩니다:
- S: 단일 책임 원칙 (Single Responsibility Principle)
- O: 개방-폐쇄 원칙 (Open/Closed Principle)
- L: 리스코프 치환 원칙 (Liskov Substitution Principle)
- I: 인터페이스 분리 원칙 (Interface Segregation Principle)
- D: 의존성 역전 원칙 (Dependency Inversion Principle)
이 원칙들은 소프트웨어를 더 유연하고 유지보수 가능하게 만들어줍니다.
1. 단일 책임 원칙 (SRP)
클래스는 단 하나의 책임만 가져야 한다.
설명:
클래스는 하나의 역할만 가져야 하며, 변경의 이유가 하나여야 합니다. 여러 책임이 한 클래스에 섞이면 수정이 어려워지고, 코드 재사용성이 떨어집니다.
예제:
class ReportGenerator {
public void generateReport() {
// 보고서 생성 로직
}
public void printReport() {
// 보고서 출력 로직
}
}
위 클래스는 보고서 생성과 출력 두 가지 책임을 가지고 있습니다. 이를 분리하면:
class ReportGenerator {
public void generateReport() {
// 보고서 생성 로직
}
}
class ReportPrinter {
public void printReport() {
// 보고서 출력 로직
}
}
장점:
- 클래스가 더 간결해짐.
- 특정 책임의 변경이 다른 부분에 영향을 미치지 않음.
2. 개방-폐쇄 원칙 (OCP)
클래스는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 한다.
설명:
새로운 기능이 추가될 때 기존 코드를 수정하지 않고 확장할 수 있어야 합니다. 이를 통해 기존 코드의 안정성을 보장할 수 있습니다.
예제:
기존 코드:
class Notification {
public void send(String type) {
if (type.equals("Email")) {
System.out.println("Send Email");
} else if (type.equals("SMS")) {
System.out.println("Send SMS");
}
}
}
OCP를 적용:
interface Notification {
void send();
}
class EmailNotification implements Notification {
@Override
public void send() {
System.out.println("Send Email");
}
}
class SMSNotification implements Notification {
@Override
public void send() {
System.out.println("Send SMS");
}
}
장점:
- 새로운 알림 타입 추가 시 기존 코드를 수정할 필요 없음.
3. 리스코프 치환 원칙 (LSP)
하위 클래스는 상위 클래스에서 기대하는 동작을 모두 수행할 수 있어야 한다.
설명:
상위 클래스의 객체를 하위 클래스 객체로 대체해도 프로그램의 동작에 문제가 없어야 합니다.
예제:
위반 사례:
class Rectangle {
private int width;
private int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setWidth(height);
super.setHeight(height);
}
}
위 코드에서는 사각형(Rectangle) 객체를 기대하는 코드가 정사각형(Square) 객체에서 의도치 않은 동작을 경험할 수 있습니다.
해결:
직사각형과 정사각형을 별도 클래스로 설계.
4. 인터페이스 분리 원칙 (ISP)
클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
설명:
하나의 거대한 인터페이스 대신, 더 작고 구체적인 인터페이스로 분리해야 합니다.
예제:
위반 사례:
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
@Override
public void work() {
System.out.println("Robot working");
}
@Override
public void eat() {
// 로봇은 먹지 않음
}
}
ISP 적용:
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Robot implements Workable {
@Override
public void work() {
System.out.println("Robot working");
}
}
장점:
- 불필요한 메서드 의존 제거.
5. 의존성 역전 원칙 (DIP)
고수준 모듈은 저수준 모듈에 의존해서는 안 된다. 둘 다 추상화에 의존해야 한다.
설명:
구체적인 구현보다 인터페이스와 같은 추상화에 의존하도록 설계해야 합니다.
예제:
위반 사례:
class Keyboard {}
class Monitor {}
class Computer {
private Keyboard keyboard;
private Monitor monitor;
public Computer() {
this.keyboard = new Keyboard();
this.monitor = new Monitor();
}
}
DIP 적용:
interface InputDevice {}
class Keyboard implements InputDevice {}
interface OutputDevice {}
class Monitor implements OutputDevice {}
class Computer {
private InputDevice inputDevice;
private OutputDevice outputDevice;
public Computer(InputDevice inputDevice, OutputDevice outputDevice) {
this.inputDevice = inputDevice;
this.outputDevice = outputDevice;
}
}
장점:
- 의존성 주입을 통해 유연성 증가.
SOLID 원칙이 필요한 이유
- 유지보수성: 코드를 더 쉽게 수정하고 확장할 수 있습니다.
- 재사용성: 모듈화된 설계를 통해 코드 재사용이 가능.
- 확장성: 요구사항 변경에 유연하게 대응.
- 가독성: 명확하고 직관적인 코드 작성.
SOLID 원칙을 준수하면, 변화에 강하고 확장 가능한 소프트웨어를 설계할 수 있습니다. 이를 실천함으로써 소프트웨어 개발의 품질을 한 단계 높여보세요!
'개발 부트캠프 > 백엔드' 카테고리의 다른 글
[Java] 계층형 아키텍처 패턴 (Layered Architecture Pattern) (0) | 2025.01.07 |
---|---|
[Java] MVC 패턴 (1) | 2025.01.07 |
[Java] JDBC (Java Database Connectivity) (0) | 2025.01.07 |
[Java] 쓰레드(Thread) (0) | 2025.01.06 |
[Java] Collection(List / Set / Map) (0) | 2025.01.06 |