SOLID 원칙은 객체 지향 프로그래밍에서 유지보수성과 확장성을 높이기 위한 5가지 설계 원칙이다.
이 원칙을 잘 따르면 코드 변경에 유연하고, 재사용성이 높으며, 유지보수가 쉬운 코드를 만들 수 있다.

1. 단일 책임 원칙 (SRP - Single Responsibility Principle)
📌 한 클래스는 하나의 책임만 가져야 한다.
- 하나의 클래스는 하나의 기능(역할)에만 집중해야 하며, 다른 기능과 섞이지 않아야 한다.
❌ 위반 예시
아래 User 클래스는 사용자 정보 관리, 로그인 처리, 데이터 저장까지 여러 책임을 가지고 있다.
public class User {
private String name; // 사용자 정보
public void login() { /* 로그인 기능 */ }
public void saveUser() { /* 데이터 저장 기능 */ }
}
위처럼 하나의 클래스가 여러 역할을 하면, 유지보수가 어려워진다.
✅ 단일 책임 원칙 적용
각 기능을 별도의 클래스로 분리하여 하나의 클래스가 하나의 역할만 수행하도록 만든다.
// 사용자 정보 관리
public class User {
private String name;
}
// 로그인 기능 담당
public class AuthService {
public void login(User user) { /* 로그인 처리 */ }
}
// 데이터 저장 담당
public class UserRepository {
public void saveUser(User user) { /* 데이터 저장 */ }
}
- 코드 수정이 용이함 (로그인 변경 시 AuthService만 수정하면 됨)
- 여러 기능이 엉켜 있는 복잡한 클래스를 방지할 수 있음
2. 개방-폐쇄 원칙 (OCP - Open/Closed Principle)
📌 소프트웨어 요소는 "확장"에는 열려 있어야 하고, "수정"에는 닫혀 있어야 한다.
- 새로운 기능을 추가할 때 기존 코드를 수정하지 않고, 확장할 수 있도록 설계해야 한다.
❌ 위반 예시
새로운 도형이 추가될 때마다 AreaCalculator 클래스를 수정해야 한다.
public class Shape {
public String type;
}
public class AreaCalculator {
public double calculate(Shape shape) {
if (shape.type.equals("circle")) {
return /* 원의 넓이 계산 */;
} else if (shape.type.equals("square")) {
return /* 사각형의 넓이 계산 */;
}
}
}
✅ 개방-폐쇄 원칙 적용
인터페이스를 활용하여 확장(새로운 도형 추가)은 쉽게 하고, 기존 코드 수정은 최소화한다.
public interface Shape {
double calculateArea();
}
public class Circle implements Shape {
public double calculateArea() { return /* 원의 넓이 계산 */; }
}
public class Square implements Shape {
public double calculateArea() { return /* 사각형의 넓이 계산 */; }
}
public class AreaCalculator {
public double calculate(Shape shape) {
return shape.calculateArea();
}
}
- 새로운 도형을 추가해도 기존 AreaCalculator는 변경할 필요 없음
- 다형성을 활용하여 유연한 확장 가능
3. 리스코프 치환 원칙 (LSP - Liskov Substitution Principle)
📌 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.
- 부모 클래스를 사용하는 곳에서 자식 클래스를 넣어도 문제없이 동작해야 한다.
❌ 위반 예시
전기차(ElectricCar)는 Car 클래스를 상속받았지만, accelerate() 메서드를 사용할 수 없다.
class Car {
public void accelerate() {
System.out.println("자동차가 휘발유로 가속합니다.");
}
}
class ElectricCar extends Car {
@Override
public void accelerate() {
throw new UnsupportedOperationException("전기차는 이 방식으로 가속하지 않습니다.");
}
}
public class Main {
public static void main(String[] args) {
Car car = new ElectricCar();
car.accelerate(); // 오류 발생 (LSP 위반)
}
}
부모 클래스를 대체(Car car = new ElectricCar();)했을 때 오류가 발생하면 LSP 위반이다.
✅ 리스코프 치환 원칙 적용
가속 기능을 인터페이스로 분리하여 각 자동차가 올바르게 동작하도록 수정한다.
// 가속 기능 인터페이스
interface Acceleratable {
void accelerate();
}
class GasolineCar implements Acceleratable {
@Override
public void accelerate() {
System.out.println("내연기관 자동차가 가속합니다.");
}
}
class ElectricCar implements Acceleratable {
@Override
public void accelerate() {
System.out.println("전기차가 배터리로 가속합니다.");
}
}
public class Main {
public static void main(String[] args) {
Acceleratable car = new ElectricCar();
car.accelerate(); // "전기차가 배터리로 가속합니다." (오류 없음)
}
}
- 부모 클래스를 대체해도 코드가 정상적으로 동작함
- 인터페이스를 활용해 역할을 분리할 수 있음
4. 인터페이스 분리 원칙 (ISP - Interface Segregation Principle)
📌 클라이언트는 자신이 사용하지 않는 메서드에 의존하면 안 된다.
- 큰 인터페이스 하나보다는 여러 개의 작은 인터페이스로 분리해야 한다.
❌ 위반 예시
Dog 클래스는 fly() 메서드를 사용하지 않지만, Animal 인터페이스를 구현해야 한다.
public interface Animal {
void fly();
void run();
void swim();
}
public class Dog implements Animal {
public void fly() { /* 사용하지 않음 */ }
public void run() { /* 달리기 */ }
public void swim() { /* 수영 */ }
}
✅ 인터페이스 분리 원칙 적용
기능별로 인터페이스를 분리한다.
public interface Runnable {
void run();
}
public interface Swimmable {
void swim();
}
public class Dog implements Runnable, Swimmable {
public void run() { /* 달리기 */ }
public void swim() { /* 수영 */ }
}
- 인터페이스가 명확해지고 불필요한 메서드 구현이 필요 없음
5. 의존관계 역전 원칙 (DIP - Dependency Inversion Principle)
📌 구체적인 클래스에 의존하지 말고, 인터페이스나 추상 클래스에 의존하는 것
❌ 위반 예시
NotificationService가 EmailNotifier에 직접 의존하고 있다.
class EmailNotifier {
public void sendEmail(String message) { /* 이메일 전송 */ }
}
class NotificationService {
private EmailNotifier emailNotifier = new EmailNotifier();
public void sendNotification(String message) {
emailNotifier.sendEmail(message);
}
}
✅ 의존관계 역전 원칙 적용
인터페이스를 사용하여 유연한 구조로 변경한다.
interface Notifier {
void send(String message);
}
class EmailNotifier implements Notifier {
public void send(String message) { /* 이메일 전송 */ }
}
class NotificationService {
private Notifier notifier;
public NotificationService(Notifier notifier) {
this.notifier = notifier;
}
public void sendNotification(String message) {
notifier.send(message);
}
}
- SMSNotifier, PushNotifier 등 다른 구현체로 쉽게 변경 가능
'Spring' 카테고리의 다른 글
Spring - REST API 기반 일정 관리 서버 시스템2 (0) | 2025.04.04 |
---|---|
Spring Container와 Spring Bean (0) | 2025.03.27 |
REST API 기반 일정 관리 서버 시스템 구축하기(트러블 슈팅) (3) | 2025.03.25 |
JDBC (0) | 2025.03.24 |
롬복(Lombok)이란? (0) | 2025.03.21 |