본문 바로가기
Java/객체 지향

SOLID 원칙

by 정구정구 2026. 7. 14.

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