CodeGym /행동 /JAVA 25 SELF /추상화와 계층 구조 구현

추상화와 계층 구조 구현

JAVA 25 SELF
레벨 19 , 레슨 2
사용 가능

1. 계층 구성: 추상에서 구체로

추상화와 계층 구조를 구현한다는 것은 코드를 일반 규칙에서 구체적인 세부 사항으로 구조화하는 방법입니다. 먼저 모든 객체가 무엇을 할 수 있어야 하는지(추상화)를 정의하고, 그다음 각 구체 클래스가 그것을 어떻게 수행하는지 구체화합니다.

프로그래밍에서도 삶과 마찬가지로 모든 것은 질문에서 시작됩니다. 예를 들어: “원과 직사각형의 공통점은 무엇일까요?” 답: 둘 다 — 도형입니다. 그럼 도형의 공통점은 무엇일까요? 보통 넓이가 있고, 그릴 수 있습니다.

Java에서는 이것을 abstract-클래스로 표현합니다:

public abstract class Shape {
    public abstract double area();
    public abstract void draw();
}

여기서 말하는 것은:

  • 모든 도형은 자신의 면적을 계산할 수 있어야 합니다(area()).
  • 모든 도형은 스스로를 그릴 수 있어야 합니다(draw()).
  • 구체적으로 어떻게 하는지는 아직 중요하지 않습니다(지금은).

이제 구체적인 도형을 만들어 봅시다:

public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public void draw() {
        System.out.println("반지름이 " + radius + "인 원을 그립니다");
    }
}

public class Rectangle extends Shape {
    private double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }

    @Override
    public void draw() {
        System.out.println("직사각형 " + width + "x" + height + "을 그립니다");
    }
}

무엇을 했나요?

  • 공통점을 추상 클래스에 올렸습니다.
  • 세부 동작은 하위 클래스에서 구체화했습니다.

도식:


.          Shape
         /     \
     Circle   Rectangle

표: 무엇이 어디에 구현되어 있는가

클래스 area() draw() 고유 필드
Shape
abstract
abstract
-
Circle 구현됨 구현됨
radius
Rectangle 구현됨 구현됨
width, height

2. 왜 편리할까? (그리고 왜 이렇게 동작할까)

서로 다른 객체를 위한 통일된 인터페이스

예를 들어, 도형 컬렉션이 있다고 합시다:

Shape[] shapes = {
    new Circle(5),
    new Rectangle(3, 4),
    new Circle(2.5)
};

형에 상관없이 동일하게 순회할 수 있습니다:

for (Shape shape : shapes) {
    shape.draw();
    System.out.println("면적: " + shape.area());
}

누가 원이고 누가 직사각형인지는 JVM이 알아서 처리하게 두세요! 이것이 바로 다형성입니다(이미 한 번 언급했고, 다음 블록에서 더 자세히 다룹니다).

확장 용이성

삼각형을 추가하고 싶나요? 이렇게 작성하면 됩니다(새 타입 — 기존 코드는 수정하지 않음): Triangle extends Shape.

public class Triangle extends Shape {
    private double base, height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double area() {
        return 0.5 * base * height;
    }

    @Override
    public void draw() {
        System.out.println("삼각형을 그립니다: 밑변 " + base + ", 높이 " + height);
    }
}

나머지 코드는(예: 도형 목록 순회) 변경할 필요가 없습니다.

코드 중복 방지

모든 도형에 공통 속성(예: 색상)이 생겼다면, 추상 클래스에 올리는 것이 편합니다:

public abstract class Shape {
    private String color = "검정";

    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }

    public abstract double area();
    public abstract void draw();
}

이제 어떤 하위 클래스든 — 원이든 삼각형이든 — 색상을 상속으로 받습니다.

3. 실습: 미니 그래픽 편집기 구현

모든 것을 합쳐 봅시다. 간단한 그래픽 편집기를 만든다고 가정해 보세요.

추상 클래스 Figure

public abstract class Figure {
    private String color = "black";

    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }

    public abstract void draw();
    public abstract void resize(double factor);
}

구체 도형

public class Line extends Figure {
    private double length;

    public Line(double length) {
        this.length = length;
    }

    @Override
    public void draw() {
        System.out.println("길이가 " + length + "인 선을 색상 " + getColor() + "로 그립니다");
    }

    @Override
    public void resize(double factor) {
        length *= factor;
        System.out.println("새 선 길이: " + length);
    }
}

public class Ellipse extends Figure {
    private double a, b;

    public Ellipse(double a, double b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void draw() {
        System.out.println("축이 " + a + "과 " + b + "인 타원을 색상 " + getColor() + "로 그립니다");
    }

    @Override
    public void resize(double factor) {
        a *= factor;
        b *= factor;
        System.out.println("타원의 새로운 축: " + a + ", " + b);
    }
}

새 도구(예: Polygon)를 추가하고 싶나요? 새 클래스를 만들기만 하면 됩니다 — 편집기 전체 코드는 추상 Figure를 통해 동작합니다.

코드에서의 사용

Figure[] figures = {
    new Line(10),
    new Ellipse(5, 3)
};

for (Figure figure : figures) {
    figure.setColor("red");
    figure.draw();
    figure.resize(1.5);
}

출력:

길이가 10.0인 선을 색상 red로 그립니다
새 선 길이: 15.0
축이 5.0과 3.0인 타원을 색상 red로 그립니다
타원의 새로운 축: 7.5, 4.5

계층 시각화


.            Figure
             /    \
          Line   Ellipse

4. 중복을 피하는 방법: 공통 필드와 메서드

때로는 모든 하위 클래스에 공통 메서드뿐 아니라 공통 필드(예: 중심 좌표)도 있습니다. 추상 클래스가 이를 넣기에 가장 좋은 곳입니다:

public abstract class Figure {
    private double x, y; // 중심 좌표

