기본 개념
데이터 구조(Data Structure)란 오직 데이터를 보관하기 위해서만 구현되는 특수한 클래스를 의미합니다. 즉, 순수한 모델(Pure Model)로서 Car(자동차), Kid(아이), Animal(동물), Event(이벤트), Employee(직원), Company(회사), Customer(고객) 등이 대표적인 예입니다. 이러한 데이터들은 일반적으로 다른 클래스의 인스턴스 변수로 선언되거나 취급됩니다.
데이터 구조 클래스의 메서드는 실질적으로 중요한 작업을 수행해서는 안 됩니다. 만약 실제 로직을 수행한다면 그 클래스는 더 이상 데이터 구조라고 할 수 없습니다.
따라서 메서드는 주로 게터(getter)와 세터(setter), 즉 접근자(accessor)와 변경자(mutator)로 구성됩니다. 일반적으로 인스턴스 변수가 private으로 선언되기 때문입니다. 다만 데이터 구조의 변수는 public으로 선언하고 클래스 인스턴스에서 직접 접근할 수 있어야 한다는 대안적인 견해도 있으며, private 변수 개념이 더 바람직하다는 주장에 대해서는 논쟁의 여지가 있습니다.
- 이러한 맥락에서 데이터 구조 클래스는 자신의 데이터(변수)를 외부에 노출하며, 의미 있는 메서드나 함수는 갖지 않습니다.
- 반면 MainActivity, ListAdapter, Calculator, Iterator 같은 일반 클래스(객체)는 자신의 데이터를 숨기고, 그 데이터를 다루는 메서드를 노출합니다.
결국 문제를 해결하는 두 가지 접근 방식이 있습니다. 첫째, 데이터 구조를 가장 순수한 형태로 구현하고 별도의 객체 클래스를 만들어 해당 데이터에 대한 연산을 수행하는 방식입니다. 둘째, 모델 클래스를 객체 클래스로 만들어 데이터는 숨기고 메서드만 노출하는 방식입니다. 아래 예시를 통해 살펴보겠습니다.
public class Square {
public Point topLeft;
public double side;
}
public class Rectangle {
public Point topLeft;
public double height;
public double width;
}
public class Circle {
public Point center;
public double radius;
}
public class Geometry {
public final double PI = 3.141592653589793;
public double area(Object shape) throws NoSuchShapeException {
if (shape instanceof Square) {
Square s = (Square) shape;
return s.side * s.side;
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return r.height * r.width;
} else if (shape instanceof Circle) {
Circle c = (Circle) shape;
return PI * c.radius * c.radius;
}
throw new NoSuchShapeException();
}
}이 해법에서는 도형(Square, Rectangle, Circle)들이 데이터 구조로 취급되고, Geometry 클래스가 객체로 취급됩니다.
장점 — 새로운 메서드를 추가해야 할 때 Geometry 클래스에만 추가하면 됩니다. 바로 이런 경우에 이 해법이 적합합니다.
단점 — 새로운 데이터 구조(즉, 새로운 도형)를 추가해야 할 때는 Geometry 클래스의 모든 메서드를 수정해야 하는 부담이 있습니다.
데이터 구조와 객체의 차이점
- 객체(Object)는 동작(behavior)은 노출하고 데이터는 숨깁니다. 덕분에 기존 동작을 변경하지 않고도 새로운 종류의 객체를 추가하기 쉽습니다. 반면, 기존 객체에 새로운 동작을 추가하기는 어렵습니다.
- 데이터 구조는 데이터를 노출하며 중요한 동작은 갖지 않습니다. 덕분에 기존 데이터 구조에 새로운 동작(함수)을 추가하기는 쉽지만, 기존 함수에 새로운 데이터 구조를 추가하기는 어렵습니다.
정리하자면, 새로운 데이터 타입 추가가 잦은 시스템에는 객체 지향 설계가 유리하고, 새로운 연산(함수) 추가가 잦은 시스템에는 절차적·데이터 구조 중심 설계가 유리합니다. 따라서 시스템에서 앞으로 어떤 변경이 많을지 예측하고 그에 맞는 설계 방식을 선택하는 것이 좋은 소프트웨어 설계의 핵심입니다.