좋은 아키텍처를 만드는 일은 객체 지향 설계 원칙을 이해하고 응용하는 데서 출발한다.
객체 지향이란?
- 데이터와 함수의 조합
- 실제 세계를 모델링하는 방법
- 캡슐화
- 상속
- 다형성
캡슐화?
OO가 데이터와 함수를 쉽게 캡슐화하는 방법을 제공한다고 말하는데, 이는 잘못된 설명이다. OO를 기반으로 설계되지 않은 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 <stclib.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 를 사용할 때 Point의 멤버 x와 y에 접근할 수 있는 방법이 없고, 어떤 이름으로 선언되어 있는지 전혀 없다(x와 y일 것으로 추론만 할 수 있을 뿐). 완벽한 캡슐화가 이루어졌다.
이제 OO를 지원하는 C++ 언어를 살펴보자.
point.h
class Point {
public:
Point(double x, double y);
double distance(const Point& p) const;
private:
double x;
double y;
}
point.cc
#include "point.h"
#include <math.h>
Point::Point(double x, double y)
: x(x), y(y)
{}
double Point::distance(const Point& p) const {
double dx = x - p.x;
double dy = y - p.y;
return sqrt(dx*dx+dy*dy);
}
이제 point.h를 사용하는 사람은 Point의 멤버 변수인 x와 y를 알게 된다. 물론 직접 사용할 수 있는 방법은 없겠지만 멤버 변수가 존재한다는 사실을 알게 된다. 멤버 변수 이름이 바뀐다면 point.cc 파일을 다시 컴파일해야 한다.
OO가 강력한 캡슐화에 의존한다는 정의는 받아들이기 힘들다. 실제로 많은 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 np = malloc(sizeof(struct NamedPoint));
np->x = x;
np->y = y;
np->name = name;
return np;
}
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.c 에서 NamedPoint를 Point의 상속인 것처럼 사용하고 있다. OO 언어에서는 (struct Point*)
로 표현된 업캐스팅을 암묵적으로 지원해줄 뿐이다.
다형성?
OO의 핵심 개념이다. 사실 C 언어에서도 다형성을 제공할 수는 있다. 하지만 OO 언어가 다형성을 좀 더 안전하고 편리하게 사용할 수 있게 해준다.
소프트웨어 아키텍트의 관점에서 OO는 다형성을 제공하고 다형성은 고수준 -> 저수준으로 이동하는 의존성을 (인터페이스를 사용함으로)역전하게 만들 수 있다.