    public Figure(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public void moveTo(double newX, double newY) {
        x = newX;
        y = newY;
        System.out.println("도형이 위치 (" + x + ", " + y + ")로 이동되었습니다");
    }

    public abstract void draw();
}

이제 Line이나 Ellipse는 이 메서드를 다시 구현하지 않고도 이동할 수 있습니다.

5. 또 다른 예: 결제 시스템

추상화는 도형에만 국한되지 않습니다! 결제 처리 시스템을 만든다고 가정해 봅시다.

추상 클래스 Payment

public abstract class Payment {
    public abstract void process();
}

구체 구현

public class CreditCardPayment extends Payment {
    @Override
    public void process() {
        System.out.println("신용카드 결제 처리");
    }
}

public class PaypalPayment extends Payment {
    @Override
    public void process() {
        System.out.println("PayPal 결제 처리");
    }
}

사용

Payment[] payments = {
    new CreditCardPayment(),
    new PaypalPayment()
};

for (Payment payment : payments) {
    payment.process();
}

출력:

신용카드 결제 처리
PayPal 결제 처리

6. 이 접근 방식의 장점

  • 통일된 인터페이스: 서로 다른 객체를 동일한 방식으로 다룰 수 있습니다.
  • 확장성: 새로운 종류의 객체를 추가해도 기존 코드를 다시 작성할 필요가 없습니다.
  • 최소한의 중복: 공통 사항을 기본 추상 클래스에 올립니다.
  • 유연성: 세부를 신경 쓰지 않고 추상 타입의 컬렉션을 사용할 수 있습니다.

7. 현실 예시: 교통

추상화는 교과서 밖에서도 등장합니다. 예를 들어, 교통 시스템을 설계한다고 합시다:

public abstract class Transport {
    public abstract void move();
    public abstract void fuelUp();
}

구체적인 교통수단은 세부를 구현합니다:

public class Car extends Transport {
    @Override
    public void move() {
        System.out.println("자동차가 도로를 달립니다");
    }

    @Override
    public void fuelUp() {
        System.out.println("휘발유를 주유합니다");
    }
}

public class Bicycle extends Transport {
    @Override
    public void move() {
        System.out.println("자전거는 페달을 밟습니다");
    }

    @Override
    public void fuelUp() {
        System.out.println("자전거는 연료가 필요 없습니다. 라이더의 샌드위치만 있으면 됩니다!");
    }
}

8. 유용한 도식: 추상화 계층을 어떻게 구성할까

[추상 클래스]
        |
   [구체 하위 클래스]
        |
   [더 구체적인 하위 클래스] (필요하다면)

- 공통된 것은 위로!
- 고유한 것은 아래로!

9. 추상화와 계층 구현 시 흔한 실수

실수 № 1: 하위 클래스에서 코드 중복.
각 하위 클래스에서 동일한 필드나 메서드를 반복해서 작성하고 있다면, 그것들을 추상 클래스로 올려야 한다는 신호입니다. 중복이 줄어든다면 추상 범위를 “더 넓게” 가져가는 것을 두려워하지 마세요.

실수 № 2: ‘일반에서 구체로’ 원칙을 어김.
초보자는 종종 공통점을 잊고 세부부터 계층을 만들기 시작합니다. 그 결과 RedCircleWithShadow 같은, 전체 구조에 잘 맞지 않는 이상한 클래스가 생깁니다. 항상 먼저 추상을 도출하고, 그다음에 세부를 정하세요.

실수 № 3: 지나치게 깊은 계층 구조.
상속 체인이 3–4단계를 넘는다면, 상속 대신 합성(composition)이나 인터페이스를 사용하는 편이 나을 수 있습니다.

실수 № 4: 의미 없는 메서드의 강제 구현.
추상 클래스에 일부 하위 클래스에는 맞지 않는 추상 메서드가 너무 많다면 구조를 재고할 필요가 있습니다. 예를 들어, 모든 교통수단이 fuelUp() 메서드를 필요로 하는 것은 아닙니다(자전거에는 불필요합니다).

실수 № 5: 추상 클래스와 인터페이스를 혼동.
추상 클래스는 공통 상태 및/또는 부분 구현이 있을 때 사용합니다. 인터페이스는 데이터를 보관하거나 동작을 구현하지 않고 메서드의 존재만 “약속”할 때 사용합니다. 필요 없으면 이 둘을 섞지 마세요.

1
과제
JAVA 25 SELF, 레벨 19, 레슨 2
잠금
교통 시뮬레이터: 자동차가 달립니다 🛣️
교통 시뮬레이터: 자동차가 달립니다 🛣️
1
과제
JAVA 25 SELF, 레벨 19, 레슨 2
잠금
기하 도형 라이브러리 🔵⬛
기하 도형 라이브러리 🔵⬛
1
과제
JAVA 25 SELF, 레벨 19, 레슨 2
잠금
기본 인사 관리 시스템 🧑‍💼
기본 인사 관리 시스템 🧑‍💼
1
과제
JAVA 25 SELF, 레벨 19, 레슨 2
잠금
유연한 매장 결제 시스템 💰
유연한 매장 결제 시스템 💰
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION