CodeGym /행동 /JAVA 25 SELF /실제 과제에서 추상화를 설계하는 예제

실제 과제에서 추상화를 설계하는 예제

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

1. 실제 애플리케이션에서의 추상화: 왜 필요한가

지난 강의에서 이미 추상화라는 개념을 살펴보고 간단한 예제를 보았습니다. 이제 이 접근이 좀 더 현실적인 과제에서 어떻게 작동하는지 보겠습니다. 실제 프로젝트에서는 서로 다르지만 유사한 객체를 거의 항상 다루게 됩니다. 예를 들어 결제 방식이 다르거나, 교통수단의 종류가 다르거나, 그래픽 에디터의 도형이 다릅니다. 추상화를 적용하지 않으면 코드는 곧 if-else 분기와 복붙의 집합이 되기 쉽습니다. 추상화를 적용하면 — 구조적이고, 보기 좋으며, 무엇보다도 개발과 유지보수에 편리합니다.

추상화의 이점:

  • 구현 세부사항을 숨김: 내부 구현을 몰라도 공통 인터페이스를 통해 객체를 다룰 수 있습니다.
  • 코드 중복 방지: 공통 동작과 필드를 기본 클래스에 모읍니다.
  • 시스템을 쉽게 확장: 새로운 종류의 객체를 추가할 때 기존 코드를 갈아엎을 필요가 없습니다.
  • 코드를 유연하게: 시스템의 나머지를 바꾸지 않고도 구현을 교체할 수 있습니다.

서로 다른 영역의 예시 몇 가지를 살펴봅시다.

2. 예시 1: 결제 시스템

문제 설정

여러분이 인터넷 쇼핑몰 모듈을 작성한다고 해봅시다. 할 일은 다양한 결제 수단(신용카드, PayPal, 암호화폐)의 처리를 구현하는 것입니다. 모두가 “결제를 처리”해야 하지만, 세부 동작은 제각각입니다.

추상화: 클래스 Payment

public abstract class Payment {
    protected double amount;

    public Payment(double amount) {
        this.amount = amount;
    }

    // 추상 메서드: 결제를 어떻게 처리할지는 하위 클래스가 결정한다
    public abstract void process();

    // 모든 결제에 공통인 메서드
    public void printAmount() {
        System.out.println("결제 금액: " + amount + " 루블");
    }
}

구체 구현

public class CreditCardPayment extends Payment {
    private String cardNumber;

    public CreditCardPayment(double amount, String cardNumber) {
        super(amount);
        this.cardNumber = cardNumber;
    }

    @Override
    public void process() {
        System.out.println("카드 결제 처리: " + cardNumber);
        // 여기에는 은행 연동이 들어갈 수 있습니다 :)
    }
}
public class PaypalPayment extends Payment {
    private String email;

    public PaypalPayment(double amount, String email) {
        super(amount);
        this.email = email;
    }

    @Override
    public void process() {
        System.out.println("PayPal 결제 처리, 계정: " + email);
        // 여기서는 PayPal API 호출
    }
}
public class CryptoPayment extends Payment {
    private String walletAddress;

    public CryptoPayment(double amount, String walletAddress) {
        super(amount);
        this.walletAddress = walletAddress;
    }

    @Override
    public void process() {
        System.out.println("암호화폐 결제 처리, 지갑: " + walletAddress);
        // 여기에는 블록체인 매직이 있을 수 있습니다
    }
}

추상화 사용

import java.util.*;

public class PaymentDemo {
    public static void main(String[] args) {
        List<Payment> payments = new ArrayList<>();
        payments.add(new CreditCardPayment(1500.0, "1234 5678 9012 3456"));
        payments.add(new PaypalPayment(500.0, "user@example.com"));
        payments.add(new CryptoPayment(0.05, "0xABCD..."));

        for (Payment payment : payments) {
            payment.printAmount();
            payment.process();
            System.out.println("---");
        }
    }
}

실행 결과:

결제 금액: 1500.0 루블
카드 결제 처리: 1234 5678 9012 3456
---
결제 금액: 500.0 루블
PayPal 결제 처리, 계정: user@example.com
---
결제 금액: 0.05 루블
암호화폐 결제 처리, 지갑: 0xABCD...
---

장점:

  • 새로운 결제 수단(예: Apple Pay)을 기존 코드를 바꾸지 않고 추가할 수 있습니다.
  • 결제를 다루는 코드는 구체 타입에 의존하지 않습니다.
  • 공통 로직(예: printAmount()로 금액 출력)은 한 곳에서만 구현합니다.

3. 예시 2: 운송 수단

문제 설정

게임이나 시뮬레이터에서 자동차, 자전거, 기차 같은 다양한 교통수단이 있다고 합시다. 모두 “이동”할 수 있지만 방식은 다릅니다. 어떤 것은 주유가 필요하고, 어떤 것은 필요하지 않습니다.

추상화: 클래스 Transport

public abstract class Transport {
    protected String name;

    public Transport(String name) {
        this.name = name;
    }

    public abstract void move();

    // 모든 교통수단이 주유가 필요한 것은 아니지만, 기본값은 필요 없음
    public void fuelUp() {
        System.out.println(name + ": 주유가 필요하지 않습니다.");
    }
}

구체 구현

public class Car extends Transport {
    public Car(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + "가 도로를 달립니다.");
    }

    @Override
    public void fuelUp() {
        System.out.println(name + ": 휘발유를 주유합니다.");
    }
}
public class Bicycle extends Transport {
    public Bicycle(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + "가 페달을 밟습니다.");
    }
    // fuelUp은 오버라이드하지 않습니다 — 자전거는 주유가 필요하지 않습니다
}
public class Train extends Transport {
    public Train(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + "가 레일 위를 달립니다.");
    }

    @Override
    public void fuelUp() {
        System.out.println(name + ": 디젤을 주유하거나 전기를 충전합니다.");
    }
}

추상화 사용

import java.util.*;

public class TransportDemo {
    public static void main(String[] args) {
        List<Transport> vehicles = Arrays.asList(
            new Car("Toyota"),
            new Bicycle("Stels"),
            new Train("사프산")
        );

        for (Transport t : vehicles) {
            t.move();
            t.fuelUp();
            System.out.println("---");
        }
    }
}

실행 결과:

Toyota가 도로를 달립니다.
Toyota: 휘발유를 주유합니다.
---
Stels가 페달을 밟습니다.
Stels: 주유가 필요하지 않습니다.
---
사프산이 레일 위를 달립니다.
사프산: 디젤을 주유하거나 전기를 충전합니다.
---

장점:

  • 타입을 검사하지 않고 모든 교통수단을 동일하게 처리할 수 있습니다.
  • 새로운 교통수단(예: 전동 킥보드)을 쉽게 추가할 수 있습니다.

4. 예시 3: 그래픽 에디터

문제 설정

미니 그래픽 에디터를 만든다고 합시다. 선, 타원, 다각형이 있고 — 이 모든 것은 “도형”이며 그릴 수 있고 크기를 변경할 수 있습니다. 하지만 각 도형은 이를 자신만의 방식으로 구현합니다.

추상화: 클래스 Figure

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

    public abstract void draw();

    public abstract void resize(double factor);

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

구체 구현

public class Line extends Figure {
    private double length;

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

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

    @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 + "인 타원을 색상 " + color + "으로 그립니다");
    }

    @Override
    public void resize(double factor) {
        a *= factor;
        b *= factor;
        System.out.println("타원의 새로운 크기: " + a + " x " + b);
    }
}
public class Polygon extends Figure {
    private int sides;

    public Polygon(int sides) {
        this.sides = sides;
    }

    @Override
    public void draw() {
        System.out.println("변이 " + sides + "개인 다각형을 색상 " + color + "으로 그립니다");
    }

    @Override
    public void resize(double factor) {
        System.out.println("변이 " + sides + "개인 다각형의 크기를 " + factor + "배로 변경합니다");
    }
}

