C++ Software-Design Study - 2

2025. 5. 20. 21:24·C++

이전에는 각객체마다 Draw함수가 있어 각 객체가 Draw를 하는 방식이었다. 이것을 조금 바꿔볼건데 

2단계를 거쳐서 바꿀것이다.

 

첫번째 방법은  일반적인 Visitor패턴을 사용하는 방법이고

두번째 방법도 Visitor 패턴을 사용하는데 C++의 variant를 사용해서 수정해 볼 것이다.

 

Visitor 이 패턴은 기존 객체의 구조를 변경하지 않고도 새로운 연산을 추가할 수 있게 해주는 패턴이다. 

  • 객체 구조와 알고리즘을 분리
  • 객체가 방문자를 수용(accept())하면 방문자가 객체를 처리(visit())
  • 새로운 작업 추가 시 기존 코드 수정 없이 새로운 Visitor 클래스 구현만으로 확장 가능

 

코드로 알아보자

[Shape 코드]

class Shape{
public:
	explicit Shape() = default;
	virtual ~Shape() = default;
	virtual void accept(ShapeVisitor const& v) = 0;
};

이전과 다르게 Shape에는 Draw함수가 사라지고 accept 함수가 생겼다. 

accept 함수는 visitor를 받아드리고 visitor가 방문을 할 경우 로직을 처리한다.

 

[Visitor 코드]

// ShapeVisitor.h
class Circle;
class Square;

class ShapeVisitor {
	public:
		virtual ~ShapeVisitor() = default;
		virtual void visit(Circle const&) const = 0;
		virtual void visit(Square const&) const = 0;
};

// Draw.h
#include "Circle.h"
#include "ShapeVisitor.h"
#include "Square.h"
#include <iostream>

class Draw : public ShapeVisitor
{
public:
    void visit(Circle const& c /*, ...*/) const override
    {
        std::cout << "Circle draw " << c.radius() << std::endl;
    }
    void visit(Square const& s /*, ...*/) const override
    {
        std::cout << "Square Draw " << s.side() <<std::endl;
    }
};

 

// Circle.h
#pragma once
#include "Shape.h"

class Circle : public Shape{
public:
	explicit Circle(double radius);
	double radius() const; 
	Point cetner() const;

	// Shape을(를) 통해 상속됨
	void accept(ShapeVisitor const& v) override;
private:
	double radius_;
	Point center_{ 0,0 };
};

// Circle.cpp
#include "Circle.h"
#include <iostream>

Circle::Circle(double radius) :
	radius_{ radius } {
}

double Circle::radius() const {
	return radius_;
}

Point Circle::cetner() const { 
	return center_; 
}

void Circle::accept(ShapeVisitor const& v)
{
	v.visit(*this);
}

각 객체들의 생긴게 비슷하기 때문에 Circle 클래스만 살펴보면 Draw 함수는 사라지고 accept 함수가 생겼다.

실제로 draw 함수를 처리하는 부분은 Visitor에게 위임했다. 이것이 객체지향의 관심사 분리이다.

그런데 이게 맞나? 하는 의문이 들었을거다.

draw 함수가 그냥 accept로만 변경되고 Visitor 클래스만 더 생겨서 복잡해진거 아닌가?  => 맞다. 

그게 바로 이 코드의 문제점이다.

결국 draw 로직은 분리했지만 Visitor 객체에 강하게 의존성을 가지게 된다. 그러면 이러한 문제점을 해결하는 방법은??

 

 

C++ Vairent 사용하기

c++17부터 사용가능한데 Vairent를 사용하는 것이다. Varient는 타입안전한 union 타입이다.

Varient은 Visit 함수와 함께 사용할 수 있는데 객체와 객체를 그리는 것을 완전히 분리 할 수 있다.

코드로 보면 다음과 같다.

// Circle.h
class Circle {
public:
	explicit Circle(double radius);
	double radius() const; 
	Point cetner() const;

private:
	double radius_;
	Point center_{ 0,0 };
};

// Shape.h
using Shape = std::variant<Circle, Square>;

// Draw.h
class Draw
{
public:
    void operator()(Circle const& c) const
    {
        std::cout << "Circle draw" << std::endl;
    }
    void operator()(Square const& s) const
    {
        std::cout << "Suare draw" << std::endl;
    }
};


// main.cpp

void drawAllShapes(Shapes const& shapes) {
	for (auto const& shape : shapes) {
		std::visit(Draw{}, shape);
	}
}

int main()
{
	Shapes shapes;
	shapes.emplace_back(Circle{ 2.3 });
	shapes.emplace_back(Square{ 2.3 });
	shapes.emplace_back(Circle{ 2.3 });
	drawAllShapes(shapes);
	return 0;
}

 

우선 첫번째로 Circle에서는  draw,accept 함수가 없다. 즉 Circle 자체는 그리는 행위와 완전히 분리되어있다. 

그리는 로직은 Draw 객체에게 모두 위임을했다.

drawAllShapes 함수에서 보는것 처럼 Visit함수를 사용해서 Draw에 마치 패턴매칭을 이룰 수 있게 만들어준다. 대박!!

 

cpp reference에 가보면 visit 함수에 대한 예시는 예술이다. 정말 패턴매칭처럼 생겼다. 예시코드를 함께 보자

 using value_t = std::variant<int, long, double, std::string>;
 
// helper type for the visitor #4
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
 
 std::visit(overloaded{
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
        }, v);

람다함수와 template를 함께 사용하면 멋진 코드를 작성 할 수 있다.

 

결론적으로 Visitor 패턴을 통해 객체과 객체 로직을 분리 할 수 있었다.

'C++' 카테고리의 다른 글

C++ Software-Design Study - 1  (0) 2025.05.19
'C++' 카테고리의 다른 글
  • C++ Software-Design Study - 1
교쟁이
교쟁이
  • 교쟁이
    Just Do it!!!
    교쟁이
  • 전체
    오늘
    어제
    • 분류 전체보기 (29)
      • C++ (2)
        • 자료구조 (0)
        • CMake (0)
        • 기초 (2)
        • 네트워크 (4)
      • 항해99 (15)
        • WIL (4)
      • Back-end (3)
        • DATABASE (3)
      • 글쓰기 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    항해 플러스
    항해 플러스 # CI/CD #Terraform
    database
    자바스크립트
    동생
    항해99
    네카라쿠배
    항해99 #nodejs
    TCP/IP
    부트캠프
    항해 플러스 #테라폼
    programming
    스타트업
    코드스테이츠
    테스트
    비전공자
    개발자
    Jest
    파이널 프로젝트
    신입개발자
    socket
    sql #database
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
교쟁이
C++ Software-Design Study - 2
상단으로

티스토리툴바