가독성 높은 코드 만들기 방법 #3 – 추상화

OOP 의 정수는 추상화 입니다. 추상화를 잘하는 개발자가 디자인 패턴이라는 달콤한 열매를 따 먹을 수 있습니다.
추상화란 구체적인 동작을 하는 class 들로 부터 공통 Interface을 찾아서 가상 함수를 정의 하는것입니다. 그 가상 함수가 포함된 class 를 추상 class 라고 합니다.

실제 어떻게 하는지 예를 들겠습니다.
아래는 추상화가 안된 Line, Circle, Square 가 있습니다.

class Point{...}

class Line {

	Point start;
	Point end;

	public Line(Point pointStart, Point pointEnd){...}

	public void draw() {...}
}


class Circle {

	Point center;
	int radius;

	public Circle(Point center, int radius){...}
	
	public void draw() {...}
}


class Square {

	Point leftTop;
	Point rightBottom;

	public Square(Point leftTop, Point rightBottom){...}
	
	public void draw() {...}
}


int main() {

	Line line = new Line(new Point(10, 10), new Point(20, 20));
	line.draw();

	Circle circle = new Circle(new Point(30, 30), 10));
	circle.draw();

	Square square = new Square(new Point(15, 15), new Point(60, 30));
	square.draw();
}

위의 코드에서 Line, Circle, Square 의 공통점을 찾으면 “draw 함수가 필요 하다”는 것입니다. 하지만 각각의 내부 구현은 다릅니다.(Line, Circle, Square 를 그리는 알고리즘은 각각 다르겠죠)

이러한 경우 추상 class를 정의하고, Line, Circle, Square가 그것을 상속 받아서 draw 함수 내부 구현을 각각 하는 것입니다.
아래와 같이 추상 class 인 Shape 을 정의 했습니다.

class Shape { // 난 가상 함수를 포함하므로 추상 class

	public virtual void draw(); // 이건 가상 함수, 실제 동작은 없어요!

	// Language 마다 virtual, abstract 등 keyword는 다릅니다.

}

여기서 draw 를 그냥 함수가 아닌 “가상 함수” 라고 말합니다. 즉 아직 구현이 없고 그 형태만 정의된, 다시 말하면 Interface 만 정의된 상태의 함수를 의미 합니다.
가상 함수가 있는 추상 class 는 아래 의미를 내포하고 있습니다.

  • 추상 class 로는 객체를 생성 할 수 없다. 구현이 없는 가상 함수를 포함하고 있기 때문에… 즉 구현이 없는 draw 함수를 호출하는 말도 안되는 상황이 발생하기 때문에.. 아래와 같이 coding 하면 실제 compile error 가 발생합니다.
Shape shape = new Shape(); 
shape.draw() // <- 난 가상함수, 어떻게 동작 하라고??? 
  • 그러므로 추상 class 를 상속받는 Sub class 를 만들어 사용해야 한다.
  • 그 Sub Class 는 가상 함수의 동작을 반드시 구현해야한다.
  • Sub Class 에서 가상 함수의 동작을 구현 했으므로 이제야 비로서 객체를 생성하여 사용 할 수 있다.

아래와 같이 추상화 class 를 적용해 보겠습니다.

class Point {...}

class Shape { //추상 class

	public virtual void draw(); // 가상함수
}


class Line extends Shape {

	Point start;
	Point end;

	public Line(Point pointStart, Point pointEnd){...}

	public override void draw() {	
		// Shape 의 가상함수 draw 를 여기서 구현
	}
}


class Circle extends Shape {

	Point center;
	int radius;

	public Circle(Point center, int radius){...}
	
	public override void draw() {	
		// Shape 의 가상함수 draw 를 여기서 구현
	}
}


class Square extends Shape {

	Point leftTop;
	Point rightBottom;

	public Square(Point leftTop, Point rightBottom){...}
	
	public override void draw() {
		// Shape 의 가상함수 draw 를 여기서 구현
	}
}


int main() {

	ArrayList<Shape> shapeList = new ArrayList<Shape>();

	shapeList.add(new Line(new Point(10, 10), new Point(20, 20));
	shapeList.add(new Circle(new Point(30, 30), 10));
	shapeList.add(new Square(new Point(15, 15), new Point(60, 30));

	foreach(Shape shape : shapeList)
		shape.draw();
}

이제 Line, Circle, Square 의 다른 종류의 class 들이지만, 모두 추상 class인 Shape 에서 상속 받았습니다. 그리고 각각 draw 함수를 따로 구현 했습니다. shape.draw()를 호출하면 그 상속받은 sub class 의 draw 가 호출 됩니다. 아래 코드를

Line line = new Line(new Point(10, 10), new Point(20, 20));
line.draw();

Circle circle = new Circle(new Point(10, 10), 50));
circle.draw();

Square square = new Square(new Point(10, 10), new Point(20, 20));
square.draw();

이렇게도 사용 할 수 있게 되었습니다.

Shape line = new Line(new Point(10, 10), new Point(20, 20));
line.draw();

Shape circle = new Circle(new Point(10, 10), 50));
circle.draw();

