SOLID 객체지향설계
- 정의
응집도를 높이고 (High Cohesion) 결합도는 낮추는(Loose Coupling) 는 고전원칙을 객체지향의 관점에서 재정립한 것이 SOLID 5원칙이다.
- SRP (Single Responsibillity Principle): 단일책임원칙
- OCP (Open Closed Prinsipel) : 개방폐쇄원칙
- LSP ( Liskov Substitution Principle) :리스코프 치환원칙
- ISP (Interface Segregation Principle) : 인터페이스 분리 원칙
- DIP (Dependency Inversion Principle ) : 의존역전원칙
SOLID가 개념이긴 하지만 우리가 만드는 제품, 즉 소프트웨어에 녹여내야 하는 개념이다. SOLID 를 잘 녹여낸 소프트웨어는 그렇지 않은 소프트웨어에 비해 상대적으로 이해하기 쉽고, 리팩터링과 유지보수가 수월할 뿐만 아니라 논리적으로 정연하다. 디자인패턴의 뼈대가 되고 스프링프레임워크의 근간이기도 하다.
- SRP (Single Responsibillity Principle): 단일책임원칙
"어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다." - 로버트 C.마틴
남자라고 하는 클래스와 남자 클래스에 의존하는 다양한 클래스가 있다고 가정한다.
역할과 책임이 많은 것이 보여진다.
[다양한 책임을 가진 남자 클래스]
이런 경우에 역할(책임)을 분리하는 것이 단일 책임 원칙이다.
[단일 책임 원칙을 적용해 남자클래스를 단일 책임을 가진 여러 클래스로 분리]
하나의 클래스가 역할과 책임에 따라 네 개의 클래스로 쪼개진 것을 볼 수 있다. 그리고 역할과 클래스명도 딱 떨어지니 이해하기 좋다. 클래스를 역할과 책임에 따라 분리해서 각각 하나의 역할과 책임만 갖게 한다.
단일 책임 원칙은 속성, 메서드, 패키지, 모듈, 컴포넌트, 프레임워크등에도 적용할 수 있는 개념이다.
- 단일 책임원칙이 잘못된 경우
1. 속성이 단일 책임 원칙을 지키지 않은 경우
2. 메서드가 단일 책임 원칙을 지키지 않은 경우
1). 속성이 단일 책임 원칙을 지키지 않은 경우
예제) 객체지향세계에서 남자는 반드시 군대를 가고, 여자는 절대로 군대를 가지 않는다고 가정
class 사람{
String 군번;
}
사람 로미오 = new 사람();
사람 줄리엣 = new 사람();
줄리엣.군번 = "1573042009";
사람클래스에 군번속성이 들어있다. 그러면 사람형 참조변수 줄리엣이 가진 군번 속성에 값을 할당하거나 읽어오는 코드를 제제할 방법이 없다.
- 리팩토링
사람 클래스를 남자클래스, 여자클래스로 분할하고 남자 클래스에만 군번 속성을 갖게하면 된다. 이게 바로 단일 책임원칙을 적용하는 것이다. 남자 클래스와 여자클래스에 공통점이 없다면 사람클래스는 제거하면 되고, 공통점이 많다면 사람클래스를 상위클래스로 해서 공통점을 클래스에 두고 남자 클래스,여자클래스는 사람클래스를 상속하고 차이점만 각자 구현하면 된다.
2). 메서드가 단일 책임 원칙을 지키지 않은 경우
class 강아지 {
final static Boolean 수컷 = true;
final static Boolean 암컷 = false;
Boolean 성별;
void 소변보다() {
if(this.성별 = 수컷){
//한쪽 다리를 들고 소변을 본다.
}else {
//뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
}
}
}
강아지가 성별에 따라 소변보다() 메서드가 분기 처리가 되는 것을 볼 수 있다. 강아지 클래스의 소변보다() 메서드가 수컷강아지의 행위와 암컷강아지의 행위를 모두 구현하려고 하기에 단일 책임(행위)원칙을 위배하고 있는 것이다.
- 리펙토링
abstract class 강아지{
abstract void 소변보다()
}
class 수컷강아지 extends 강아지 {
void 소변보다() {
//한쪽 다리를 들고 소변을 본다.
}
}
class 암컷강아지 extends 강아지 {
void 소변보다() {
//뒷다리 두개로 앉은 자세로 소변을 본다.
}
}
2. OCP (Open Closed Prinsipel) : 개방폐쇄원칙
"소프트웨어 엔티티(클래스,모듈,함수 등)은 확장에 대해서는 열려 있어야하지만 변경에 대해서는 닫혀 있어야한다."
-로버트C.마틴
→ "자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀있어야한다."
운전가가 마티즈를 구입했다고 가정해보겠습니다.
마티즈는 창문을 수동으로 개방해야 하고 기어도 수동으로 조작해야하는 차였습니다.
그 다음 차로 쏘나타를 구입하고 또 테슬라 혹은 다른 차를 구입했을 때
이러한 차들은 수동이 아닌 창문을 자동으로 개방하고 기어도 자동으로 조작을 할 수 있는 차들이었습니다.
그렇다면 운전자가 이 차가 변경되는 것에 다 맞추어서 운전자도 운전하는 습관을 바꿔야할까요?
상위클래스 또는 인터페이스를 중간에 둠으로써 다양한 자동차가 생긴다고 해도 객체지향세계의 운전자는 운전습관에 영향을 받지 않게 됩니다. 자동차 입장에서는 자신의 확장에는 개방돼 있는 것이고, 운전자 입장에서는 주변의 변화에 폐쇄돼 있는 것이됩니다.
3. L(iskov) S(ubstitution) P(rinciple) : 리스코프 치환원칙
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 합니다.
즉 하위클래스의 인스턴스는 상위형 객체 참조변수에 대입해 상위 클래스의 인스턴스 역할을 하는데 문제가 없어야합니다.
그림을 보면 하위에 존재하는 것들은 상위에 있는 것들의 역할을 하는데 전혀 문제가 없습니다.
삼다수가 물 또는 음료의 역할을 하는 것은 전혀 문제가 되지 않습니다.
리스코프 치환 원칙은 객체 지향의 상속이라는 특성을 올바르게 활용하면 자연스럽게 얻게 되는 부분이라고 보시면 됩니다.
상속에 대해 간단히 설명드리면
하위클래스 is a kind of 상위클래스 – 하위분류는 상위분류의 한종류
구현클래스 is able to 인터페이스 – 구현분류는 인터페이스 할 수 있어야 한다.
두 개의 문장대로 구현한 프로그램이라면 리스코프 치환 원칙을 잘 지키고 있다고 할 수 있습니다.
4. ISP (Interface Segregation Principle) : 인터페이스 분리 원칙
남자 클래스를 나누는 것이 아니라 여자친구를 만날때는 남자친구역할만 할 수 있게 인터페이스로 제한하고, 어머니와 있을 때는 아들 인터페이스로 제한하고, 직장 상사앞에서는 사원인터페이스로 제한하는 것이 바로 인터페이스 분할 원칙의 핵심이다.
결론적으로 단일 책임원칙(SRP)와 인터페이스 분할원칙(ISP)은 같은 문제에 대한 두 가지 다른 해결책이라고 볼 수 있다. 프로젝트 요구사항과 설계자의 취향에 따라 단일 책임 원칙이나 인터페이스분할 원칙중 하나를 선택해서 설계할 수 있다. 특별한 경우가 아니라면 단일 책임 원칙을 적용하는 것이 더 좋은 해결책이라고 할 수 있다.
5. DIP (Dependency Inversion Principle ) : 의존역전원칙
"추상된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화 된것에 의존해야 한다."
"자주 변경되는 구체클래스에 의존하지 마라" - 로버트C.마틴
자동차가 스노우타이어에 의존하고 있다. 자동차는 한번 사면 몇년은 타야하는데 스노우 타이어는 계절이 바뀌면 일반 타이어로 교체해야 한다. 이런 경우 스노우 타이어를 일반 타이어로 교체할 때 자동차는 그 영향에 노출되있음을 알 수 있다. 자동차 자신보다 더 자주 변하는 스노우 타이어에 의존하기에 부서지기 쉽습니다.
자동차가 구체적인 타이틀(스노우타이어, 일반타이어, 광폭타이어) 이 아닌 추상화된 타이어 인터페이스만 의존하게함으로써 스노우 타이어에서 일반타이어로, 또는 다른 구체적인 타이어로 변경되어도 자동차는 그 영향을 받지 않는 형태로 구성된다. 앞서 자동차와 스노우타이어만 있을 때에는 스노우 타이어가 그 무엇에도 의존하지 않는 클래스였는데, 이 그림에서는 추상적인 것인 타이어 인터페이스에 의존하게 됬다. 바로 의존의 방향이 역전 된 것이다. 그리고 자동차는 자신보다 변하기 쉬운 스노우타이어에 의존하던 관계를 중간에 추상화된 타이어 인터페이스를 추가해 두고 의존관게를 역전시키고 있다.
이처럼 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위클래스를 두어 변하기 쉬운것의 변화에 영향받지 않게 하는 것이 의존역전원칙이다.
'java' 카테고리의 다른 글
java_6_정적멤버와 static (0) | 2021.11.01 |
---|---|
java_17 스레드 (0) | 2021.11.01 |
java_16객체입출력보조스트림 ( ObjectOutputStream & ObjectInputSteam) (0) | 2021.10.29 |
java_15_Io 패키지- 출력스트림(Writer) (0) | 2021.10.29 |
java_15_Io 패키지- 출력스트림(OutputStream) (0) | 2021.10.29 |