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는 각 장비 간 결합으로 인해 종속되어있고, 테스트와 장비의 전환도 쉽지 않다.
- 각 장비 별 인터페이스를 구현하여 종속성을 분리하였으며 추상화를 통해 전환이 자유로워진다.
댓글남기기