SOLID 설계 원칙이란 다음을 뜻한다.
- 단일 책임 원칙(Single Responsibility Principle)
- 개방-폐쇄 원칙(Open-Closed Principle)
- 리스코프 치환 원칙(Liskov Substitution Principle)
- 인터페이스 분리 원칙(Interface Segregation Principle)
- 의존 역전 원칙(Dependency Inversion Principle)
단일 책임 원칙
**클래스는 단 한개의 책임만을 가져야 한다**.
객체 지향은 책임을 객체에게 할당하는 것이다. 클래스가 여러 책임을 가지면 각 책임마다 변경되는 이유가 발생하기 때문에, 클래스가 한 개의 이유로만 변경되거나 교체되려면 클래스의 책임은 하나여만 한다.
예를 들어 한 클래스가 데이터를 읽어 처리하고 화면에 보여주는 두 가지 책임을 가진다면, 기능이 변화되었을 때 변경이 어렵게 된다. 또 어떠한 기능을 사용하고자 할 때 필요하지 않은 패키지까지 의존해야 하는 경우가 생갈 수도 있다. 이럴 땐 클래스를 분리하여 한 클래스가 한 책임만 지게 하는 것이 옳다.
예를 들어 List<Student>를 받아 각각 학생들의 이름을 출력하는 책임을 가진 객체가 있다고 한다면 List<Student>에서 이름만을 뽑아 List<String>으로 만들고, 이를 다른 객체가 받아 출력하는 식으로 개선이 가능할 것이다.
또한 여러 사용자 객체가 한 객체의 서로 다른 부분을 사용한다면 그 부분을 분리하는 것도 고려해 볼 수도 있다. 예를 들어 다음은 자바 체스 게임에서 방향을 담당하는 Direction enum이다.
`package chess.domain; import java.util.Arrays; import java.util.List; import chess.exception.IllegalMoveException; public enum Direction { NORTH(0, 1), WEST(-1, 0), EAST(1, 0), SOUTH(0, -1), NORTHNORTH(0, 2), SOUTHSOUTH(0, -2), NORTHEAST(1, 1), NORTHWEST(-1, 1), SOUTHEAST(1, -1), SOUTHWEST(-1, -1), NNE(1, 2), NEE(2, 1), NNW(-1, 2), NWW(-2, 1), SSE(1, -2), SEE(2, -1), SSW(-1, -2), SWW(-2, -1); private static final String INVALID_DIRECTION = "올바르지 않은 방향입니다."; private int xDegree; private int yDegree; Direction(int xDegree, int yDegree) { this.xDegree = xDegree; this.yDegree = yDegree; } public static Direction of(int x, int y) { return Arrays.stream(Direction.values()).filter(d -> d.xDegree == x && d.yDegree == y) .findFirst() .orElseThrow(() -> new IllegalMoveException(INVALID_DIRECTION)); } public boolean isDiagonal() { List<Direction> diagonalDirection = Arrays.asList(NORTHWEST, NORTHEAST, SOUTHWEST, SOUTHEAST); return diagonalDirection.contains(this); } public boolean isForwardDouble() { return (this == NORTHNORTH || this == SOUTHSOUTH); } public boolean isForwardForPawn() { List<Direction> forwardDirection = Arrays.asList(NORTH, NORTHNORTH, SOUTH, SOUTHSOUTH); return forwardDirection.contains(this); } public int getYDegree() { return yDegree; } public int getXDegree() { return xDegree; } } `
이름에서 유추할 수 있는 본연의 업무인 시작위치와 도착위치를 받아서 이동할 방향을 얻어내는 책임 이외에, 방향이 대각선 방향에 속하는지, 2칸 이동인지, 폰에게 적합한 이동인지 판별하는 메소드들이 섞여 있다. 이들을 분리하여 별도의 클래스로 변경할 수 있을 것이다.
개방 폐쇄 원칙
**확장에는 열려 있으면셔 변경에는 닫혀 있어야 한다.**
기능을 변경하거나 확장할 수 있으면서 그 기능을 이미 사용하는 코드는 수정하지 않아도 잘 작동해야 한다.
이는 추상화(인터페이스)를 통해 만들 수 있다. 예를 들어 콘솔 체스 프로그램을 웹으로도 보이게 해야 한다면 뷰 로직을 인터페이스로 만들면 기존의 콘솔 프로그램을 그대로 두고도 웹 체스 프로그램을 작동시킬 수 있다.
다운캐스팅을 하거나, 비슷한 if-else 블록이 존재하거나 하는 것은 개방 폐쇄 원착이 깨졌다는 신호이다.
리스코프 치환 원칙
**상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.**
스 게임에서 기본적으로 모든 말들은 다른 말을 뛰어넘을 수 없지만 오직 나이트만이 다른 말을 뛰어넘을 수 있다. 체스 게임을 구현하기 위해 모든 타입의 말들이 Piece를 상속하여 구현되어 있다. 이 경우 나이트의 경우에만 뛰어넘기를 방지하는 로직을 끄기 위해 이런 코드를 짠다고 한다면 리스코프 원칙을 위반한 것이 된다.
`if (!(sourcePiece instanceof Knight)) { validateNoObstacle(source, destination); }`
리스코프 치환 원칙은 확장과도 연관이 있기에 리스코프 치환 원칙을 어기면 개방 폐쇄 원칙 역시 여길 가능성이 높아진다.
인터페이스 분리 원칙
**인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.**
다르게 말하면 자신이 사용하는 메서드에만 의존해야 한다는 뜻이다. C++에서는 이 원칙을 어김으로 인해서 소스 재컴파일 문제 등이 발생할 수 있지만, 자바에서는 이런 문제가 일어나지는 않는다. 다만 단일 책임 원칙과 연결되어 있으므로 이 원칙을 지키는 것이 좋다.
의존 역전 원칙
**고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.**
저수준 모듈은 고수준 모듈의 세부 기능을 구현한 것이다. 즉 고수준 모듈이 저수준 모듈에 의존하지 않음으로써 저수준 모듈을 쉽게 교체할 수 있게 함이다.
Comments powered by Disqus.