icon안동민 개발노트

다형성을 활용한 도형 그리기 프로그램


실습 개요

 이번 실습에서는 다형성의 개념을 실제로 적용하여 다양한 도형을 그리고 관리하는 프로그램을 만들어볼 것입니다.

 이 과정을 통해 추상 클래스, 가상 함수, 상속 등의 개념을 실제 코드로 구현하고 그 효용성을 직접 체험해볼 수 있습니다.

학습 목표

  1. 추상 클래스와 순수 가상 함수의 사용법 익히기
  2. 상속을 통한 클래스 계층 구조 설계하기
  3. 다형성을 활용하여 객체를 유연하게 다루는 방법 이해하기
  4. 스마트 포인터를 사용하여 메모리를 안전하게 관리하는 방법 학습하기
  5. 실제 문제에 객체 지향 설계 원칙을 적용하는 능력 기르기

프로그램 요구사항

  1. Shape라는 추상 기본 클래스를 정의합니다.
  2. Circle, Rectangle, Triangle 등의 구체적인 도형 클래스를 Shape에서 파생시킵니다.
  3. 각 도형은 면적과 둘레를 계산할 수 있어야 합니다.
  4. 각 도형은 자신을 그리는 메서드를 가져야 합니다(콘솔에 출력).
  5. DrawingProgram 클래스를 만들어 여러 도형을 관리하고 그릴 수 있도록 합니다.

단계별 구현 가이드

 Shape 클래스 정의

 먼저 Shape 추상 클래스를 정의합니다.

 이 클래스는 모든 도형의 공통 인터페이스를 제공합니다.

#include <iostream>
#include <vector>
#include <memory>
#include <cmath>
 
class Shape {
public:
    virtual double getArea() const = 0;
    virtual double getPerimeter() const = 0;
    virtual void draw() const = 0;
    virtual ~Shape() = default;
};
  • getArea() : 도형의 면적을 반환합니다.
  • getPerimeter() : 도형의 둘레를 반환합니다.
  • draw() : 도형을 그립니다(여기서는 콘솔에 출력).
  • 가상 소멸자를 선언하여 다형적 삭제가 올바르게 동작하도록 합니다.

 구체적인 도형 클래스 구현

 Circle, Rectangle, Triangle 클래스를 Shape에서 상속받아 구현합니다.

class Circle : public Shape {
private:
    double radius;
 
public:
    Circle(double r) : radius(r) {}
 
    double getArea() const override {
        return M_PI * radius * radius;
    }
 
    double getPerimeter() const override {
        return 2 * M_PI * radius;
    }
 
    void draw() const override {
        std::cout << "Drawing a circle with radius " << radius << std::endl;
    }
};
 
class Rectangle : public Shape {
private:
    double width, height;
 
public:
    Rectangle(double w, double h) : width(w), height(h) {}
 
    double getArea() const override {
        return width * height;
    }
 
    double getPerimeter() const override {
        return 2 * (width + height);
    }
 
    void draw() const override {
        std::cout << "Drawing a rectangle with width " << width 
                  << " and height " << height << std::endl;
    }
};
 
class Triangle : public Shape {
private:
    double a, b, c;  // 세 변의 길이
 
public:
    Triangle(double side1, double side2, double side3) 
        : a(side1), b(side2), c(side3) {}
 
    double getArea() const override {
        // 헤론의 공식 사용
        double s = (a + b + c) / 2;
        return std::sqrt(s * (s - a) * (s - b) * (s - c));
    }
 
    double getPerimeter() const override {
        return a + b + c;
    }
 
    void draw() const override {
        std::cout << "Drawing a triangle with sides " 
                  << a << ", " << b << ", " << c << std::endl;
    }
};

 각 도형 클래스는 Shape의 순수 가상 함수를 오버라이드하여 구체적인 구현을 제공합니다.

 DrawingProgram 클래스 구현

 DrawingProgram 클래스는 여러 도형을 관리하고 그리는 역할을 합니다.

class DrawingProgram {
private:
    std::vector<std::unique_ptr<Shape>> shapes;
 
public:
    void addShape(std::unique_ptr<Shape> shape) {
        shapes.push_back(std::move(shape));
    }
 
    void drawAll() const {
        for (const auto& shape : shapes) {
            shape->draw();
        }
    }
 
