SOLID 원칙
SOLID 원칙
SOLID 원칙은 객체지향 설계 5원칙이라고도 불리며, 각 원칙의 앞 글자를 따서 만들어졌다. 객체지향 설계의 핵심 중 하나인 의존성 관리를 잘 하기 위해 SOLID 원칙을 준수해야한다.
S : Single Responsibility Principle
단일 책임 원칙 (Single Responsibility Principle) 은 클래스가 오직 하나의 목적/이유로만 변경되어야 한다는 것을 강조한다. 여기서 책임 이란 단순히 메서드의 개수를 뜻하지 않고, 특정 사용자나 기능 요구사항에 따라 소프트웨어의 변경 요청을 처리하는 역할을 의미한다.
즉 클래스는 한 가지 변화의 이유만 가져야 하며, 이를 통해 변경이 발생했을 때 다른 기능에 영향을 덜 미치도록 설계된다. 이렇게 하면 유지보수가 쉬워지고 코드가 더 이해하기 쉬워진다.
O : Open-Closed Principle
개방 폐쇄 원칙 (Open-Closed Principle)은 확장에는 열려있고, 변경에는 닫혀 있어야 함을 강조합니다. 이때 확장이란 새로운 타입을 추가함으로써 새로운 기능을 추가하는 것을 의미하며, 폐쇄란 확장이 일어날 때 상위 레벨의 모듈이 영향을 받지 않아야 함을 의미한다. 이를 통해서 모듈의 행동을 쉽게 변경할 수 있다. 모듈이란 크기와 상관없이 클래스, 패키지, 라이브러리와 같이 프로그램을 구성하는 임의의 요소를 의미한다.
L : Liskov Substitution Principle
리스코브 치환 원칙(Liskov Substitution Principle) 은 서브 타입은 언제나 상위 타입으로 교체할 수 있어야한다. 즉, 서브 타입은 상위 타입이 약속한 규약을 지켜야 함을 강조한다. 이 원칙은 부모 쪽으로 업 캐스팅을 하는 것이 안전함을 보장하기 위해 존재한다. 상위 타입에 대해 기대되는 역할과 행동 규약이 있는데 이를 벗어나면 안된다. 만약, 하위타입이 상위 타입에 기대되는 역할을 만족하지 않는다면, 상위 타입을 사용하는 클라이언트 코드에서는 하위 타입이 누구인지 물어봐야 하는데, 이는 OCP를 달성하기 어렵게 한다. LSP를 위반하는 대표적인 사례로는 Rectangle 예제가 있다.
다음은 직사각형 Rectangle을 구현한 클래스다. 너비와 높이 값을 지정, 반환 할 수 있으며 넓이를 계산할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Rectangle {
public int width;
public int height;
// 너비 반환, Width Getter
public int getWidth() {
return width;
}
// 너비 할당, Width Setter
public void setWidth(int width) {
this.width = width;
}
// 높이 반환, Height Getter
public int getHeight() {
return height;
}
// 높이 할당, Height Setter
public void setHeight(int height) {
this.height = height;
}
//직사각형 넓이 반환 함수
public int getArea() {
return width * height;
}
}
정사각형은 직사각형의 범주에 포함되는데, 그렇다면 직사각형을 상속하여 정사각형 객체를 만들면 정상적으로 작동할까? 직사각형 클래스를 상속 받아 정사각형 객체를 구현해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Square extends Rectangle{
@Override
public void setWidth(int Width) {
super.setWidth(width);
super.setHeight(getWidth());
}
@Override
public void setHeight(int height) {
super.setHeight(height);
super.setWidth(getHeight());
}
}
Rectangle 객체를 상속 받은 Square 클래스에서는 정사각형의 너비와 높이가 같다는 특징을 구현했다. 너비와 높이 둘 중 하나를 입력해도 나머지 값이 일치되도록 메서드를 오버라이드 해주었다.
이제 확인을 해보면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setHeight(5);
rectangle.setWidth(10);
System.out.println(rectangle.getArea());
Rectangle square = new Square();
square.setWidth(10);
square.setHeight(5);
System.out.println(square.getArea());
}
}
출력값 : 50 25
Rectangle
클래스의 동작과 그것을 상속받은 Square
클래스의 동작이 전혀 다르다는 사실을 알 수 있다.
이는 Square
이 Rectangle
을 상속받는것이 올바른 상속관계가 아니라는 것을 의미하며, 자식 객체가 부모 객체의 역할을 완전히 대체하지 못한다는 의미다.
이는 리스코프 치환 원칙을 위배하므로 변경해보면
Shape
클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Shape {
public int width;
public int height;
// 너비 반환, Width Getter
public int getWidth() {
return width;
}
// 너비 할당, Width Setter
public void setWidth(int width) {
this.width = width;
}
// 높이 반환, Height Getter
public int getHeight() {
return height;
}
// 높이 할당, Height Setter
public void setHeight(int height) {
this.height = height;
}
// 사각형 넓이 반환
public int getArea() {
return width * height;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//직사각형 클래스
public class Rectangle extends Shape {
public Rectangle(int width, int height) {
setWidth(width);
setHeight(height);
}
}
//정사각형 클래스
public class Square extends Shape{
public Square(int length) {
setWidth(length);
setHeight(length);
}
}
Shape
클래스를 상속 받는 Rectangle
클래스와 Square
클래스입니다.
1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
Shape rectangle = new Rectangle(10, 5);
Shape square = new Square(5);
System.out.println(rectangle.getArea());
System.out.println(square.getArea());
}
}
출력 : 50 25
이제 리스코프 치환 법칙을 준수한다.
I : Interface Segregation Principle
인터페이스 분리 원칙(Interface Segregation Principle) 은 클라이언트 입장에서 인터페이스를 분리해야 함을 강조한다. 사용하지 않지만 의존성을 가지고 있다면 해당 인터페이스가 변경되는 경우 영향을 받는다. 따라서, 독립적인 개발과 배포가 불가능하다. 사용하는 기능만 제공하도록 인터페이스를 분리해 변경의 여파를 최소화할 수 있다.
D : Dependency Inversion Principle
의존성 역전 원칙(Dependency Inversion Principle) 은 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 되며, 모두 추상화에 의존해야 함을 강조해야한다. SOLID는 서로 연관이 있는데, 의존성 역전 원칙을 통해서 하위 레벨의 모듈은 개방 폐쇄 원칙을 준수하면서 새로운 타입이 추가 가능하다.
OCP는 개발 이전에 설계를 완료해야 하는것일까?
소프트웨어에는 특정 기능이 추가되고 변경될지 예측을 하기 어렵기때문에 미래의 변경을 예상하고, 준비하기 보단 고객이 원하는 부분을 빠르게 완성해서 전달한 뒤 피드백을 수용하는 방법을 사용해 볼 수 있다. 변화에 대한 가장 좋은 예측은 변화를 경험하는 것이다. 발생할 것 같은 변화를 발견한다면 향후 해당 변화와 같은 종류의 변화로부터 코드를 보호하거나 예방하여 설계할 수 있다. 즉, 고객이 요구할 모든 종류의 변경을 완벽히 예측하고, 이에대한 변경에 대응하기 위해 추상화를 적용하기 보단, 고객이 변경을 요구할 때까지 기다리고 추상화를 만들어서 향후 추가로 재발하는 변화로부터 보호될 수 있도록 하는 것이다.