객체지향 설계에서 SOLID 5원칙을 빼놓을 수 없는 것 같습니다. 해당 글은 예전에 공부할 때 써놓았던건데 올리는걸 깜빡하고 있었습니다..! 다양한 글들을 읽어보고 제 방식대로 요리조리 한번 생각해본 SOLID 5원칙 입니다.
하나의 모듈은 한 가지 책임을 가져야 한다는 것, 즉 모듈의 변경 원인은 한 가지여야 함을 의미합니다.
한 가지 모듈은 여러 역할을 가지면 안되고 한 역할에 대해서만 책임을 져야합니다.
변경의 원인이 한 가지이기 때문에 대상이 명확해집니다. 이 효과는 시스템의 규모가 크면 클수록 더욱 확대 됩니다.
확산적 변경 - 한 클래스가 여러 변경 원인에 의해 자주 변경되는 경우
산탄총 수술 - 변경할 때 마다 여러 클래스를 변경해야 하는 경우
소프트웨어 구성요소는 확장에는 열려있고, 변경에는 닫혀있어야 한다는 원리입니다.
변경에 대한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화 해야한다는 의미로, 요구 사항의 변경이나 추가에 있어서 기존 구성요소는 수정이 일어나지 말아야하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 합니다. 그러기 위해서는 추상화와 다형성을 적극 활용해야 합니다.
하위 타입은 상위 타입을 대체할 수 있어야 합니다. 상위 타입이 하위 타입으로 변경 되어도, 사용자는 차이점을 인식하지 못하고 사용할 수 있어야 한다는 것 입니다.
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를 사용한 암호화
}
}
이러한 문제는 다음과 같은 설계로 개선할 수 있습니다.
더이상 하위 모듈에 의존하지 않고 추상화에 의존하고 있습니다.
좋은 글 감사합니다