5장 객체 지향 프로그래밍
좋은 아키텍처를 만드는 일은 객체 지향 OO 설계 원칙을 이해하고 응용하는 데서 출발한다.
캡슐화?
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 = malloc(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);
}
point.h 를 사용하는 측에서 struct Point 의 멤버에게 접근할 방법이 전혀 없다.
이것이 바로 완벽한 캡슐화이며 OO가 아닌 언어에서도 충분히 가능하다.
OO 프로그래밍은 프로그래머가 충분히 올바르게 행동함으로써 캡슐화된 데이터를 우회해서 사용하지 않을 거라는 믿음을 기반으로 한다. 하지만 OO를 제공한다고 주장한 언어들이 실제로는 C 언어에서 누렸던 완벽한 캡슐화를 약화해 온 것은 틀림없다.
상속?
namedPoint.h (헤더 파일)
struct NamedPoint;
struct NamedPoint* makeNamedPoint(double x, double y, char* name);
void setName(struct 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) {
struct NamedPoint* p = malloc(sizeof(struct NamedPoint));
p->x = x;
p->y = y;
p->name = name;
return p;
}
void setName(struct NamedPoint* np, char* name) {
np->name = name;
}
char* getName(struct NamedPoint* np) {
return np->name;
}
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");
printf("distance=%f\n",
distance(
(struct Point*) origin,
(struct Point*) upperRight));
}
main 프로그램을 살펴보면 NamedPoint 데이터 구조가 마치 Point 데이터 구조로부터 파생된 구조인 것처럼 동작한다는 사실을 볼 수 있다.
OO가 출현하기 이전부터 프로그래머가 흔히 사용하던 기법이었다. 실제로 C++에서는 이 방법을 이용해서 단일 상속을 구현했다.
OO 언어가 고안되기 훨씬 이전에도 상속과 비슷한 기법이 사용되었다 고 말할 수 있다. 하지만 이렇게 말하는 데는 어폐가 있다. 상속을 흉내내는 요령은 있었지만 사실상 상속만큼 편리한 방식은 절대 아니기 때문이다.
다형성?
함수를 가리키는 포인터를 응용한 것이 다형성이다.
함수에 대한 포인터를 직접 사용하여 다형적 행위를 만드는 이 방식에는 문제가 있는데 함수 포인터는 위험하다는 사실이다. 포인터를 초기화하는 관례를 준수해야 한다는 사실을 기억해야 한다. 그리고 이들 포인터를 통해 모든 함수를 호출하는 관례를 지켜야 한다는 점도 기억해야 한다. 프로그래머가 관례를 지켜야 한다는 사실을 망각하게 되면 버그가 발생하고 이러한 버그는 찾아내고 없애기가 힘들다.
OO 언어는 이러한 관례를 없애주며 따라서 실수할 위험이 없다.
다형성이 가진 힘
플러그인 아키텍처는 입출력 장치 독립성을 지원하기 위해 만들어졌고 등장 이후 거의 모든 운영체제에서 구현되었다. 그럼에도 대다수의 프로그래머는 직접 작성하는 프로그램에서는 이러한 개념을 확장하여 적용하지 않았는데 함수를 가리키는 포인터를 사용하면 위험을 수반하기 때문이었다.