[OOP] SOLID 원칙

김민식·2023년 11월 7일
0

CS

목록 보기
4/7
post-thumbnail

좋은 객체 지향 설계의 5가지 원칙(SOLID)

SOLID 란?

  • 클린코드로 유명한 Robert C.Martin이 좋은 객체 지향 설계의 5가지 원칙(Software desing principls)을 정의



SRP : 단일 책임 원칙

Single Responsibility Principle

  • 한 클래스는 하나의 책임만 가져야 한다
  • 클래스를 수정할 때에는 오직 하나의 이유로 수정해야 한다
  • 변경이 생겼을 떄 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것
// SRP 위반
public class User {
  public User() {
    // User 생성자 로직
  }
  
  public saveUser {
    // 데이터베이스 저장 로직
  }
}

// SRP 준수
public class User {
  public User() {
    // User 생성자 로직
  }
}

public class UserDatabase {
  public saveUser {
    // 데이터베이스 저장 로직
  }
}



OCP : 개방 - 폐쇄 원칙

Open/closed Principle

  • 가장 중요한 원칙이다

  • 소프트웨어 개체는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다
  • 즉, 기존 코드를 변경하지 않으면서도 시스템의 기능을 확장 할 수 있어야 함
  • 다형성을 활용
// override를 사용하여 기존 calculateArea()를 변경하지 않는다 (다형성)
public interface Shape {
    double calculateArea();
}

public class Rectangle implements Shape {
    public double length;
    public double width;

    @Override
    public double calculateArea() {
        return length * width;
    }
}

public class Circle implements Shape {
    public double radius;

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



LSP : 리스코프 치환 원칙

Liskov Substitution Principle

  • 프로그램에서 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 대체해도 프로그램의 정확성이나 실행에 영향을 주지 않아야 한다
  • 즉, 자식 클래스는 부모 클래스의 행동 방식을 완벽하게 모방할 수 있어야 하며, 자식 클래스의 객체는 부모 클래스의 객체로 예상되는 모든 역할을 수행할 수 있어야 한다
  • 다형성에서 하위(자식) 클래스는 인터페이스 규약을 다 지켜야 한다는 것, 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면, 이 원칙이 필요하다
// LSP 위반
public class Bird {
    public void fly(){
    }
}

public class Ostrich extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Ostrich can't fly.");
    }
}

Bird bird = new Ostrich();
bird.fly(); // 런타임 에러 발생, LSP 위반




// LSP 준수
public abstract class Bird {
    // 공통 기능
}

public class FlyingBird extends Bird {
    public void fly(){
        System.out.println("This bird can fly.");
    }
}

public class NonFlyingBird extends Bird {
    // 비행하지 않는 새를 위한 기능
}

public class Sparrow extends FlyingBird {
    // 참새는 날 수 있음
}

public class Ostrich extends NonFlyingBird {
    // 타조는 날 수 없음
}

FlyingBird sparrow = new Sparrow();
sparrow.fly(); // 문제없음

Bird ostrich = new Ostrich();
// ostrich.fly(); // 이 코드는 존재하지 않음. LSP 준수



ISP : 인터페이스 분리 원칙

Interface Segregation Principle

  • 클라이언트는 사용하지 않는 메소드에 의존하면 안된다
  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다
  • 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리
  • 사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리
  • 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않음
  • 인터페이스가 명확해지고, 대체 가능성이 높아진다
// 운전 인터페이스
interface DrivingService {
    void drive();
}

// 정비 인터페이스
interface MaintenanceService {
    void fixEngine();
}

// 운전자 클라이언트
class Driver implements DrivingService {
    public void drive() {
        // 운전 기능 구현
    }
}

// 정비사 클라이언트
class Mechanic implements MaintenanceService {
    public void fixEngine() {
        // 엔진 수리 기능 구현
    }
}



DIP : 의존관계 역적 원칙

Dependency Inversion Principle

  • 고수준 모듈이 저수준 모듈에 의존하지 않으며, 둘 다 추상화에 의존해야 한다
  • 추상화에 의존해야지, 구체화에 의존하면 안된다
  • 즉, 구현 클래스에 의존하지 말고, 인터페이스에 의존해라
// 추상화 인터페이스
interface Switchable {
    void turnOn();
    void turnOff();
}

// 저수준 모듈
class LightBulb implements Switchable {
    public void turnOn() {
        // 전구를 켬
    }

    public void turnOff() {
        // 전구를 끔
    }
}

// 고수준 모듈
class ElectricPowerSwitch {
    private Switchable device;

    public ElectricPowerSwitch(Switchable device) {
        this.device = device;
    }

    void press() {
        if (device != null) {
            device.turnOn();
        } else {
            device.turnOff();
        }
    }
}


정리

  • 단일 책임 원칙은 코드를 간결하게 유지하는 데 도움
  • 개방-폐쇄 원칙은 확장성을 부여
  • 리스코프 치환 원칙과 인터페이스 분리 원칙은 코드의 유연성을 제공
  • 의존관계 역전 원칙은 결합도를 낮추어 시스템의 독립성을 강화
profile
BE Developer

0개의 댓글