단일 책임 원칙 SRP(Single Responsibility Principle)
- 하나의 클래스는 한 가지의 책임만 가져야 함
- 클래스가 변경될 때 파급 효과가 작아야 함
예시
- 단일책임 원칙 위반
- User 클래스는 사용자 정보, 로그인기능, 사용자정보를 데이터베이스에 저장하는 내용 3가지의 기능을 모두 수행하고 있음
public class User {
private String name; // 사용자 정보
public void login() { /* 로그인 기능 */ }
public void saveUser() { /* 데이터베이스 저장 기능 */ }
}
- 단일 책임 원칙 적용
- User는 사용자의 정보만 관리하고, 서비스는 로그인 기능을 담당하고, 리포지토리는 데이터베이스에 사용자 정보를 저장하는 기능 담당하는 책임을 가짐
public class User { /* 사용자 정보 관리 */ }
public class AuthService {
public void login(User user) { /* 로그인 기능 */ }
}
public class UserRepository {
public void saveUser(User user) { /* 데이터베이스 저장 */ }
}
개방 폐쇄 원칙 OCP(Open Closed Principle)
- 확장에는 열려 있고, 수정에는 닫혀 있어야 함 = 새로운 기능을 추가할 때 기존 코드의 수정 없이 추가할 수 있어야 함
예시
- 개방 폐쇄 원칙 위반
- 새로운 도형이 추가 될 때마다, AreaCalculator 클래스의 calculate 메서드 내부를 if문을 사용하여 수정해야 함
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 /* 사각형의 넓이 계산 */;
}
}
}
- 개방 폐쇄 원칙 적용
- 새로운 도형이 추가되어도, Shape인터페이스를 상속하여 이를 구현하면 되기 때문에 AreaCalculator를 수정할 필요가 없음
- 다형성을 사용 = 역할(도형)과 구현(원,삼각형)을 분리한 것
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();
}
}
리스코프 치환 원칙 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 Car();
car.accelerate(); // "자동차가 가속합니다."
Car electricCar = new ElectricCar();
electricCar.accelerate(); // UnsupportedOperationException 발생
}
}
- 리스코프 치환 원칙 적용
- 가속 기능(역할)을 인터페이스로 분리
interface Acceleratable {
void accelerate();
}
class Car 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 Car();
car.accelerate(); // "내연기관 자동차가 가속합니다."
Acceleratable electricCar = new ElectricCar();
electricCar.accelerate(); // "전기차가 배터리로 가속합니다."
}
}
인터페이스 분리 원칙 ISP(Interface Segregation Principle)
- 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 함
예시
- 인터페이스 분리 원칙 위반
- Dog은 사용하지 않는 fly 메서드를 구현해야 함
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() { /* 수영 */ }
}
의존관계 역전 원칙 DIP(Dependency Inversion Principle)
- 구체적인 클래스에 의존하지 말고 인터페이스나 추상 클래스에 의존해야 함
예시
- 의존관계 역전 원칙 위반
- SMS 알림과 같은 기능이 추가되면 NotificationService 는 수정되어야 함
// Email 알림 클래스
class EmailNotifier {
public void sendEmail(String message) {
System.out.println("Email 알림: " + message);
}
}
// 알림 시스템
class NotificationService {
private EmailNotifier emailNotifier;
public NotificationService() {
// 구체적인 클래스인 EmailNotifier에 의존
this.emailNotifier = new EmailNotifier();
}
public void sendNotification(String message) {
emailNotifier.sendEmail(message);
}
}
public class Main {
public static void main(String[] args) {
NotificationService service = new NotificationService();
service.sendNotification("안녕하세요! 이메일 알림입니다.");
}
}
- 적용
// 알림 인터페이스(추상화)
interface Notifier {
void send(String message);
}
// Email 알림 클래스
class EmailNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("Email 알림: " + message);
}
}
// SMS 알림 클래스
class SMSNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("SMS 알림: " + message);
}
}
// 알림 서비스 (높은 수준 모듈)
class NotificationService {
// 추상화된 인터페이스에 의존
private Notifier notifier;
// 의존성 주입 (생성자를 통해 주입)
public NotificationService(Notifier notifier) {
this.notifier = notifier;
}
public void sendNotification(String message) {
// notifier가 어떤 구현체인지 상관하지 않음
notifier.send(message);
}
}
public class Main {
public static void main(String[] args) {
// Email 알림을 사용
Notifier emailNotifier = new EmailNotifier();
NotificationService emailService = new NotificationService(emailNotifier);
emailService.sendNotification("안녕하세요! 이메일 알림입니다.");
// SMS 알림을 사용
Notifier smsNotifier = new SMSNotifier();
NotificationService smsService = new NotificationService(smsNotifier);
smsService.sendNotification("안녕하세요! SMS 알림입니다.");
}
}
다형성의 한계 : OCP, DIP의 위반과 Spring
OCP 위반 (수정없이 기능을 확장)
- 구현 객체를 변경하기 위해서는 해당 코드를 사용하는 클라이언트측의 코드를 변경해야 함
// Circle을 계산하는 경우
public class Main {
public static void main(String[]) {
AreaCalculator areaCalculator = new AreaCalculator();
Circle circle = new Circle();
areaCalculator.calculate(circle);
}
}
// Square를 계산하는 경우
public class Main {
public static void main(String[]) {
AreaCalculator areaCalculator = new AreaCalculator();
// Circle circle = new Circle();
Square square = new Square();
areaCalculator.calculate(square);
}
}
DIP 위반 (구체화 말고 추상화에 의존)
- 클라리언트가 OrderService(=인터페이스)를 의존하고 있지만, RateDiscountPolicy라는 구현클래스에도 의존하고 있으므로 위반
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
⭐️ 결론 : Spring => OCP, DIP를 IOC, DI를 통해 가능하도록 만들어준다. ⭐️
'Spring' 카테고리의 다른 글
Spring 의존관계 주입 방법 : Constructor, Setter, Field (0) | 2025.02.06 |
---|---|
Spring Container와 Spring Bean (1) | 2025.02.05 |
연관관계 설정, 페이징, @ExceptionHandler를 적용한 일정 관리 서버 리팩토링 (1) | 2025.02.04 |
JDBC Templete을 이용한 일정 관리 서버 CRUD 구현 (3) | 2025.01.27 |
일정 관리 서버 만들기 설계 (API 명세서, ERD 작성) (0) | 2025.01.24 |