SOLID란?
- 객체지향 설계 원칙의 약어
- Single Responsibility Principle (SRP) - 단일 책임 원칙
- Open/Closed Principle(OCP) - 개방/폐쇄 원칙
- Liskov Substitution Principle(LSP) - 리스코프 치환 원칙
- Interface Segregation Principle(ISP) - 인터페이스 분리 원칙
- Dependency Inversion Principle(DIP) - 의존성 역전 원칙
Single Responsibility Principle (SRP) - 단일 책임 원칙
- 정의 : 클래스는 하나의 책임만 가져야 하며, 변경의 이유가 하나뿐이어야 한다.
- 목적 : 코드의 가독성, 유지보수성, 테스트 용이성을 높임
- Spring에서의 구현
- 각 클래스가 특정 역할만 수행하도록 설계
- 예: UserService를 UserRegistrationService, AuthenticationService, AuthorizationService로 분리.
@Service
public class UserRegistrationService {
@Autowired
private UserRepository userRepository;
public void registerUser(User user) {
// 사용자 등록 로직
}
}
@Service
public class AuthenticationService {
@Autowired
private UserRepository userRepository;
public boolean authenticate(String username, String password) {
// 인증 로직
return true;
}
}
Open/Closed Principle(OCP) - 개방/폐쇄 원칙
- 정의 : 소프트웨어 엔티티(클래스, 모듈 등)는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다.
- 목적 : 기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있도록 설계
- Spring에서의 구현
- 인터페이스와 의존성 주입(DI)을 활용하여 확장성을 높임
- 예: 결제 방식을 확장 가능한 구조로 설계
public interface PaymentStrategy {
void processPayment(Order order);
}
@Component
public class CreditCardPaymentStrategy implements PaymentStrategy {
@Override
public void processPayment(Order order) {
// 신용카드 결제 로직
}
}
@Component
public class PayPalPaymentStrategy implements PaymentStrategy {
@Override
public void processPayment(Order order) {
// PayPal 결제 로직
}
}
@Service
public class OrderService {
@Autowired
private PaymentStrategy paymentStrategy;
public void placeOrder(Order order) {
paymentStrategy.processPayment(order);
}
}
Liskov Substitution Principle(LSP) - 리스코프 치환 원칙
- 정의 : 서브타입은 언제나 자신의 기반 타입으로 대체될 수 있어야 한다.
- 목적 : 상속 관계에서 프로그램의 정확성을 유지
- Spring에서의 구현
- 부모 클래스나 인터페이스를 사용하는 코드가 자식 클래스에서도 문제없이 동작하도록 설계
- 예: 동물 클래스와 이를 상속받는 구체적인 동물 클래스
public interface Bird {
void fly();
}
@Component
public class Sparrow implements Bird {
@Override
public void fly() {
System.out.println("참새가 날아갑니다.");
}
}
@Component
public class Ostrich implements Bird { // LSP 위반 (타조는 날 수 없음)
@Override
public void fly() {
throw new UnsupportedOperationException("타조는 날 수 없습니다.");
}
}
Interface Segregation Principle(ISP) - 인터페이스 분리 원칙
- 정의 : 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
- 목적 : 인터페이스를 작고 구체적으로 설계하여 불필요한 의존성을 제거
- Spring에서의 구현
- 큰 인터페이스를 여러 개로 나누어 필요한 기능한 구현하도록 만든다.
// Before ISP (큰 인터페이스)
public interface MessagingService {
void sendEmail();
void sendSMS();
}
// After ISP (작은 인터페이스)
public interface EmailService {
void sendEmail();
}
public interface SMSService {
void sendSMS();
}
@Service
public class NotificationService implements EmailService, SMSService {
@Override
public void sendEmail() {
// 이메일 발송 로직
}
@Override
public void sendSMS() {
// SMS 발송 로직
}
}
Dependency Inversion Principle(DIP) - 의존성 역전 원칙
- 정의 : 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야한다.
- 목적 : 코드의 유연성과 테스트 용이성을 높인다.
- Spring에서의 구현
- 의존성 주입(DI)을 통해 구체적인 구현 대신 추상화에 의존하도록 설계
public interface NotificationSender {
void send(String message);
}
@Component
public class EmailNotificationSender implements NotificationSender {
@Override
public void send(String message) {
System.out.println("Email sent: " + message);
}
}
@Component
public class SMSNotificationSender implements NotificationSender {
@Override
public void send(String message) {
System.out.println("SMS sent: " + message);
}
}
@Service
public class NotificationService {
private final NotificationSender notificationSender;
@Autowired
public NotificationService(NotificationSender notificationSender) {
this.notificationSender = notificationSender;
}
public void notify(String message) {
notificationSender.send(message);
}
}
SOLID Principles 적용 시 이점
- 유지보수성(Maintainability): 코드가 명확하고 변경이 용이함.
- 확장성(Extensibility): 새로운 기능 추가 시 기존 코드를 수정하지 않아도 됨.
- 재사용성(Reusability): 독립적인 컴포넌트를 여러 곳에서 재사용 가능.
- 테스트 용이성(Testability): 각 컴포넌트가 독립적이라 단위 테스트 작성이 쉬움.