    void printInfo() const {
        for (const auto& shape : shapes) {
            shape->draw();
            std::cout << "  Area: " << shape->getArea() << std::endl;
            std::cout << "  Perimeter: " << shape->getPerimeter() << std::endl;
        }
    }
};
  • std::unique_ptr를 사용하여 도형 객체의 소유권을 관리합니다.
  • addShape() : 새로운 도형을 추가합니다.
  • drawAll() : 모든 도형을 그립니다.
  • printInfo() : 모든 도형의 정보(면적, 둘레)를 출력합니다.

 메인 함수 구현

 프로그램의 실행을 위한 메인 함수를 구현합니다.

int main() {
    DrawingProgram program;
 
    program.addShape(std::make_unique<Circle>(5));
    program.addShape(std::make_unique<Rectangle>(4, 6));
    program.addShape(std::make_unique<Triangle>(3, 4, 5));
 
    std::cout << "Drawing all shapes:" << std::endl;
    program.drawAll();
 
    std::cout << "\nShape information:" << std::endl;
    program.printInfo();
 
    return 0;
}

프로그램 실행 및 결과 분석

 프로그램을 컴파일하고 실행하면 다음과 같은 결과를 얻을 수 있습니다.

Drawing all shapes:
Drawing a circle with radius 5
Drawing a rectangle with width 4 and height 6
Drawing a triangle with sides 3, 4, 5
 
Shape information:
Drawing a circle with radius 5
  Area: 78.5398
  Perimeter: 31.4159
Drawing a rectangle with width 4 and height 6
  Area: 24
  Perimeter: 20
Drawing a triangle with sides 3, 4, 5
  Area: 6
  Perimeter: 12

 이 결과를 통해 다음과 같은 점을 관찰할 수 있습니다.

  1. 다형성의 작동 : DrawingProgram은 구체적인 도형 타입을 알지 못하지만, Shape 인터페이스를 통해 모든 도형을 동일하게 처리할 수 있습니다.
  2. 가상 함수의 동작 : 각 도형 객체의 구체적인 draw(), getArea(), getPerimeter() 메서드가 호출됩니다.
  3. 코드 재사용 : 새로운 도형을 추가하더라도 DrawingProgram 클래스를 수정할 필요가 없습니다.

실습

 새로운 도형 추가하기

 정사각형(Square) 클래스를 추가해봅시다.

class Square : public Shape {
private:
    double side;
 
public:
    Square(double s) : side(s) {}
 
    double getArea() const override {
        return side * side;
    }
 
    double getPerimeter() const override {
        return 4 * side;
    }
 
    void draw() const override {
        std::cout << "Drawing a square with side " << side << std::endl;
    }
};

 메인 함수에 다음 줄을 추가하여 정사각형 객체를 생성할 수 있습니다.

program.addShape(std::make_unique<Square>(5));

 도형 정렬 기능 추가

 도형들을 면적이나 둘레를 기준으로 정렬하는 기능을 DrawingProgram 클래스에 추가해봅시다.

class DrawingProgram {
    // ... 기존 코드 ...
 
public:
    void sortByArea() {
        std::sort(shapes.begin(), shapes.end(),
            [](const auto& a, const auto& b) {
                return a->getArea() < b->getArea();
            });
    }
 
    void sortByPerimeter() {
        std::sort(shapes.begin(), shapes.end(),
            [](const auto& a, const auto& b) {
                return a->getPerimeter() < b->getPerimeter();
            });
    }
};

 이제 program.sortByArea() 또는 program.sortByPerimeter()를 호출하여 도형들을 정렬할 수 있습니다.

연습 문제

  1. Shape 클래스에 색상 속성을 추가하고, 각 도형을 그릴 때 색상 정보도 출력하도록 프로그램을 수정하세요.
  2. 복합 도형(CompositeShape)을 나타내는 클래스를 구현하세요. 이 클래스는 여러 개의 Shape 객체를 포함하고, 전체 면적과 둘레를 계산할 수 있어야 합니다.
  3. DrawingProgram 클래스에 특정 유형의 도형만 선택하여 그리는 기능을 추가하세요. (힌트 : dynamic_cast를 사용할 수 있습니다)

 참고자료

  • "Design Patterns : Elements of Reusable Object-Oriented Software" by Erich Gamma et al. - 객체 지향 설계 패턴에 대해 더 깊이 학습할 수 있습니다.
  • "Effective C++" by Scott Meyers - C++의 효과적인 사용법과 최적의 실천 방법을 배울 수 있습니다.
  • "C++ Primer" by Stanley B. Lippman et al. - C++의 기본 개념부터 고급 주제까지 폭넓게 다룹니다.