# Reading/--- 개발서적

[클린 아키텍처] 객체 지향 프로그래밍

jiniz.ll 2021. 4. 19. 20:54

그냥 읽는 것만으로는 여전히 책을 이해하기 힘들기에 정리하는 글..

 

객체 지향에 대한 한 가지 의견

캡슐화 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’ 다