티스토리 뷰

728x90

SOLID란 무엇인가?

객체 지향 프로그래밍에서 SOLID는 소프트웨어 설계의 이해, 개발, 유지보수를 향상시키기 위한 다섯 가지 설계 원칙의 약어입니다.

이 원칙들을 적용하면 버그 감소, 코드 품질 향상, 더 체계적인 코드 생산, 결합도 감소, 리팩토링 개선, 코드 재사용 촉진 등의 효과를 볼 수 있습니다.

 

SOLID 원칙 깊게 살펴보기

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

핵심: 한 클래스는 하나의, 오직 하나의 변경 이유만 가져야 한다.

왜 중요한가?

  • 클래스의 응집도 향상
  • 유지보수 용이성 증가
  • 다른 클래스와의 결합도 감소

예시:

// 잘못된 예
public class ProfileManager {
    public boolean authenticateUser(String username, String password) {
        // 인증 로직
    }

    public UserProfile showUserProfile(String username) {
        // 사용자 프로필 표시 로직
    }

    public UserProfile updateUserProfile(String username) {
        // 사용자 프로필 업데이트 로직
    }

    public void setUserPermissions(String username) {
        // 권한 설정 로직
    }
}

// 개선된 예
public class AuthenticationManager {
    public boolean authenticateUser(String username, String password) {
        // 인증 로직
    }
}

public class UserProfileManager {
    public UserProfile showUserProfile(String username) {
        // 사용자 프로필 표시 로직
    }

    public UserProfile updateUserProfile(String username) {
        // 사용자 프로필 업데이트 로직
    }
}

public class PermissionManager {
    public void setUserPermissions(String username) {
        // 권한 설정 로직
    }
}

2. 개방-폐쇄 원칙 (OCP: Open-Closed Principle)

핵심: 객체나 개체는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 한다.

장점:

  • 기존 코드 수정 없이 새로운 기능 추가 가능
  • 코드의 유연성과 재사용성 향상
  • 시스템 안정성 개선

예시:

// OCP를 적용하기 전
public class Circle {
    public double radius;
}

public class Square {
    public double side;
}

public class AreaCalculator {
    public double totalArea(Object[] shapes) {
        double area = 0;
        for (Object shape : shapes) {
            if (shape instanceof Circle) {
                Circle circle = (Circle) shape;
                area += Math.PI * circle.radius * circle.radius;
            } else if (shape instanceof Square) {
                Square square = (Square) shape;
                area += square.side * square.side;
            }
        }
        return area;
    }
}

// OCP를 적용한 후
public interface Shape {
    double area();
}

public class Circle implements Shape {
    public double radius;

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

public class Square implements Shape {
    public double side;

    @Override
    public double area() {
        return side * side;
    }
}

public class AreaCalculator {
    public double totalArea(Shape[] shapes) {
        double area = 0;
        for (Shape shape : shapes) {
            area += shape.area();
        }
        return area;
    }
}

3. 리스코프 치환 원칙 (LSP: Liskov Substitution Principle)

핵심: 파생 클래스는 기본 클래스를 대체할 수 있어야 한다.

중요성:

  • 다형성과 확장성의 올바른 사용 보장
  • 예측 가능한 코드 동작 유지
  • 코드 재사용성 증가

예시:

class Person {
    public String speakName() {
        return "I am a person!";
    }
}

class Child extends Person {
    @Override
    public String speakName() {
        return "I am a child!";
    }
}

public class Main {
    public static void printName(Person person) {
        System.out.println(person.speakName());
    }

    public static void main(String[] args) {
        Person person = new Person();
        Child child = new Child();

        printName(person); // 출력: I am a person!
        printName(child);  // 출력: I am a child!
    }
}

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

핵심: 클래스는 사용하지 않는 인터페이스를 구현하도록 강제되어서는 안 된다.

장점:

  • 불필요한 의존성 제거
  • 인터페이스의 응집도 향상
  • 코드의 유연성과 재사용성 증가

예시:

// ISP를 위반한 예
interface Book {
    void read();
    void download();
}

class OnlineBook implements Book {
    public void read() {
        // 읽기 로직
    }

    public void download() {
        // 다운로드 로직
    }
}

class PhysicalBook implements Book {
    public void read() {
        // 읽기 로직
    }

    public void download() {
        // 물리적 책에는 의미 없는 메서드
        throw new UnsupportedOperationException();
    }
}

// ISP를 적용한 예
interface Readable {
    void read();
}

interface Downloadable {
    void download();
}

class OnlineBook implements Readable, Downloadable {
    public void read() {
        // 읽기 로직
    }

    public void download() {
        // 다운로드 로직
    }
}

class PhysicalBook implements Readable {
    public void read() {
        // 읽기 로직
    }
}

5. 의존관계 역전 원칙 (DIP: Dependency Inversion Principle)

핵심: 추상화에 의존해야 하며, 구체화에 의존하면 안 된다.

상위 모듈은 하위 모듈에 의존해서는 안 된다. 둘 다 추상화에 의존해야 한다.

추상화는 세부 사항에 의존해서는 안 된다. 세부사항이 추상화에 의존해야 한다.

 

예시:

// DIP를 적용하기 전
class MySQLDatabase {
    public String getUserData(int id) {
        // MySQL 데이터베이스에서 사용자 데이터를 가져오는 로직
    }
}

class UserService {
    private MySQLDatabase database;

    public UserService() {
        this.database = new MySQLDatabase();
    }

    public String getUser(int id) {
        return this.database.getUserData(id);
    }
}

// DIP를 적용한 후
interface Database {
    String getUserData(int id);
}

class MySQLDatabase implements Database {
    public String getUserData(int id) {
        // MySQL 데이터베이스에서 사용자 데이터를 가져오는 로직
    }
}

class PostgreSQLDatabase implements Database {
    public String getUserData(int id) {
        // PostgreSQL 데이터베이스에서 사용자 데이터를 가져오는 로직
    }
}

class UserService {
    private Database database;

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

    public String getUser(int id) {
        return this.database.getUserData(id);
    }
}

결론

SOLID 원칙은 객체 지향 프로그래밍에서 깔끔하고 유지보수가 쉬운 코드를 작성하기 위한 강력한 가이드라인입니다. 이 원칙들을 이해하고 적용함으로써, 우리는 더 나은 소프트웨어 설계와 개발을 할 수 있습니다. 물론 실제 상황에서는 이 원칙들을 완벽하게 적용하기 어려울 수 있지만, 이를 지향점으로 삼고 노력한다면 분명 더 좋은 코드를 작성할 수 있을 것입니다.

참고 블로그

https://dev.to/lukeskw/solid-principles-theyre-rock-solid-for-good-reason-31hn

728x90
댓글