SOLID 원칙이란?
시간이 지나도 유지보수와 확장이 쉬운, 이해하기 편하고 유연한 소프트웨어를 만들기 위한 다섯 가지 객체 지향 설계 원칙의 모음
Single Responsibility Principle(단일 책임 원칙)
클래스는 단 하나의 책임만을 가져야 한다.
▼잘못된 코드
더보기
// 너무 많은 책임을 가진 클래스
public class Employee {
private String name;
private double salary;
// 책임 1: 직원 정보 관리
public void setName(String name) {
this.name = name;
}
// 책임 2: 급여 계산
public double calculatePay() {
return salary * 1.1;
}
// 책임 3: 데이터베이스 저장
public void saveToDatabase() {
// DB 저장 로직
System.out.println("Saving to database...");
}
// 책임 4: 리포트 생성
public void generateReport() {
System.out.println("Generating report...");
}
}
▼올바른 코드
더보기
// 직원 정보만 관리
public class Employee {
private String name;
private double salary;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSalary() { return salary; }
}
// 급여 계산 담당
public class PayrollCalculator {
public double calculatePay(Employee employee) {
return employee.getSalary() * 1.1;
}
}
// 데이터베이스 관리 담당
public class EmployeeRepository {
public void save(Employee employee) {
System.out.println("Saving " + employee.getName() + " to database");
}
}
// 리포트 생성 담당
public class ReportGenerator {
public void generateReport(Employee employee) {
System.out.println("Generating report for " + employee.getName());
}
}
Open/Closed Principle(개방/폐쇄 원칙)
클래스는 확장에는 열려있고, 수정에는 닫혀있어야 한다
▼잘못된 코드
더보기
public class DiscountCalculator {
public double calculateDiscount(String customerType, double amount) {
if (customerType.equals("REGULAR")) {
return amount * 0.05;
} else if (customerType.equals("VIP")) {
return amount * 0.15;
} else if (customerType.equals("PREMIUM")) { // 새 타입 추가시 코드 수정 필요
return amount * 0.20;
}
return 0;
}
}
▼올바른 코드
더보기
// 추상화를 통한 확장 가능한 설계
public interface DiscountStrategy {
double calculateDiscount(double amount);
}
public class RegularCustomerDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.05;
}
}
public class VIPCustomerDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.15;
}
}
// 새로운 할인 정책 추가시 기존 코드 수정 없이 확장 가능
public class PremiumCustomerDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.20;
}
}
public class DiscountCalculator {
public double calculateDiscount(DiscountStrategy strategy, double amount) {
return strategy.calculateDiscount(amount);
}
}
Liskov Substitution Principle(리스코프 치환 원칙)
자식 클래스는 부모 클래스를 대체할 수 있어야 한다
▼잘못된 코드
더보기
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
// 정사각형은 가로와 세로가 같아야 하는데...
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // 높이도 같이 변경
}
@Override
public void setHeight(int height) {
this.width = height; // 너비도 같이 변경
this.height = height;
}
}
// 문제 상황
public class TestLSP {
public static void testRectangle(Rectangle rect) {
rect.setWidth(5);
rect.setHeight(10);
// Rectangle이라면 50이어야 하지만, Square라면 100이 됨
System.out.println("Area: " + rect.getArea());
}
}
▼올바른 코드
더보기
// 공통 인터페이스 사용
public interface Shape {
int getArea();
}
public class Rectangle implements Shape {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public int getArea() {
return width * height;
}
}
public class Square implements Shape {
private int side;
public Square(int side) {
this.side = side;
}
@Override
public int getArea() {
return side * side;
}
}
Interface Segregation Principle(인터페이스 분리 원칙)
클라이언트는 자신이 사용하지 않는 메서드에 의존하면 안 된다
- 여기서 말하는 클라이언트는 이미 구현된 코드를 사용하는 코드를 뜻함.
▼잘못된 코드
더보기
// 너무 많은 기능을 가진 인터페이스
public interface Worker {
void work();
void eat();
void sleep();
void receiveSalary();
}
// 로봇은 먹거나 자지 않는데도 구현해야 함
public class Robot implements Worker {
@Override
public void work() {
System.out.println("Robot working");
}
@Override
public void eat() {
// 로봇은 먹지 않음 - 불필요한 구현
throw new UnsupportedOperationException();
}
@Override
public void sleep() {
// 로봇은 자지 않음 - 불필요한 구현
throw new UnsupportedOperationException();
}
@Override
public void receiveSalary() {
// 로봇은 급여를 받지 않음
throw new UnsupportedOperationException();
}
}
▼올바른 코드
더보기
// 인터페이스를 작은 단위로 분리
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public interface Sleepable {
void sleep();
}
public interface Payable {
void receiveSalary();
}
// 필요한 인터페이스만 구현
public class Human implements Workable, Eatable, Sleepable, Payable {
@Override
public void work() {
System.out.println("Human working");
}
@Override
public void eat() {
System.out.println("Human eating");
}
@Override
public void sleep() {
System.out.println("Human sleeping");
}
@Override
public void receiveSalary() {
System.out.println("Human receiving salary");
}
}
public class Robot implements Workable {
@Override
public void work() {
System.out.println("Robot working");
}
// 불필요한 메서드 구현 없음!
}
Dependency Inversion Principle(의존 역전 원칙)
고수준 모듈은 저수준 모듈에 의존하면 안 된다. 둘 다 추상화에 의존해야 한다
▼잘못된 코드
더보기
// 저수준 클래스 (구체적인 구현)
public class EmailSender {
public void sendEmail(String message) {
System.out.println("Sending email: " + message);
}
}
// 고수준 클래스가 저수준 클래스에 직접 의존
public class NotificationService {
private EmailSender emailSender = new EmailSender();
public void notify(String message) {
emailSender.sendEmail(message);
// SMS로 바꾸려면? 코드를 수정해야 함!
}
}
▼올바른 코드
더보기
// 추상화 (인터페이스)
public interface MessageSender {
void sendMessage(String message);
}
// 저수준 클래스들이 인터페이스 구현
public class EmailSender implements MessageSender {
@Override
public void sendMessage(String message) {
System.out.println("Sending email: " + message);
}
}
public class SMSSender implements MessageSender {
@Override
public void sendMessage(String message) {
System.out.println("Sending SMS: " + message);
}
}
// 고수준 클래스는 추상화에 의존
public class NotificationService {
private MessageSender messageSender;
// 의존성 주입을 통해 유연성 확보
public NotificationService(MessageSender messageSender) {
this.messageSender = messageSender;
}
public void notify(String message) {
messageSender.sendMessage(message);
}
}
// 사용 예제
public class Main {
public static void main(String[] args) {
// 이메일로 알림
NotificationService emailNotification =
new NotificationService(new EmailSender());
emailNotification.notify("Hello via Email");
// SMS로 알림 - 코드 변경 없이 교체 가능!
NotificationService smsNotification =
new NotificationService(new SMSSender());
smsNotification.notify("Hello via SMS");
}
}
'Java > 객체 지향' 카테고리의 다른 글
| Enum의 객체 지향적 활용 방법 (0) | 2026.07.21 |
|---|---|
| 정적 팩토리 메서드 (0) | 2026.06.15 |
| 상속(Inheritance) VS 합성(Composition) (0) | 2026.06.15 |
| 방어적 복사 (0) | 2026.06.15 |
| 값 객체(Value Object)와 불변성 (0) | 2026.06.12 |