그냥 읽는 것만으로는 여전히 책을 이해하기 힘들기에 정리하는 글..
객체 지향에 대한 한 가지 의견
→ 캡슐화 encapsulation, 상속 inheritance, 다형성 polymorphism
캡슐화 Encapsulation
- 데이터와 함수를 쉽고 효과적으로 캡슐화하는 방법을 제공하기 때문
- 구분선 바깥에서 데이터는 은닉되고(private), 일부 함수만이 외부에 노출(public)
But, C 언어에서도 완벽한 캡슐화가 가능함
// point.h
struct Point;
struct Point* makePoint(double x, double y);
double distance(struct Point *p1, struct Point *p2);
// point.c
#include "point.h"
#include <stdlib.h>
#include <math.h>
struct Point {
double x, y;
}
struct Point* makePoint(double x, double y) {
struct Point* p = maloc(sizeof(struct Point));
p->x = x;
p->y = y;
return p;
}
double distance(struct Point* p1, struct Point* p2) {
double dx = p1->x - p2->x;
double dy = p1->y - p2->y;
return sqrt(dx*dx + dy*dy)
}
[클린 아키텍처 p.39 코드 샘플]
완벽한 캡슐화
→ makePoint() 함수와 distance() 함수를 호출할 수는 있지만 Point 구조체의 데이터 구조와 함수가 어떻게 구현되었는지 알 수 없음
→ C++ → Java, C# 으로 가면서 오히려 클래스 선언과 정의(헤더와 구현체)를 구분하는 것이 불가능해짐(약해짐)
즉, 오히려 OO가 강력한 캡슐하에 의존하지 않음 (캡슐화를 강제하지 않음)
상속 Inheritance
- 상속 : 어떤 변수와 함수를 하나의 유효 범위로 묶어 재정의 하는 것
C 에서도 상속과 유사한 기법이 사용되었었음
But, 상속만큼 편리한 방식은 아니긴 함
// namedPoint.h
struct NamedPoint* makeNamedPoint(double x, double y, char* name);
void setName(strcut NamedPoint* np, char* name);
char* getName(struct NamedPoint* np);
// namedPoint.c
#include "namedPoint.h"
#include <stdlib.h>
struct NamedPoint {
double x, y;
char* name;
}
struct NamedPoint* makeNamedPoint(double x, double y, char* name) {
...
}
void setName(strcut NamedPoint* np, char* name) {
...
}
char* getName(struct NamedPoint* np) {
...
}
// main.c
#include "point.h"
#include "namedPoint.h"
#include <stdio.h>
int main(int ac, char** av) {
struct NamedPoint* origin = makeNamedPoint(0.0, 0.0, "origin");
struct NamedPoint* upperRight = makeNamedPoint(1.0, 1.0, "upperRight);
// NamedPoint인자를 Point타입으로 강제 변환하여 사용함
printf("distance=%f\n", distance((struct Point*) origin, (struct Point*) upperRight));
}
[클린 아키텍처 p.42-43 코드 샘플]
→ NamedPoint 에 선언된 두 변수(x, y)의 순서가 Point와 동일하여 NamedPoint가 Point를 포함하는 상위 집합처럼 사용할 수 있음
→ OO가 출현하기 이전에 사용하던 방식
→ C++ 은 이 방법을 이용하여 단일 상속을 구현
즉, OO가 상속에 대해서는 상당히 편리한 방식을 제공하지만, 상속과 유사한 개념에 대해서는 이전부터 사용해왔음
다형성 Polymorphism
- 다형성이란?
: 하나의 객체가 여러 가지 타입을 가질 수 있는 것
다형성 역시 OO 언어 이전에 다형성을 표현할 수 있는 언어가 있었음
#include <stdio.h>
void copy() {
int c;
while ((c=getchar()) != EOF)
putchar(c);
}
[클린 아키텍처 p.44 코드 샘플]
책 내용 중..
getchar() 함수는 STDIN에서 문자를 읽고, putchar() 함수는 STDOUT으로 문자를 쓴다. 그러면 STDIN은 어떤 장치이며, STDOUT은 어떤 장치인가?
이러한 함수는 다형적polymorphic이다.
즉, 행위가 STDIN과 STDOUT의 타입에 의존한다.
→ ?????
유닉스 운영체제의 경우 모든 입출력 장치 드라이버가 다섯 가지 표준 함수를 제공할 것을 요구하는데, 열기(open), 닫기(close), 읽기(read), 쓰기(write), 탐색(seek) 이 그것이다.
FILE 데이터 구조는 이 다섯 함수를 가리키는 포인터를 포함한다.
그렇다면 STDIN을 FILE*로 선언하면, STDIN은 콘솔 데이터 구조를 가리키므로 getchar()는 다음과 같은 방식으로 구현할 수 있다.
extern strcut FILE* STDIN;
int getchar() {
return STDIN->read();
}
[클린 아키텍처 p.45 코드 샘플]
즉, getchar()는 STDIN으로 참조되는 FILE 데이터 구조의 read 포인터를 가리키는 함수를 단순히 호출할 뿐
→ 이러한 기법이 OO가 지닌 다형성의 근간임
즉, 함수를 가리키는 포인터를 응용한 것이 다형성
But, 함수 포인터는 위험
→ OO는 이러한 위험성을 없애줌
다형성이 가진 진가
- 플러그인 아키텍처 plugin architecture
: 입출력 장치 독립성 device independent을 지원하기 위해 만들어짐
→ OO는 이것을 적용하기 쉽게 해줌
여기까지 살펴보면
OO가 딱히 새롭게 만든것은 없다
즉 객체 지향 프로그래밍에 대해 이야기할 때, 이 세 가지로 표현하기에는 무언가 부족한 부분이 있음
캡슐화 encapsulation, 상속 inheritance, 다형성 polymorphism 은 OO의 핵심이 아니라 매커니즘이다
의존성 역전
이 부분에 대해서 어렴풋이 이해는 하고 있었지만 그럼에도 책을 통해 이해하는 데는 한계가 있었음..
아래 블로그 링크가 이해하기 쉽고, 영상을 보면 책에 설명된 의존성 역전에 대해 이해하는데 도움이 됨 (뒷부분으로 갈수록 집중이 잘 안됐지만..😂)
(물론 책에 SOLID 원칙의 DIP를 위한 챕터가 따로 마련되어 있지만, 객체 지향을 설명하는데 의존성 역전이 빠질 수 없다하여 먼저 짚고 가보려 한다!)
책과 영상을 믹스해서 정리해보자면
구조적 설계 Structed Design 에서는
- Top-Down 방법론
: 소스 코드 의존성 방향 = 런타임 의존성 방향
→ 런타임 의존성 방향은 어쩔 수 없지만 소스 코드 의존성 방향이 역전 되어야 함
- DIP
: 고수준 정책 High Level Policy 은 저수준 상세 Low Level Details 에 의존하면 안된다
: 둘은 Abstract Type 에 의존해야 함
소스 코드에서 HL1 모듈은 인터페이스(런타임에는 존재하지 않음)를 통해 F() 를 호출
But 간접적으로 HL1은 ML1 모듈의 F() 함수를 호출하는 것처럼 이해할 수 있음
여기서 ML1과 I 인터페이스 사이의 소스코드 의존성(상속 관계)이 제어흐름과 반대인 것이 중요
- HL1은 interface 가 변하지 않는 한 ML1 의 변화로부터 자유로움
- ML1 의 구체적인 변경으로부터 상위레벨인 HL1을 보호하는 것
=> 의존성 역전 dependency inversion
- OO 언어가 다형성을 안전하고 편리하게 제공한다는 사실은, 소스 코드 의존성을 어디에서든 역전시킬 수 있다는 의미
- 소프트웨어 아키텍트는 소스 코드 의존성을 원하는 방향으로 설정할 수 있음
→ OO 가 지향하는 것
결과적으로
OO란 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어 권한을 획득할 수 있는 능력임!
이라고 함
(마지막 한 장은 나중에 다시 읽어봐야겠음)
[영상 추가 내용]
- IoC(Inversion of Control) 를 통해 상위 레벨의 모듈을 하위 레벨의 모듈(계속 수정될 수 있음)로 부터 보호하는 것
- 객체 지향의 핵심은 ‘의존성 관리 dependency management’ 다
'# Reading > --- 개발서적' 카테고리의 다른 글
[Clean Code] 6장. 객체와 자료 구조 (0) | 2022.03.01 |
---|---|
[Clean Code] 5장. 형식 맞추기 (0) | 2022.02.28 |
[Clean Code] 4장. 주석 (0) | 2022.02.25 |
[Clean Code] 3장. 함수 (0) | 2022.02.23 |
[클린 아키텍처] 구조적 프로그래밍 (0) | 2021.04.12 |