SOLID 설계 원칙

      SOLID 설계 원칙에 댓글 없음

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++에서는 이 원칙을 어김으로 인해서 소스 재컴파일 문제 등이 발생할 수 있지만, 자바에서는 이런 문제가 일어나지는 않는다. 다만 단일 책임 원칙과 연결되어 있으므로 이 원칙을 지키는 것이 좋다.

의존 역전 원칙

고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.

저수준 모듈은 고수준 모듈의 세부 기능을 구현한 것이다. 즉 고수준 모듈이 저수준 모듈에 의존하지 않음으로써 저수준 모듈을 쉽게 교체할 수 있게 함이다.

댓글 남기기