SOLID 5원칙에 대하여

박진형·2022년 8월 25일
0

객체지향 설계에서 SOLID 5원칙을 빼놓을 수 없는 것 같습니다. 해당 글은 예전에 공부할 때 써놓았던건데 올리는걸 깜빡하고 있었습니다..! 다양한 글들을 읽어보고 제 방식대로 요리조리 한번 생각해본 SOLID 5원칙 입니다.

SOLID 5원칙

단일 책임의 원칙

하나의 모듈은 한 가지 책임을 가져야 한다는 것, 즉 모듈의 변경 원인은 한 가지여야 함을 의미합니다.

한 가지 모듈은 여러 역할을 가지면 안되고 한 역할에 대해서만 책임을 져야합니다.

변경의 원인이 한가지일때 좋은점

변경의 원인이 한 가지이기 때문에 대상이 명확해집니다. 이 효과는 시스템의 규모가 크면 클수록 더욱 확대 됩니다.

SRP 적용 방법

확산적 변경 - 한 클래스가 여러 변경 원인에 의해 자주 변경되는 경우

  • 해결 : 클래스 추출을 통해 혼재된 책임을 개별 클래스로 분할하여 클래스 당 하나의 책임만을 맡도록 하는 것입니다. 추출된 각각의 클래스가 유사한 책임을 중복해서 갖고 있다면 다시 한번 부모 클래스로 추출을 해 사용할 수 있습니다.

산탄총 수술 - 변경할 때 마다 여러 클래스를 변경해야 하는 경우

  • 해결 : 필드 이동이나 메서드 이동을 통해 책임을 몰아줄만한 클래스가 없다면 새로운 클래스를 만들어 해결합니다. 산발적으로 여러곳에 분포된 책임들을 한곳에 모으면서 설계를 깨끗이 합니다. (응집성 향상)

개방 폐쇄 원칙

소프트웨어 구성요소는 확장에는 열려있고, 변경에는 닫혀있어야 한다는 원리입니다.

변경에 대한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화 해야한다는 의미로, 요구 사항의 변경이나 추가에 있어서 기존 구성요소는 수정이 일어나지 말아야하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 합니다. 그러기 위해서는 추상화와 다형성을 적극 활용해야 합니다.

리스코프 치환 원칙

하위 타입은 상위 타입을 대체할 수 있어야 합니다. 상위 타입이 하위 타입으로 변경 되어도, 사용자는 차이점을 인식하지 못하고 사용할 수 있어야 한다는 것 입니다.

public class Rectangle {
	private int width;
	private int height;

	public Rectangle(int width, int height) {
		this.width = width;
		this.height = height;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public void setHeight(int height) {
		this.height = height;
	}
}

public class Square extends Rectangle{
	public Square(int size) {
		super(size, size);
	}

	@Override
	public void setWidth(int width) {
		super.setWidth(width);
		super.setHeight(width);
	}

	@Override
	public void setHeight(int height) {
		super.setHeight(height);
		super.setWidth(height);
	}
}

public static void resize(Rectangle rectangle, int width, int height) {
		rectangle.setWidth(width);
		rectangle.setHeight(height);

	}

public static void main(String[] args) {
		Rectangle rectangle = new Rectangle(10, 15);
		resize(rectangle, 20, 10);
		System.out.println(rectangle.getWidth() + "," + rectangle.getHeight());

		Rectangle square = new Square(10);
		resize(square, 20, 10);
		System.out.println(square.getWidth() + ", " + square.getHeight());
	}

	

위 예제는 직사각형이라는 상위 타입을 하위 타입인 정사각형이 대체를 했을 때 사용자는 뭔가 이상하다는 것을 알 수 있습니다.

분명 resize는 매개 변수로 width, height로 받길래 20, 10을 전달했는데 출력해보니 10, 10으로 출력됩니다.

이것은 하위 타입이 상위타입을 완벽하게 대체할 수 없다는 것입니다.

인터페이스 분리 원칙

자신이 사용하지 않는 인터페이스는 구현하지 말아야 하는 원칙입니다.

다음과 같이 사람이 날고싶다해서 Bird라는 인터페이스를 모두 구현할 필요는 없습니다. 알을 낳고 싶지는 않기 때문이죠.

public interface Bird {
	void fly();
	void layEggs();
}

public class Person implements Bird{

	@Override
	public void fly() {
		
	}

	@Override
	public void layEggs() {

	}
}

Bird라는 인터페이스에서 fly를 따로 떼어내어 Flyable이라는 인터페이스로 만들고 Person이 구현하도록 분리 하면 됩니다.

public interface Flyable {
	void fly();
}

public class Person implements Flyable{

	@Override
	public void fly() {

	}
}

의존성 역전 원칙

상위 모듈은 하위 모듈에 의존해서는 안되며 하위 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야한다는 것입니다.

다시 말해서, 추상화에 의존하며 구체화에 의존하지 않는 설계 원칙을 말합니다.

구체화에 의존한다면 다음과 같은 문제가 발생합니다.

SimplePasswordEncryptor라는 PasswordEncryptor 인터페이스를 구현한 구현체가 있다고 가정하겠습니다. 그리고 이 구현체를 AuthService가 의존한다고 가정하겠습니다.

public class AuthService {
	
	private SimplePasswordEncryptor passwordEncryptor;
	
		public void encrypt(String password)  {
		...encrytor를 사용한 암호화
	}
} 

만약에 이 AuthService가 SimplePasswordEncryptor도 사용하고 BcryptPasswordEncryptor를 사용하려고 하면 어떻게 할까요?

분명 AuthService에 변경이 발생합니다 OCP를 위반하게 되는 것이죠.

public class AuthService {

	private SimplePasswordEncryptor simplePasswordEncryptor;
	private BcryptPasswordEncryptor bcryptPasswordEncryptor;
	
	public void encrypt(String password)  {
		...encrytor를 사용한 암호화
	}
}

이러한 문제는 다음과 같은 설계로 개선할 수 있습니다.

더이상 하위 모듈에 의존하지 않고 추상화에 의존하고 있습니다.

2개의 댓글

comment-user-thumbnail
2022년 8월 25일

좋은 글 감사합니다

1개의 답글