추상화 사용

import java.util.*;

public class EditorDemo {
    public static void main(String[] args) {
        List<Figure> figures = new ArrayList<>();
        figures.add(new Line(10));
        figures.add(new Ellipse(5, 3));
        figures.add(new Polygon(6));

        for (Figure f : figures) {
            f.setColor("green");
            f.draw();
            f.resize(2);
            System.out.println("---");
        }
    }
}

실행 결과:

길이가 10.0인 선을 색상 green으로 그립니다
선의 새 길이: 20.0
---
축이 5.0과 3.0인 타원을 색상 green으로 그립니다
타원의 새로운 크기: 10.0 x 6.0
---
변이 6개인 다각형을 색상 green으로 그립니다
변이 6개인 다각형의 크기를 2.0배로 변경합니다
---

장점:

  • 모든 도형을 하나의 리스트에 담아 동일한 방식으로 처리할 수 있습니다.
  • 새로운 도형(예: 별, 하트)을 쉽게 추가할 수 있습니다.
  • 공통 메서드(예: setColor()로 색상 설정)는 한 번만 구현합니다.

5. 추상화가 코드를 단순화하는 방법

위의 각 예시에는 공통된 패턴이 있습니다:

  • 기본 추상 클래스가 계약(객체가 무엇을 할 수 있는지)을 정의합니다.
  • 구체 하위 클래스가 세부 구현을 담당합니다.
  • 추상화에 의존하는 코드는 객체 타입에 의존하지 않으므로 시스템이 유연하고 확장 가능합니다.

접근 방식 비교 표

추상화 없음 (if-else) 추상화 사용 (OOP)
타입별로 조건이 많음 새로운 타입 — 코드를 수정함
로직 중복 로직 — 한 곳에
확장하기 어려움 추가 — 쉬움
테스트하기 어려움 구현을 쉽게 교체 가능

6. 추상화 설계 시 흔한 실수

오류 №1: 추상화를 위한 추상화.
객체 타입이 하나뿐이고 확장 계획이 없다면 추상 클래스는 필요 없습니다. 이유 없이 코드를 복잡하게 만들지 마세요.

오류 №2: 지나치게 일반적인 추상화.
기본 클래스가 너무 “모호”하면 하위 클래스들이 이름 외에는 공통점이 없을 수 있습니다. 예를 들어 모든 것을 아우르는 “Thing” 같은 추상화입니다. 이는 코드의 이해와 유지보수를 어렵게 만듭니다.

오류 №3: 하위 클래스에서의 코드 중복.
모든 하위 클래스가 동일한 메서드 구현을 가진다면, 그 메서드는 기본 클래스로 올려(추상 메서드가 아니라) 공통 구현으로 두는 것이 좋습니다.

오류 №4: ‘일반에서 구체로’ 원칙 위반.
추상 클래스에 특정 하위 클래스만 필요한 세부사항이 들어가기 시작했다면 — 추상화를 잘못 선택한 것입니다.

오류 №5: 추상 메서드 구현을 잊었다.
하위 클래스에서 모든 추상 메서드를 구현하지 않으면, 컴파일러가 그 클래스도 추상 클래스로 만들도록 강제합니다. 가끔은 당황스럽죠 :)

1
과제
JAVA 25 SELF, 레벨 19, 레슨 3
잠금
그래픽 애플리케이션의 면적 계산 🎨
그래픽 애플리케이션의 면적 계산 🎨
1
과제
JAVA 25 SELF, 레벨 19, 레슨 3
잠금
범용 알림 발송 시스템 📧💬
범용 알림 발송 시스템 📧💬
1
과제
JAVA 25 SELF, 레벨 19, 레슨 3
잠금
회사 차량 관리 🚚🚲
회사 차량 관리 🚚🚲
1
과제
JAVA 25 SELF, 레벨 19, 레슨 3
잠금
소매점 주문 처리 모듈 🛒
소매점 주문 처리 모듈 🛒
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION