Wanna be Brilliant Full-Stack Developer
자바객체지향프로그래밍의 이해 SRP와 DIP 본문
SRP라는것은 무엇인가?
영어로 하면 Single Response Principle (단일 책임 원칙) 이라는 뜻이다.
단일 책임? 책임이라는것이 무엇인지 명확히 이해를 해야한다.
책임은 객체지향프로그래밍에서는 행위라고 한다.
즉 메서드이다.
우리가 집에 구성원이 있는데 엄마가 있고 아빠가 있고 아들과 딸이 있다.
그러면 엄마는 엄마가 집에서 요리도하고 빨래도 하고 청소도하고 3가지를 다해버리면
책임이 너무 많다. 그래서 책임을 분리하는것이다.
엄마는 요리만하고
아빠는 빨래를 하고
딸은 청소를 할수 있다.
책임을 다 분리를 하여 엄마는 요리를 하고 아빠는 빨래를 하고 딸은 청소를 할것이다,
이렇게 되면 장점이 하나 있다. 그러면 집이 더러우면 누구 때문인가?
딸이 문제이다. 딸에게 책임을 물을 수 있다.
음식이 맛이 없으면 엄마의 문제이다.
옷이 더러우면 아빠의 문제이다.
책임을 분리하는 순간 그 책임을 명확하게 누구의 문제인지 지을 수 있다.
그러면 먼가 요리에 문제가 있으면 엄마의 요리 메서드만 수정하면 되고
청소에 문제가 있으면 청소의 메서드만 수정하면 된다.
그게 단일책임 원칙이다. 책임을 단순화 하는것이다.
그런데 책임을 하나만 들고 있는것이아니라 두개를 들고있을 수도 있다.
이렇게 여러가지 책임이 있을 수 있는데 책임을 분리하는 이유는 무엇인가?
정확하게 누구의 책인지 나중에 알 수 있기 떄문이다.
집에 돈이 없으면 아빠의 문제? 공부를 못하면 딸의 문제이다.
그러면 DIP는 무엇인가?
Dependency Ineversion Principle이라고 하여 의존성 역전 원칙이라고 한다.
이게 무슨말인가?
만약에 어떤 자동차가 있다. 이 자동차가 만들어지기 위해서는 바퀴도 필요하고 엔진도 필요하다.
이건 상속의 개념이 아니다. 자동차는 타입의 일치가 되지 않기떄문에 엔진과 바퀴를 상속할 수 없다.
엔진과 바퀴는 여러 종류가 있을 수 있다.
이렇게 종류가 있을떄! 이런 종류들은 다 엔진과 바퀴를 상속하고 있따.
자동차가 바퀴를 만들떄 2000CC를 의존하고 있으면
나중에 이자동차를 3000CC로 바꿔야하는데? 라고 하면 프로그램의 수정이 일어난다.
자동차를 3000CC라고 수정을 해야한다.
근데 내가 의존을 할떄 자동차가 태초에 부모(엔진)를 의존하고 있으면
말이 달라진다. 2000CC , 3000CC로 바꾸든 이 프로그램의 수정이 일어날 필요가 없다.
코드 수정이 필요 없다.
그러면 도대체 이런 원칙이 좋은거 같은데!
처음에 만들떄 잘 만들면 원칙이 중요하지 않은거 아닌가 생각할 수 있는데?
이게 왜중요하는가 하면 프로그램을 만들떄 처음부터 완벽하게 만들 수 없다.
스타크래프트만 봐도 다 업그레이드가 된다.
안드로이드만봐도 계쏙 버전이 발전이 된다.
프로그램은 처음부터 완벽하게 만들 수가 없기 떄문에 우리는 프로그램을 만들떄
CI를 고민해야한다. CI가 무엇인가? Continuous Integration
지속적 통합이라는 뜻이 있다.
계속 적으로 업데이트가 되어야한다는 것이다.
프로그램을 만들떄 CI는 필수이다.
지속적으로 통합할떄 내가 프로그램을 SRP의 원칙과
DI의 원칙을 잘 지켜서 만들면 수정을 편안하게 할수 있다는 것이다.
이걸 안지킨다면 수정이 굉장히 어렵다.
SRP와 DIP를 실습을 한번 해보자!
실제로 new를 할 수 있는 홍길동이 있고 임꺽정이 있고 김유신이 있고 그리고 이몽룡, 장보고 이순신, 이방원이 있다.
홍길동과 임꺽정은 종업원이다.
그리고 김유신과 이몽룡은 캐셔이다.
장보고와 이순신은 요리사이다.
이방원은 사장님이다.
그리고 종업원과 캐셔는 홀 직원이다.
이렇게 추상화를 했다. 홍길동은 종업원이고 홀직원이고 홍길동이다.
3가지의 이름을 다 가진다.
임꺽정도 임꺽정이기도 하고 종업원이기도 하고 홀직원이기도 하다.
장보고는 요리사이다.
이런 구성이 있을때 책임을 적어보자!
홍길동은 종업원이니까 서빙을 하고 주문도 받는다.
임꺽정도 마찬가지이다.
김유신과 이몽룡은 계산을 한다. 계산도 하면서 매일 정산을 한다.
장보고는 요리를 한다. 이순신도 요리를 하고
이방원은 사장님이니까 업무지시를 한다.
이렇게 각자의 책임이 명확히 분리 되어 있다.
이렇게 프로그램을 만들었을떄 문제가 무엇인가?
원래 계산을 어떻게 했냐면 계산을 할때 현금계산을 했는데
갑자기 카드 결제로 변경이 되었다고 하면 그러면 메서드를 몇개 바꿔야하는가?
2개이다.
이런걸 머라고 말하는가? 책임이 분산되어 있다고 한다.
추상 클래스가 계산을 들고 있으면 굉장히 편하지 않을까? 어차피 계산은 로직이 똑같으니까
구현을 미룰 필요도 없고 얘가 구현을 해버리면된다.
이렇게 되면 추상클래스만 수정하면 된다. 굉장히 편안해진다.
정산도 마찬가지이다.
정산도 원래는 정산을 할떄 손으로 수기를 했는데 이제 계산기로 정산을 하게 되면 편안하겠지만
프로그램 로직이 바뀔때 두군대를 변경하는게 아니라 정산을 위로 올려서
추상클래스가 관리하게 되고 위에것만 변경하면 된다.
그다음에 서빙을 하고 주문을 하는것 또한
주문을 받는것을 하는데 키오스크로 변경이 되어서 종업원이 주문 메서드가 더이상 필요 없다.
이제 홀서빙만 하면 된다. 그리고 서빙만 하면 되니까 저 두개만 날리면 되니까 지금은 실수를 안하지만
이런 직원이 수천명이 되버리면 실수가 될수 있기 떄문에 이 서빙을 또 위로 올려서
내가 어떻게 할 수 있는가? 서빙을 삭제만 해버리면 된다. 그러면 끝난다.
주문도 마찬가지로 위쪽으로 올려서 관리를 하면 된다.
근데 요리는 조금 다르다. 요리사는 장보고는 어떤 요리를 하냐면
양식을 하고 이순신은 한식을 한다. 요리하는것이 조금 다르다.
그러면 부모의 추상 메서드를 만들고 자식이 직접 구현을 해줘야한다.
그러면 애는 완벽하게 요리사 입장에서 구현할 수 없다.
요리사를 상속하는 자식들마다 다르게 행위를 할것이기 떄문이다.
요리사와 종업원은 둘이 서로 의존을 해야한다.
서로 의존관계이다. 종업원이 요리사를 의존하게 되면 굉장히 편안하다.
만약에 홍길동이 요리사를 의존하는것이 아니라 장보고를 의존하게 되면?
의존한다는것이 어떤 뜻인가? 홍길동이 장보고한테 음식좀 만들어 요청을 해야한다?
어떤 음식? 장보고는 양식이니까 스파게티를 만들어줘라고 하는 서로 의존관계가 있다.
근데 이렇게 코드를 짜버리면 DIP원칙에 위배가 된다. 나중에 코드 수정이 굉장히 많아진다.
왜냐하면 장보고가 일을 그만두고 정몽주가 태어난다고 하면 정몽주가 요리를 시작하게 되면
애를 정몽주로 바꿔야하면 코드 수정이 일어나게 된다.
그래서 의존을 하게되면 구체적인것을 의존하는것이 아니라 추상적인것을 의존해야한다.
마지막으로 홀직원은 다같이 청소를 한다고 하자!
이 청소를 구현할 수 있으면 구현을 하면 되는데 홍길동은 화장실 청소를 하고
임꺽정은 주방청소를 하고 김유신은 홀청소를 하고, 이몽룡은 테이블 청소를 하는데
이렇게 구체적인 행위가 다르기 떄문에 청소를 추상으로 만들어야한다.
여기에 하나를 더 추가할것인데 인터페이스!
어떤 인터페이스인가? 행위에 대한 제약을 줄것이다.
종업원은 talk 손님한테 이야기할 수 있다.
종업원과 캐셔는 talk를 할 수 있는데 요리사는 talk를 할 수 없다.
이렇게 의존성을 두면 위험하다. 왜냐하면 장보고가 일을 그만두면
엄청나게 프로그램이 복잡해진다.
여기에 장보고가 아니라 요리사에 의존을 한다.
추상적인것에 의존한다는것이다.
책임을 완벽하게 분리하다.
이 상태에서 시나리오를 짜보자.
계산이 현금계산에서 카드계산으로 바뀌었다.
그럼 이 한군데에서만 수정하면 된다.
정산도 수기정산에서 계산기 정산으로 변경하면 된다.
주문도 키오스크로 받으면 주분받기를 주석처리하면 된다.
한군데만 수정하면 되니 아주 심플하다.
마지막으로 의존을 하는데
장보고에서 정몽주로 바뀌었다. 근데 상관이 없다.
장보고 대신에 정몽주를 하나 만들면 된다.
이렇게 해도 오류가 나지 않는다. 왜냐하면 의존을 요리사에 하고 있기 때문이다.
package ch05.srpdip;
interface CanAble {
public abstract void talk();
}
abstract class 홀직원 implements CanAble{
abstract void 청소();
@Override
public void talk() {
System.out.println("손님과 대화");
}
}
abstract class 종업원 extends 홀직원{
void 서빙하기() {
System.out.println("서빙하기");
}
// void 주문받기() {
// System.out.println("주문받기");
// }
}
abstract class 캐셔 extends 홀직원 {
void 정산하기() {
System.out.println("계산기 정산하기");
}
void 계산하기() {
System.out.println("카드 계산하기");
}
}
abstract class 요리사 {
abstract void 요리();
}
class 홍길동 extends 종업원{
요리사 j; // 의존성 역전 원칙
@Override
void 청소() {
System.out.println("화장실 청소");
}
}
class 임꺽정 extends 종업원{
요리사 j; // 의존성 역전 원칙
@Override
void 청소() {
System.out.println("주방청소");
}
}
class 김유신 extends 캐셔{
@Override
void 청소() {
System.out.println("홀청소");
}
}
class 이몽룡 extends 캐셔{
@Override
void 청소() {
System.out.println("테이블청소");
}
}
//class 장보고 extends 요리사{
//
// @Override
// void 요리() {
// System.out.println("양식 만들기");
// }
//}
class 정몽주 extends 요리사{
@Override
void 요리() {
System.out.println("양식 만들기");
}
}
class 이순신 extends 요리사{
@Override
void 요리() {
System.out.println("한식 만들기");
}
}
public class OOPEx10 {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
인터페이스는 implements로 가져온다!
package ch05;
interface RemoconAble {
public void 초록버튼();
public void 빨간버튼();
}
class Samsung implements RemoconAble{
@Override
public void 초록버튼() {
System.out.println("전원이 켜졌습니다.");
}
@Override
public void 빨간버튼() {
System.out.println("전원이 꺼졌습니다.");
}
// 내부를 만드시오.
}
class LG implements RemoconAble{
@Override
public void 초록버튼() {
System.out.println("전원이 켜졌습니다.");
}
@Override
public void 빨간버튼() {
System.out.println("전원이 꺼졌습니다.");
}
}
public class ExampleEx01 {
public static void main(String[] args) {
// 삼성 리모콘 2개 만들기 (new)
Samsung s1 = new Samsung();
Samsung s2 = new Samsung();
// 엘지 리모콘 2개 만들기 (new)
LG g1 = new LG();
LG g2 = new LG();
}
}
'Some Memos > Java' 카테고리의 다른 글
자바 2차원 배열이란? (0) | 2023.03.14 |
---|---|
자바 배열이란? (0) | 2023.03.13 |
자바 객체지향 프로그래밍 인터페이스와 추상클래스의 차이 (0) | 2023.03.12 |
자바객체지향프로그래밍 인터페이스 (0) | 2023.03.08 |
자바 객체지향프로그래밍 추상 클래스 (0) | 2023.03.06 |