S O L I D
스프링 공부를 하다 객체 지향 설계를 학습하기 앞서 알아 두면 좋다고 하여 기록한다.
악덕 면접관에게 걸리면 면접에도 나올 수 있다고 하니 알아두자.
SOLID란 클린코드로 유명한 러버트 마틴이 좋은 객체 지향 설계의 5가지 원칙을 정리한 것이다.
- S : SRP(Single Responsibility Principle) 단일 책임 원칙
- O :OCP(Open/Closed Principle) 개방-폐쇄원칙
- L : LSP(LIskov Substitutiom Principle) 리스코프 치환 원칙
- I : ISP(Interface Segregation Principle) 인터페이스 분리 원칙
- D : DIP(Dependency Inversion Principle) 의존관계 역전 원칙
SRP 단일 책임 원칙
정의 : 한 클래스는 하나의 책임만 가져야한다.
"하나의 책임만 가진다"는 표현이 좀 모호하다.
쉽게 설명하자면, "한 클래스는 하나의 일만 해야 한다" 또는 "클래스가 변경되는 이유는 단 하나여야 한다"이다. 정리하면, 어떤 클래스를 만들 때 그 클래스가 여러 가지 다른 일을 하지 않도록 설계해야 한다는 원칙이다.
남자라는 클래스와 남자 클래스에 의존하는 다양한 클래스가 있다고 생각해보자.
예를 들어 어늘 여자친구와 헤어졌다고 해보자.
남자는 더 이상 챙길 일 없는 기념일과 대상이 없는 키스하기에 힘들어하게 된다.
따라서 이런 경우에 역할(책임)을 분리하라는 것이 단일 책임 원칙이다.
[속성이 SRP를 지키지 않은 경우]
class 사람 {
String 군번;
...
}
...
사람 로미오 = new 사람();
사람 줄리엣 = new 사람();
줄리엣 군번 = "154564";//이건?
여자인 줄리엣이 가진 군번 속성에 값을 할당하거나 읽어 오는 코드를 제제할 방법이 없다.
이 경우 남자 클래스와 여자 클래스로 분할하고 남자 클래스에만 군번 속성을 갖게 하면 SRP를 지킨 코드로 변모시킬 수 있다.
[메서드가 단일 책임 원칙을 지키지 않은 경우]
class 강아지 {
final static Boolean 수컷 = true;
final static Boolean 암컷 = false;
Boolean 성별;
void 소변보다(){
if(this.성 == 수컷){
//한쪽 다리를 들고 소변본다.
}else {
//뒷다리 두개 굽혀 앉은 자세로 소변본다.
}
}
}
소변보다() 메서드가 수컷의 행위와 암컷의 행위를 모두 구현하려고 하기에 단일 책인 원칙을 위배
[메서드가 단일 책임 원칙을 적용해 개선한 코드]
abstract class 강아지 {
abstract void 소변보다()
}
class 수컷강아지 extends 강아지 {
void 소변보다() {
//한쪽 다리를 들고 소변을 본다.
}
}
class 암컷강아지 extends 강아지 {
void 소변보다() {
//뒷다리 두개 굽혀 앉은 자세로 소변본다.
}
}
OCP 개방-폐쇄 원칙
정의 : 소프트웨어 요소는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.
OCP의 핵심 아이디어는 기존의 코드를 변경하지 않으면서도,
시스템의 기능을 확장할 수 있어야 한다는 것이다.
즉, 새로운 기능을 추가하고 싶을 때 기존 코드를 수정하는 대신,
기존 코드에 새로운 기능을 '추가'할 수 있는 방식으로 설계해야 한다는 의미이다.
이를 자동차와 운전자의 예로 쉽게 설명해보자.
운전자는 자동차를 운전할 때, 가속, 제동, 방향 전환 등의 기본적인 조작을 한다.
여기서 자동차는 운전자가 사용하는 "인터페이스"라고 할 수 있다.
만약 자동차의 내부 기계 구조나 소프트웨어가 업데이트되어도, 운전자가 자동차를 조작하는 방법은 변하지 않아야 한다.
즉, 자동차의 내부 구현은 변경될 수 있지만, 운전자가 사용하는 인터페이스는 변경되지 않아야 한다.
이것이 "변경에는 닫혀 있어야 한다"는 부분이다.
한편, 자동차 제조사는 새로운 기능을 추가할 수 있어야 한다. 새로운 기능을 추가하는 것은 자동차의 확장이다.
하지만 이러한 확장이 운전자가 자동차를 운전하는 기본적인 방법을 바꾸지 않아야 한다.
운전자는 여전히 가속 페달을 밟아서 가속하고, 브레이크 페달을 밟아서 정지한다.
이것이 "확장에는 열려 있어야 한다"는 부분이다.
LSP 리스코프 치환 원칙
정의 : 프로그램의 객체는 프로그램의 확장성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
쉽게 설명하기 위해, "직사각형-정사각형" 문제를 예로 들어보자.
직사각형 클래스가 있고, 이를 상속받아 정사각형 클래스를 만들었다고 가정하자.
직사각형 클래스에는 너비와 높이 속성이 있으며, 이 속성들을 변경할 수 있는 메서드가 있다.
이 경우, 직사각형의 인스턴스를 사용하는 코드에서는 너비와 높이를 독립적으로 설정할 수 있지만,
정사각형의 인스턴스에서는 너비와 높이를 동일하게 유지해야 한다.
따라서, 직사각형을 사용하는 코드에 정사각형 인스턴스를 대체하면, 해당 코드는 예상대로 작동하지 않을 수 있다.
예를 들어, 너비와 높이를 다르게 설정하려고 할 때 정사각형에서는 이를 처리할 수 없기 때문이다.
이 예에서 볼 수 있듯, LSP는 서브클래스가 기반 클래스의 계약을 준수하며, 기반 클래스를 사용하는 코드를 변경하지 않고도 서브클래스의 인스턴스로 대체될 수 있어야 함을 의미한다.
만약 이 원칙이 제대로 지켜지지 않는다면, 객체 지향 설계의 다형성을 활용하는 데 있어서 문제가 발생할 수 있습니다.
따라서, LSP를 따르는 설계는 시스템의 유연성을 향상시키고, 재사용성을 높이며, 코드의 유지보수를 용이하게 한다.
ISP 인터페이스 분리 원칙
정의 : 클라이언트는 자신이 사용하지 않는 메서드에 의존하면 안 된다.
즉, 하나의 일반적인 인터페이스보다는 여러 개의 구체적인 인터페이스가 더 낫다는 것을 의미한다.
다양한 종류의 프린터를 생각해보자.
일부 프린터는 문서를 인쇄하는 기능만 있고, 다른 일부는 스캔하고, 팩스를 보낼 수도 있다.
만약 모든 프린터 기능(인쇄, 스캔, 팩스)을 하나의 인터페이스에 넣는다면,
인쇄 기능만 필요한 프린터도 스캔과 팩스 기능을 구현해야만 한다.
이는 사용하지 않는 기능에 대한 의존성을 강제하게 되어, 설계가 비효율적이고 불필요하게 복잡해진다.
ISP를 적용하려면, 각 기능별로 별도의 인터페이스를 만들어야 한다.
예를 들어, Printable 인터페이스, Scannable 인터페이스, Faxable 인터페이스 등을 따로 정의한다.
이렇게 하면 각 클래스는 필요한 기능에만 의존하게 되어, 시스템의 유연성과 유지보수성이 향상된다.
ISP를 통해 시스템 내의 의존성을 최소화하고, 각 부품의 재사용성을 높일 수 있습니다. 또한, 클라이언트는 자신에게 필요한 인터페이스만을 사용하여 더 명확하고, 이해하기 쉬운 코드를 작성할 수 있다.
DIP 의존관계 역전 원칙
정의 : 고수준 모듈이 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.
이 원칙은 "세부 사항이 원칙을 의존하지 않고, 원칙이 세부 사항을 의존해야 한다"는 아이디어에 기반한다.
간단하게 말해, DIP는 시스템의 다양한 부분 사이의 의존성을 관리하는 방법에 관한 것이다.
쉽게 설명하기 위해, 전등과 전등 스위치의 예를 들어보자.
전등 스위치(고수준 모듈)가 직접적으로 전등(저수준 모듈)에 의존하는 대신, 둘 다 '스위치 가능'이라는 인터페이스(추상화)에 의존하게 한다.
이렇게 하면, 나중에 다른 종류의 장치(예: 선풍기, 히터 등)를 쉽게 스위치에 연결할 수 있게 되며,
스위치 코드를 변경하지 않고도 새로운 장치를 시스템에 통합할 수 있다.
DIP를 적용함으로써, 개발자는 시스템의 다른 부분을 더 독립적으로 개발하고 테스트할 수 있으며,
변경에 더 유연하게 대응할 수 있다.
이 원칙은 시스템의 유지보수성과 확장성을 크게 향상시킬 수 있다.
이상으로 SOLID에 대한 정리 끝
'프로그래밍 언어 > Spring' 카테고리의 다른 글
[Spring] @Builder 빌더 어노테이션, 빌더 패턴 (2) | 2024.02.28 |
---|