SOLID - Object-Oriented Design

업데이트:

객체지향 설계(SOLID)

  • 로버트 마틴이 2000년대 초반에 명명한 객체 지향 프로그래밍1 및 설계의 다섯 가지 기본 원칙을 마이클 페더스가 두문자어 기억술로 소개한 것이다.
  • 프로그래머가 시간이 지나도 유지 보수와 확장이 쉬운 시스템을 만들고자 할 때 이 원칙들을 함께 적용할 수 있다.
  • SOLID 원칙들은 소프트웨어 작업에서 프로그래머가 소스 코드가 읽기 쉽고 확장하기 쉽게 될 때까지 소프트웨어 소스 코드를 리팩터링하여 코드 냄새를 제거하기 위해 적용할 수 있는 지침이다.
  • 이 원칙들은 애자일 소프트웨어 개발과 적응적 소프트웨어 개발의 전반적 전략의 일부다.

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

  • 한 클래스는 하나의 책임만 가져야 한다.
public class Music {

  private String name;
  private String artist;
  private LocalDateTime releaseDate;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getArtist() {
    return artist;
  }

  public void setArtist(String artist) {
    this.artist = artist;
  }

  public LocalDateTime getReleaseDate() {
    return releaseDate;
  }

  public void setReleaseDate(LocalDateTime releaseDate) {
    this.releaseDate = releaseDate;
  }

  // This code is violate the single responsibility principle.
  public void playMusic() {
    System.out.printf("The song(%s) is playing now.", this.name);
  }

}
  • Music 클래스에는 재생에 대한 책임이 같이 있으므로, 단일 책임의 원칙에 위배된다.
  • 그래서 아래와 같이 음악 재생에 대한 클래스를 분리해야 하였다.
public class MusicPlayer {

  public void playMusic(Music music) {
    System.out.printf("The song(%s) is playing now.", music.getName());
  }

}

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

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
  • 아래의 DataSource는 각 데이터베이스 종류에 따라 확장이 가능하다.
public class DataSource {

  private String userName;
  private String password;

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

}
public class OracleDataSource extends DataSource {

  private String jdbcUrl;

  public String getJdbcUrl() {
    return jdbcUrl;
  }

  public void setJdbcUrl(String jdbcUrl) {
    this.jdbcUrl = jdbcUrl;
  }

}
public class MongodbDataSource extends DataSource {

  private String host;
  private int port;

  public String getHost() {
    return host;
  }

  public void setHost(String host) {
    this.host = host;
  }

  public int getPort() {
    return port;
  }

  public void setPort(int port) {
    this.port = port;
  }

}

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

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
public class Item {

  private int price;

  public int getPrice() {
    return price;
  }

  public void setPrice(int price) {
    this.price = price;
  }

}
public class DiscountItem extends Item {

  private double discountRate;

  public double getDiscountRate() {
    return discountRate;
  }

  public void setDiscountRate(double discountRate) {
    this.discountRate = discountRate;
    this.applyDiscountedPrice();
  }

  private void applyDiscountedPrice() {
    super.setPrice((int)(super.getPrice() * (1 - this.discountRate)));
  }

}
public class WrongDiscountItem extends Item {

  private double discountRate;

  public double getDiscountRate() {
    return discountRate;
  }

  public void setDiscountRate(double discountRate) {
    this.discountRate = discountRate;
  }

  @Override
  public void setPrice(int price) {
    super.setPrice((int)(super.getPrice() * (1 - this.discountRate)));
  }

}
  • 위의 코드에서 Item과 WrongDiscountItem은 setPrice 메서드를 수행 시 결과 값이 달라지므로 LSP를 위반하며, Item과 DiscountItem은 LSP를 준수한다.

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

  • 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.
public interface WrongMultifunctionPrinter {

  public void print();

  public void copy();

  public void scan();

  public void fax();

}
  • WrongMultifunctionPrinter은 여러 기능들을 복합적으로 사용하고 있어 SRP와 ISP를 위반한다.
  • 아래는 각 기능을 수행하는 책임 단위로 인터페이스를 분리하였다.
public interface MultifunctionPrinter extends Printer, CopyMachine, Scanner, Fax {
}
public interface Printer {

  public void print();

}
public interface CopyMachine {

  public void copy();

}
public interface Scanner {

  public void scan();

}
public interface Fax {

  public void fax();

}

의존관계 역전 원칙(Dependency Inversion Principle - DIP)

  • 고수준의 모듈(추상화)은 저수준의 모듈(구체화)에 의존해서는 안된다.
public class WrongComputer {

  private final LCDMonitor monitor;
  private final MechanicalKeyBoard keyboard;
  private final GamingMouse mouse;

  public WrongComputer() {
    this.monitor = new LCDMonitor();
    this.keyboard = new MechanicalKeyBoard();
    this.mouse = new GamingMouse();
  }

}
public class Computer {

  private final Monitor monitor;
  private final KeyBoard keyboard;
  private final Mouse mouse;

  public Computer(Monitor monitor, KeyBoard keyboard, Mouse mouse) {
    this.monitor = monitor;
    this.keyboard = keyboard;
    this.mouse = mouse;
  }

}
public interface Monitor {
}
public class LCDMonitor implements Monitor {
}
public interface KeyBoard {
}
public class MechanicalKeyBoard implements KeyBoard {
}
public interface Mouse {
}
public class GamingMouse implements Mouse {
}
  • WrongComputer는 각 장비 간 결합으로 인해 종속되어있고, 테스트와 장비의 전환도 쉽지 않다.
  • 각 장비 별 인터페이스를 구현하여 종속성을 분리하였으며 추상화를 통해 전환이 자유로워진다.

Reference

댓글남기기