Shape square = new Square(new Point(10, 10), new Point(20, 20));
square.draw();


즉 Line, Circle, Square 의 생성된 객체를 모두 Shape 으로 동일한 type 을 받아서 사용 할 수 있습니다.
때문에 이제 한개의 ArrayList<Shape> 에 모든 객체를 담아서 관리가 가능하게 되었습니다.

	ArrayList<Shape> shapeList = new ArrayList<Shape>();

	shapeList.add(new Line(new Point(10, 10), new Point(20, 20)));
	shapeList.add(new Circle(new Point(30, 30), 10)));
	shapeList.add(new Square(new Point(15, 15), new Point(60, 30)));

	foreach(Shape shape : shapeList)
		shape.draw();



이게 무슨 이득이 있을까요?

생성된 모든 객체들을 한번에 draw 하는 함수가 만들어야 된다고 가정 하죠. 추상화가 안된 경우는 ArrayList, ArrayList, ArrayList와 같이 따로 객체를 종류 별로 따로 관리해야 하고, 아래와 같이 구현 해야 합니다.

public void drawAll(ArrayList<Line> lineList, ArrayList<circle> circleList, ArrayList<square> squareList) {

	drawLineAll(lineList);
	drawCircleAll(circleList);
	drawSquareAll(squareList);
}

public void drawLineAll(ArrayList<Line> lineList) {

	foreach(Line line : lineList)
		line.draw();
}

public void drawCircleAll(ArrayList<Circle> circleList) {

	foreach(Circle circle : circleList)
		circle.draw();
}

public void drawSquareAll(ArrayList<Square> squareList) {

	foreach(Square square : squareList)
		square.draw();
}

여기에서 Triangle, Orbit 등 class 추가 된다면, 그 때 마다 drawTriangleAll, drawOrbitAll 을 계속 만들어 주어야 합니다.

추상화가 되었다면 아래와 같이 함수 한개만 구현하면 됩니다.
Triangle, Orbit 이 추가 되어도 draw 를 호출 해주는 함수를 추가/수정이 필요 없습니다.

public void drawAll(ArrayList<Shape> shapeList) {

	foreach(Shape shape : shapeList)
		shape.draw();
}



한개 더 예를 들어 볼까요?
그림판에서 Cicle, Line, Square 들이 그려져 있고 그 위에 마우스로 사각형 영역을 설정하여 그 안에 포함 된 것들만 마우스 드레그 하여 위치를 옮기는 기능을 구현해 보죠.
아래와 같이 두개의 가상 함수(isContain, move)를 추가 후, Cicle, Line, Square 에서 각각구현하면 됩니다.

class MouseRegion {	// mouse 로 선택한 사각영역

	Point leftTop;
	Point rightBottom;	
}

class Shape {

	public virtual void draw();

	// 마우스 사각 영역에 포함 되는지 여부 리턴
	public virtual bool isContain(MouseRegion region); 

	// dx, dy 만큼 위치 이동
	public virtual void move(int dx, int dy);
}

그리고 아래와 같이 호출 하면 됩니다.

public void moveInMouseRegion(ArrayList<Shape> shapeList, MouseRegion region, int dx, int dy) {

	foreach(Shape shape : shapeList){

		if(shape.isContain(region)) {// 사각 영역에 포함된것 이라면...
			shape.move(dx, dy);			// dx, dy 거리만큼 옮겨라.
			shape.draw();									
		}
	}
}

만약 추상화가 안 되었다면 아래와 같이 객체 종류별 각각 따로 구현 되어야 겠죠.

public void moveLineInMouseRegion(ArrayList<Line> lineList, MouseRegion region, int dx, int dy) {

	foreach(Line line : lineList){

		if(line.isContain(region)) {
			line.move(dx, dy);
			line.draw();									
		}
	}
}

public void moveCircleInMouseRegion(ArrayList<Circle> circleList, MouseRegion region, int dx, int dy) {

	foreach(Circle circle : circleList){

		if(circle.isContain(region)) {
			circle.move(dx, dy);
			circle.draw();			
		}
	}
}

public void moveSquareInMouseRegion(ArrayList<Square> squareList, MouseRegion region, int dx, int dy) {

	foreach(Square square : squareList){

		if(square.isContain(region)) {
			square.move(dx, dy);
			square.draw();			
		}
	}
}

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중