스프링 입문을 위한 자바 객체지향의 원리와 이해를 읽다
개발자라면 꼭 알고있어야 하는 객체지향에대해 기록하면 좋을것 같아 글을 작성하게 되었습니다.
객체지향의 4가지 특징은 아래 링크에 기록해 두었습니다.
객체지향의 4가지 특징
스프링 입문을 위한 자바 객체지향의 원리와 이해를 읽다 개발자라면 꼭 알고있어야 하는 객체지향에대해 기록하면 좋을것 같아 글을 작성하게 되었습니다. 우선 객체지향의 4가지 특징은 아래
sol-b.tistory.com
SOLID
응집도(관련성)는 높이고
결합도(의존)는 나춰라
SOLID는 아래5가지 원칙의 앞머리 알파벳을 따서 부르는 이름입니다.
SRP : 단일 책임 원칙 - 어떤 클래스를 변경해야 하는 이유는 오직하나뿐이여야 한다.
OCP : 개방 폐쇄 원칙 - 자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀 있어야 한다.
LSP : 리스코프 치환 원칙 - 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
ISP : 인터페이스 분리 원칙 - 클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를맺으면 안된다.
DIP : 의존 역전 원칙 - 자신보다 변경하기 쉬운것에 의존하지 마라.
SOLID는 객체지향 4대 원칙을 발판이자 디자인 패턴의 뼈대이고 스프링 프레임워크의 근간입니다.
SRP 단일 책임 원칙
어떤 클래스를 변경하는 이유는 오직하나뿐이여야 한다.
즉, 하나의 클래스에 하나의 역할(책임)을 부여하라는 뜻입니다.
위와 같은 상태는 남자가 너무 많은 역할을 하고 있습니다.
만약 군대에서 전역한다면 소대장의 역할인 사격하기, 구보하기는 사용되지 않는 메서드일 것입니다.
이러한 상태를 책에서는나쁜 냄세가 난다고 합니다.
우리는 각각의 역할마다 클래스를 분리하여 설계할 수 있습니다.
이제 역할과 책임에 따라 클래스를 분리하는 훨씬 가독성 좋고 군대에서 전역한다면
소대원의 역할만 버리면 됩니다.
SRP 위배 코드
속성 위배
줄리엣은 여자이므로 '군번'을 가질 수 없다. 하지만 줄리엣은 군번 속성에 값을 할당하거나 읽어올 수 있는 코드이다.
따라서 이 코드는 현재 냄새가 나고 있다...
가질 수 없는 속성 개선
이를 개선하기 위해선 남자 클래스만 군번을 갖게 하면된다. 여자와 남자의 공통적만 모아 사람 클래스를 상위 클래스로 만들 수도 있고 둘의 공통점이 없다면 남자 클래스와 여자 클래스로 나눠서 구현할 수 있다.
하나의 속성이 여러 속성을 가지는 경우
데이터베이스 테이블에 존재하는 하나의 필드가 여러 속성을 가르키게 되면 정규화를 통해 단일 책임 원칙을 유지하도록 해야한다.
public class 사람 {
String 군번;
public static void main(String[] args) {
사람 로미오 = new 사람();
사람 줄리엣 = new 사람();
줄리엣.군번 = "11-730411994";
}
}
메서드 위배
강아지가 "수컷"인지 "암컷"인지에 따라 메서드에서 분기 처리가 진행되고 있다.
이는 강아지 클래스에서 "소변보다"의 암컷과 수컷 강아지의 행위를 모두 구현하려고 했기 때문에 "단일 책임 원칙"을 위배하게 되었다.
public class 강아지 {
final static Boolean 숫컷 = true;
final static Boolean 암컷 = false;
Boolean 성별;
void 소변보다() {
if (this.성별 == 숫컷) {
// 한쪽 다리를 들고 소변을 본다.
} else {
// 뒤다리 두 개로 앉은 자세로 소변을 본다.
}
}
}
개선코드
public abstract class 강아지 {
abstract void 소변보다();
}
public class 숫컷강아지 extends 강아지 {
void 소변보다() {
// 한쪽 다리를 들고 소변을 본다.
}
}
public class 암컷강아지 extends 강아지 {
void 소변보다() {
// 뒤다리 두 개로 앉은 자세로 소변을 본다.
}
}
추상 메서드를 통해 행위가 다른 메서드의 구현을 하위 클래스에게 위임한다.
암컷과 수컷 강아지의 서로 다른 행위의 구현을 하위 클래스에 맡겼기 때문에 "단일 책임 원칙"을 지킬 수 있었다.
"상속"은 "단일 책임 원칙"을 지키키 위한 도구로 사용한다.
객체지향 4대원칙 캡!상추다에서 사용된 것은?
모델링 과정을 담당하는 추상화입니다.
애플리케이션의 경계를 정하고 추상화를통해 클래스들을 선별하고 속성과메서드를 설계할 때 반드시
단일 책임원칙을 고려하는 습관을 들여야 겠습니다.
또한 리펙터링을 할때에도 단일책임의 원칙을 적용할 곳이 있는지 꼼꼼히 살펴봐야 겠습니다.
OCP 개방 폐쇄 원칙
자신의 확장에는 열려있고
주변의 변화에 대해서는 닫혀 있어야 한다.
제 생각은 OCP의 핵심은 다형성입니다.
다형성을 통해 구현체를 주입받아 확장(구현체의 변경)에는 열려있으면서
원래의 코드는 다형성을 이용해 코드의 변경없이 실행이 달라져야 한다고 생각합니다.
그리고 이것을 잘 표현해낸것이 디자인 패턴의 Strategy Pattern이라고 생각합니다.
다만, 이번글에서는 Strategy Pattern에 대해서는 다루지 않겠습니다.
관심이 있다면 검색을 통해 공부하시길 추천드립니다.
OCP 원칙 위배되는 경우
운전자(자신)가 "소나타(오토)"를 운전하든
"마티즈(수동)"(주변)를 운전하든 행동에 변화가 오면 안된다.
-> 주변의 변화에 자신이 변화해야 하는 경우 (문제)
현실 세계 였다면 수동 -> 오토 자신이 변화하면 되는 문제지만 객체지향 세계는 다른 해결책을 사용한다.
OCP 원칙 적용한 경우
상위 클래스 or 인터페이스를 중간에 둠으로써 다양한 자동차가 생긴다고 해도
객체지향세계의 "운전자"는 영향을 받지 않게 된다.
- 운전자의 입장에선 주변의 변화에 영향을 받지 않게 된다. (마티즈 - 소나타 든 신경안씀)
- 자동차의 입장에선 자신의 확장에 개방되 있는 것이다. (마티즈 - 소나타로 확장)
현실세계에서를 살펴보자!
아직 취준생인 입장으로서 생각해보면 면접관 입장에서 지원자는 OCP를 적용한 것과 같다고 생각됩니다.
면접관 입장에서는 어떤 지원자가 오던지 면접을 보고 점수를 매기는것에는 변화하는것이 없기때문에
OCP가 잘 적용된 사례라고 생각됩니다.
객체지향 4대원칙 캡!상추다에서 사용된 것은?
앞서 말했던 것처럼 다형성이 핵심인것 같습니다.
다형성을 이용해 유연성, 재사용성, 유지보수성을 얻을 수 있으니 개방폐쇄의 원칙은 꼭지켜져야 합니다.
LSP 리스코프 치환 원칙
서브타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다.
리스코프 치환원칙은 말로기억하기 보다는 그림을통해 기억하는것이 편합니다.
이 그림과 같이 계층도/조직도는 리스코프 치환 원칙을 위반한 사례이고,
이 그림과 같이 분류도는 리스코프 치환 원칙을 적용한 사례라고 할 수 있습니다.
예를 들어보겠습니다.
아버지 춘향이 = new 딸() ;
이라고 한다면 딸을 하나 낳아서 이름을 춘향이라고 한 것까지는 괜찮은데
춘향이에게 아버지의 역할을 맡기고 있습니다.
말이 안되는 상황입니다.
동물 뽀로로 = new 펭귄();
이라고 하면 펭귄의 이름을 뽀로로라 하고 동물의 역할을 맡기고 있습니다.
이것이 리스코프 치환 원칙을 지킨 코드입니다.
결국 리스코프 치환 원칙은 객체 지향의 상속이라는 특성을 올바르게 활용하면 자연스럽게 얻게 되는 것입니다.
객체지향 4대원칙 캡!상추다에서 사용된 것은?
상속입니다.
리스코프 치환원칙은 상속을 잘 사용해라! 라는 원칙입니다!
ISP 인터페이스 분리 원칙
클라이언트는 자신이 사용하지 않는 메서드에 의존관계를 맺으면 안된다.
SRP (단일 책임의 원칙)이 클래스 단위로 역할을 나누는 것이고
ISP (인터페이스 분리 원칙)은 인터페이스 단위로 역할은 나누는 것입니다.
역할을 나누는것은 동일하기 때문에
같은 문제에 대해 개발자의 취향에 따라 두가지 로직중 선택을 할 수 있습니다.
인터페이스 최소주의 원칙
ISP를 적용할때는 최소한의 메서드만 제공하라는 것이 인터페이스 최소주의 원칙입니다.
이전 글에 상속과 인터페이스에서 상위 클래스는 풍성할수록 좋고, 인터페이스는 작을수록 좋다고 했습니다.
아래 그림을 보면 이해하기 쉬울것입니다.
DIP 의존 역전 원칙
고차원 모듈(추상화 된 것)은
저차원 모듈(구체적인 것)에
의존하면 안된다.
이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.
즉, 구체적인 것이 추상화된 것에 의존해야 한다.
자주 변경되는 구체클래스에 의존하지 마라
사실 DIP는 객체지향의 핵심이라고 할 수 있을것 같습니다.
앞서 말씀드린 전략패턴도 DIP를 지키고 있는 패턴중에 하나입니다.
결국 DI(Dependency Injection)을 적용하라는 뜻입니다.
그럼 그림을 통해 설명드리겠습니다.
자동차가 스노우 타이어라는 구현체에 의존하고 있습니다.
이것을 추상화된것에 의존해 보겠습니다.
이제 자동차는 추상적인 타이어에 의존하고 있습니다.
어떤 구현체 (스노우, 일반, 광폭) 타이어를 적용하던지 자동차는 구현체에 신경쓰지 않고 타이어에만 의존하고 있습니다.
의존의방향이 역전된 것입니다.
DIP를 적용하므로써 변경에 자유로운 형태가 되었습니다.
이것은 개방 폐쇄의 원칙(OCP)와 같은 맥락입니다.
자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀 있기 때문입니다.
느낀점
SOLID는 객체지향 4대 특성을 잘 활용한 결과로 나타나는 것입니다.
따라서 객체지향 4대 특성을 잘 이해해야 겠다는 생각이 들었고,
SOLID마다 어쩔수 없이 겹치는 부분이 있다는 것을 알게되었습니다.
참고
[Java] 객체지향 설계 5원칙 - SOLID
객체지향의 설계 5원칙 SOLID에 대해 알아보자
sehun-kim.github.io
'Spring' 카테고리의 다른 글
싱글톤 패턴 (0) | 2023.09.22 |
---|---|
디자인 패턴 (1) | 2023.09.22 |
객체지향의 4가지 특징 (0) | 2023.09.19 |
Bean 등록 방법 (0) | 2023.08.19 |
의존성 주입 방법 (0) | 2023.08.19 |