일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Eclipse
- 파이썬
- Spring
- Network
- 백준
- functional programming
- Collections
- design-pattern
- 함수형 프로그래밍
- javscript
- 자바
- 람다 칼큘러스
- JDBC
- 프로그래머스
- 큐
- solid
- 겨울카카오인턴
- Collection
- JavaScript
- lambda calculus
- exception
- DesignPattern
- Pattern
- 로버트마틴
- 디자인패턴
- tcp
- Rails
- 스택
- Java
- Python
- Today
- Total
개발자 노트
추상화 구조에 대해 본문
개요
아키텍쳐에 대해 고민했는데요, 추상화에 의존하는 부분을 어중간하게 알고 있는 것 같았습니다. 머리에 구체적으로 그려지지 않았죠.
그러다가 스프링의 controller - service - model에서 서비스 인터페이스의 존재 이유에 대해 답변하는 날이 있었습니다.
스프링에서 왜 서비스 인터페이스를 사용해야 하는가? 아니면 왜 사용하지 말아야 하는가?
에 대해서 말이죠.
흐음... 물론 컨트롤러에서 특정 서비스에 의존하지 않고 다형성을 주기 위해 인터페이스를 작성할 수 있긴한데, 보통 gient service class가 생성되므로, 책임이 많아진 service에 대해서는 추상화가 의미가 없다.라고 했는데, 찝찝하더라구요.
아리까리한 느낌을 해결하기 위해 고민하다가 어중간함을 없애고 끝에 닿은 결론을 써보려고 합니다. 작성할 주제는 크게 3가지입니다.
다형성, 의존성 주입, 예제의 일반화입니다.
다형성(polymorphic)
인터페이스를 작성하는 주요 이유는 1가지라고 생각이 드네요. 대단하게 생각했는데 결국 이 한가지 밖에 없는 것 같습니다.
바로, client class가 특정한 클래스에 의존하는 일이 없도록 하는 겁니다. DIP의 copy 예제를 들어보겠습니다. 잊으실 수 있으니 DIP에서 설명했던 부분을 잠깐 언급해보겠습니다.
Copy 예제
Copy모듈은 키보드 모듈로 입력된 값을 복사하여 Printer 모듈로 출력하는 예제입니다.
Keyboard만 따져보겠습니다.
Copy모듈에 있서 Keyboard모듈사용 코드는 다음과 같겠습니다.
void Copy() {
int c;
while ((c = ReadKeyboard()) != EOF)
WritePrinter(c);
}
오... 여기까진 좋아보입니다. 추후 확장하고 싶습니다. 어떻게 확장하냐면, keyboard뿐만이 아니라 조이스틱으로 입력된 값 또한 복사하여 반환시켜주고 싶죠.
여기서 취할 수 있는 조치는 Copy모듈에서 keyboard냐, 조이스틱이냐에 따라서 읽는 메서드를 다르게 실행할 수 있을 수 있죠.
enum InputputDevice {keyboard, joystic};
void Copy(iutputDevice dev) {
int c;
if (dev == keyboard)
while ((c = ReadKeyboard()) != EOF)
writePrint(c);
else:
while ((c = Readjoystic()) != EOF)
writePrint(c);
}
이 양상을 보시면 ReadKeyboard와 Readjoystic을 Copy모듈에 작성함으로써 의존성이 증가하고 있습니다.
바로 이 부분 때문에 Copy모듈을 joystic만 사용하고 app에 재사용 하고싶더라도 ReadKeyboard
부분 때문에 keyboard모듈도 같이 데리고 다녀야 합니다. 그리고 입력 모듈을 새로 추가한다하면 Copy모듈 또한 if else로 수정해줘야 합니다.
그래서 DIP에서 제안하는 방법은 결국 다음처럼 말할 수 있습니다.
추상화
keyboard와 joystic을 무엇으로 추상화시킬 수 있을까요? 다시보면 inputDevice라고 볼 수 있죠? 그리고 기능은 각 모듈에 입력된 값을 반환해주는 것입니다.
따라서 다음처럼 추상화시킬 수 있게 되는 것이죠
class Reader {
public:
virtual int Read() = 0;
};
Reader란 놈은 keyboard와 joystic의 추상화된 클래스입니다. 새로 추가한다하더라도, inputDevice를 새로 추가한 것이므로 Reader class의 기능을 그대로 사용할 것 입니다. 세부구현만 바뀔 뿐이죠.
정리
따라서, 결국 다음과 같이 정리할 수 있을 것 같습니다.
1. copy가 1개의 모듈만 사용한다면 추상클래스는 필요없다.
Copy모듈을 쓴다는 뜻은 keyboard를 인풋으로만 받는다는 뜻이 되겠으니까요. 의존관계 적당하네요.
2. 동일 '타입'의 모듈을 사용해야 한다면 추상 클래스에 의존하도록 한다.
동일 타입인 inputDevice를 여러 개 사용해야 한다면 추상클래스에 의존해야 하구요. 타입이 같다는 뜻은 성향, 특성, 유형...등이 동일하다라고 볼 수 있을 것 같습니다.
결론
결론적으로, 의존하는 동일 타입의 모듈의 수와 에 따라 추상클래스 작성여부를 결정해야 한다. 라고 말할 수 있겠습니다.
이렇게 추상화에 대해 애매한 구름을 걷혀 냈습니다.
의존성 주입
그렇다면 Copy모듈에서 keyboard를 어떻게 사용해야 할까요?
void Copy(Reader& r, Writer& w) {
int c;
while((c=r.Read()) != EOF)
w.Write(c);
}
copy모듈은 이렇게 추상 클래스에만 의존하고 있잖아요? 도대체 어떻게 Reader& r
에 keyboard를 연관시킬 수 있냐는 겁니다.
문제 인식
- copy모듈이 직접 연결에 나선다면 keyboard에 종속적이게 될겁니다.
- keyboard가 나선다면 copy에 종속적이게 되겠죠... 코드가 가히 상상안되네요.
제 3자의 필요성
제 3자가 copy의 r과 Reader의 구체적인 클래스인 keyboard를 연결시켜주어야만 한다는 겁니다.
결국 이게 뭘까요? 바로 의존성 주입(Dependency Injection)해줄 누군가가 필요하다는 뜻입니다.
제 3자의 의존성
애매함의 대부분 원인이 바로 요놈이였던 것 같습니다. 제 3자는 Copy모듈도 알아야하고, Keyboard모듈도 알아야 합니다. 그래야만 Copy에 Keyboard를 주입할 수 있잖아요?
결국엔 이 제 3자가 의존성이 높아질 수 밖에 없습니다... 우리는 Copy모듈과 Keyboard 모듈간 의존성은 낮췄지만, 제 3자는 의존성이 높아지는 코드를 가질 수 밖에 없죠.
이 부분을 명심하시어 의존성 관리를 해야할 것 같네요.
factory method pattern이러한 의존성 관리 중 하나라고 볼 수 있고, 의존성이 강해질 수 밖에 없기 때문에 DI framework가 따로 있기도 하겠죠.
정리
1. Copy와 Keyboard를 연결시켜 줄 제 3자가 필요하다.
2. 의존성을 연결시켜줄 제 3자는 Copy와 Keyboard모듈에 대해 의존성 이 높을 수 밖에 없다.
의존성 주입의 또 다른 역할
우선 Copy와 Joystic을 클래스라고 볼께요. 만약에 copy 클래스가 Joystic 객체를 생성 함으로써 직접 참조했다고 생각해볼께요. Copy 모듈이 Joystic을 사용하길 원하니까요.
이때, 그나마 Copy가 Joystic만 알면 양반인데... Joystic이 다른 객체와 상호작용하고 있다면요? Joystic이 다른 객체들과 같이 협업하는 거에요. 그렇다면 Joystic의 생성자는 다음과 같겠죠.
public Joystic(TypeA a, TypeB b, TypeC c);
Copy 모듈에서 Joystic을 생성해볼까요?
Copy 모듈은 협업할 때 전혀 상관없는 a,b,c객체까지 초기화하여 Joystic을 생성해야하는 문제가 발생합니다!
a,b,c 객체를 초기화해야 한다는 것은 결국 a,b,c에도 의존하게 된다는 거죠. 정말 끔찍합니다. ㅠㅠ
그래서 의존성을 주입해주는 제 3자가 a,b,c까지 참조하여 Joystic을 생성해야만 Copy모듈이 Joystic, a,b,c에 의존하지 않는 코드가 될 수 있습니다.
다시 말해서, 의존성 주입은 client가 관심 밖의 타입에 신경쓰지 않을 수 있게 해주는 역할도 가지고 있죠 ^^ 기가 막히죠.
일반화
위와 같이 생각을 하다보니깐 이것도 저것도 위와 같이 생각할 수 있겠구나...하는 예제를 소개시켜드리겠습니다.
지극히 개인적인 의견이므로 비판적으로 수용해주시길 바랍니다.
1. 함수의 사용
상품의 부가세액을 계산한다고 생각해볼게요.
상품의 가격은 2000원이며, 부가세는 10%입니다.
def getVat:
return 2000*0.1
getVat()
를 실행하면 상품 가격이 2000원일 때의 부가세액을 구할 수 있습니다.
하지만, 상품가격이 2000원짜리만 있을리는 거의 없겠죠? 그래서 다양한 상품가격에 대해 구하고 싶은 겁니다. 이 함수의 문제는 concrete한 2000원짜리 상품가격에 의존하고 있습니다.
다형성에서 봤던대로 단일 상품가격이 아닌, 2000원, 3000원에 대한 상품가격을 구하고 싶던거에요. 그러면 다음처럼 함수를 재작성할 수 있습니다.
def getVat(price):
return price*0.1
이제 상품가격 2000원, 3000원... 짜리 등을 추상화시킨 price 변수를 선언하였습니다!
getVat함수가 추상화된 함수가격에 의존하게 되었습니다.
그리고, 특정 가격으로 의존성 주입을 시켜주어야겠지요?
getValue(3000)
getValue함수에 3000이라는 특정 값을 적용함으로써 price-> 3000으로 의존성을 주입해주었습니다!
오... 함수에서도 의존성 주입에 대해 논할 수 있다니! 색다른 관점을 얻었죠.
2. CPU scheduling
CPU scheduling에서 dispatcher가 context switching해주잖아요? 이 dispatcher의 존재가 너무 와닿지 않은겁니다. 왜 필요하지? 싶은거죠.
그런데, 위에서 정리한 내용대로 말하자면, 다음처럼 말할 수도 있을 것 같아요. process를 처리하는 코드에서 PBC를 직접 건들지 않고, dispatcher가 PCB의 context를 주입해줌으로써 의존성을 관리해주는 것이죠.
그렇다면 dispatcher가 왜 있어야 하는지 납득이 가더라구요.
3. kubernetes의 PVC
이것도 위와 같아요... pod에서 특정 PV만 사용할 것이 아니니 PVC인 추상에 의존하고, 누군가가 연결시켜주어야겠지요?
master watches에 있는 controller loop에서 binding즉, 의존성 주입을 해주네요.
끝을 맺으며...
위와 같이 정리함으로써 애매했던 부분들이 많이 사라진 것 같습니다. 이젠 대답을 잘 할 수 있을 것 같네요.
당연한데 왜이리 장황하게 설명했느냐 할 수도 있겠습니다만... 저는 위처럼 생각함으로써 이론으로 그쳐있던 문제를 현실과 맺을 수 있는 계기가 되었던 것 같습니다.
(이것저것 게시판에 작성했던 물리 학문과 비슷한 경우로요)
'이것저것' 카테고리의 다른 글
키보드로 입력한 값이 콘솔로 출력될 때까지 (0) | 2021.07.03 |
---|---|
Exception handling 여정 (0) | 2021.07.03 |
올해 공부하고 싶은 것 (0) | 2021.01.12 |
학습에 대해 - 추상적인 관념과 구체적인 현상 사이에서 (0) | 2020.06.21 |
프로젝트와 효율성(작성중) (0) | 2020.05.19 |