본문 바로가기
Java

객체지향 프로그래밍 - 객체 지향 설계 원칙

by KongJiHoon 2024. 5. 30.

SOLID

1. 단일 책임 원칙(SRP; Single Responsibility Principle)

  • 클래스는 단 하나의 책임만을 가져야 한다. 즉 클래스는 변경의 이유가 하나뿐이어야함.
  • 예) 'User' 클래스 => 클래스는 사용자 정보를 관리하는 책임만 가져야 한다. 이메일을 보내는 기능은 'EmailService'와 같은 다른 클래스로 분리시켜야 한다.
public class User {
    private String name;
    private String email;

    // 생성자, getter, setter 등
}

public class EmailService {
    public void sendEmail(User user, String message) {
        // 이메일 전송 로직
    }
}

 

2. 개방 폐쇄 원칙(OCP; Open Close Principle)

  • 소프트웨어 개체는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 한다.
  • 즉, 기존 코드를 수정하지 않고도 기능을 확장할 수 있어야 한다.
  • 예) 새로운 할인 정책을 추가할 때 기존 'Discount' 클래스를 수정하지 않고, 상속을 통해 확장.
public abstract class Discount {
    public abstract double applyDiscount(double price);
}

public class ChristmasDiscount extends Discount {
    @Override
    public double applyDiscount(double price) {
        return price * 0.9; // 10% 할인
    }
}

public class NewYearDiscount extends Discount {
    @Override
    public double applyDiscount(double price) {
        return price * 0.8; // 20% 할인
    }
}

 

3. 리스코프 치환의 원칙(LSP; Liscov Substitution Principle)

  • 서브타입(상속받은 하위 클래스)는 언제나 자신의 기반 타입(상위 클래스)로 교체될 수 있어야 한다.
  • 이는 프로그램의 정확성을 해치지 않고, 객체는 부모 클래스의 인터페이스를 그대로 사용할 수 있어야 함을 의미.
  • 예) 'Bird' 클래스를 상속하는 'Sparrow' 클래스는 'Bird' 클래스로 대체해도 동일하게 동작해야한다.
public class Bird {
    public void fly() {
        System.out.println("Flying...");
    }
}

public class Sparrow extends Bird {
    @Override
    public void fly() {
        System.out.println("Sparrow flying...");
    }
}

public void letBirdFly(Bird bird) {
    bird.fly();
}

public static void main(String[] args) {
    Bird bird = new Sparrow();
    letBirdFly(bird); // "Sparrow flying..." 출력
}

 

4. 인터페이스 분리의 원칙(ISP; Interface Segregation Principle)

  • 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. 즉, 인터페이스는 구체적인 사용 사례에 맞게 분리되어야 한다.
  • 예) 'Printer' 인터페이스가 'Print'와 'Scan'기능을 모두 포함한다면, 두 인터페이스를 분리하여 각각 필요한 기능만 구현
public interface Printer {
    void print(Document document);
}

public interface Scanner {
    void scan(Document document);
}

public class MultiFunctionPrinter implements Printer, Scanner {
    @Override
    public void print(Document document) {
        // 인쇄 로직
    }

    @Override
    public void scan(Document document) {
        // 스캔 로직
    }
}

public class SimplePrinter implements Printer {
    @Override
    public void print(Document document) {
        // 인쇄 로직
    }
}

 

5. 의존성 역전 원칙(DIP; Dependency Inversion Principle)

  • 실제 사용 관계는 바뀌지 않으며, 추상을 매개로 메시지를 주고받음으로써 관계를 최대한 느슨하게 만드는 원칙
  • 고수준 모듈은 저수준 모듈에 의존해서는 안된다. 둘 다 추상화에 의존. 추상화는 구체적인 사항에 의존해서는 안된다.
  • 구체적인 사항이 추상화에 의존
  • 예) 'UserService'가 직접 'MySQLDatabase'와 같은 구체적인 데이터베이스 구현에 의존하지 않고, 'Database' 인터페이스에 의존하도록 한다.
public interface Database {
    void save(User user);
}

public class MySQLDatabase implements Database {
    @Override
    public void save(User user) {
        // MySQL 저장 로직
    }
}

public class UserService {
    private Database database;

    public UserService(Database database) {
        this.database = database;
    }

    public void saveUser(User user) {
        database.save(user);
    }
}

public class Main {
    public static void main(String[] args) {
        Database database = new MySQLDatabase();
        UserService userService = new UserService(database);

        User user = new User("John Doe");
        userService.saveUser(user);
    }
}