Java

S.O.L.I.D 원칙

ryeonng 2024. 9. 25. 14:37

 

S.O.L.I.D 원칙

로버트 C. 마틴(Robert C. Martin), 흔히 "아저씨 보브(Uncle Bob)"로 알려진 소프트웨어 엔지니어가 발표한 객체 지향 프로그래밍 설계 원칙이다. 즉, SOLID 원칙이란 객체지향 설계의 5가지 중요한 원칙을 뜻하며, 유지보수성과 확장성을 높이기 위해 설계 과정에서 따르는 지침이다.

 

사전 기반 지식

  • 객체지향 프로그래밍의 기본 개념 (클래스, 객체, 상속, 다형성 등)
  • Java에서 인터페이스와 상속 사용 방법
  • 접근 제한자, 메서드 오버라이딩, 클래스 간 관계 등의 기본 개념

 

단일 책임 원칙 (Single Responsibility Principle, SRP)

  • 설명 : 클래스는 하나의 책임만 가져야 한다. 하나의 책임이란, 클래스가 변경되어야 하는 이유가 하나뿐이어야 한다는 의미이다.
class User {
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
}

// 이 클래스는 사용자 관리만 책임져야 함. 예를 들어,
// 데이터베이스 저장 로직이 포함되면 단일 책임을 위반.
class UserService {
    public void save(User user) {
        // save logic
    }
}

 

잘못된 예 (SRP 위반)

class UserService {
    private DBConnection dbConnection; // !! 멤버변수로 DBConnection를 직접 userService클래스에서 생성

    public void save(User user) {
        dbConnection = new DBConnection(); // DB 연결을 직접 처리
        dbConnection.save(user); // 데이터 저장 로직도 직접 처리
    }
}

SRP를 준수하기 위해서는, 데이터베이스와의 상호작용을 다른 클래스로 분리하고, UserService는 그 클래스를 통해 상호작용해야 한다. 이렇게 하면 사용자 관리와 데이터 저장의 책임이 분리된다.

 

올바른 예 (SRP 준수)

// DB 관련 로직은 별도의 클래스에서 담당
class UserRepository {
    private DBConnection dbConnection;

    public UserRepository(DBConnection dbConnection) {
        this.dbConnection = dbConnection;
    }

    public void save(User user) {
        dbConnection.save(user);
    }
}

// 사용자 관리만 담당하는 UserService 클래스
class UserService {
    private UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void saveUser(User user) {
        userRepository.save(user); // UserService는 저장을 위임만 함
    }
}

개방-폐쇄 원칙 (Open-Closed Principle, OCP)

  • 설명 : 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
interface Shape {
    double area();
}

class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }
    
		// 오버라이드 
    public double area() {
        return Math.PI * radius * radius;
    }
}

class Rectangle implements Shape {
    private double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
		
		// 오버라이드 
    public double area() {
        return width * height;
    }
}

class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.area();
    }
}

리스코프 치환 원칙 (Liskov Substitution Principle, LSP)

  • 설명 : 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다. 즉, 자식 클래스가 부모 클래스의 행동을 변경해서는 안된다는 원칙이다.
class Bird {
    public void fly() {
        System.out.println("새는 날아다닐 수 있어요");
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("펭귄은 못 날아요");
    }
}

Penguin은 Bird 클래스를 상속했지만 fly() 메서드를 정상적으로 구현하지 못하므로 LSP를 위반한다.

 

올바른 예시 (LSP 준수)

해결책은 Bird 클래스에 공통 동작으로 날 수 있는 기능을 넣는 대신, 새의 특성에 따라 상속 구조를 분리하거나, 날 수 있는지 여부를 결정할 수 있는 더 적절한 설계를 도입하는 것이다.

interface Flyable {
    void fly();
}

class Bird {
    public void eat() {
        System.out.println("Bird is eating");
    }
}

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

class Penguin extends Bird {
    // Penguin 클래스는 Flyable을 구현하지 않음, 즉 fly() 메서드가 없음
}

인터페이스 분리 원칙 (Interface Segregation Principle, ISP)

  • 설명 : 클라이언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다. 즉, 큰 인터페이스보다 구체적이고 작은 인터페이스로 나누는 것이 좋다.
interface Worker {
    void work();
    void eat();
}

// 잘못된 예: 한 인터페이스에 너무 많은 역할을 포함
class Robot implements Worker {
    public void work() {
        // robot working
    }

    public void eat() {
        // robot can't eat, 위반된 예
    }
}

// 더 나은 설계
interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Human implements Workable, Eatable {
    public void work() {
        // human working
    }

    public void eat() {
        // human eating
    }
}

class Robot implements Workable {
    public void work() {
        // robot working
    }
}

의존성 역전 원칙 (Dependency Inversion Principle, DIP)

  • 설명 : 고수준 모듈은 저수준 모듈에 의존해서는 안된다. 둘 다 추상화에 의존해야 한다.
interface MessageSender {
    void send(String message);
}

class EmailSender implements MessageSender {
    public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}


class NotificationService {
    private MessageSender sender;

    public NotificationService(MessageSender sender) {
        this.sender = sender;
    }

    public void sendNotification(String message) {
        sender.send(message);
    }
}

 

'Java' 카테고리의 다른 글

빌더 패턴  (0) 2024.09.26
디자인 패턴의 활용  (0) 2024.09.26
람다식(Lambda expression)  (2) 2024.09.13
JSTL 을 활용한 게시판 기능 만들기  (1) 2024.07.15
JSP와 MVC 패턴 Todo 프로젝트  (0) 2024.07.12