반응형

프로토타입 패턴은 생성 패턴(Creational Pattern) 중 하나로, 객체를 복제(clone)하여 새로운 객체를 생성하는 데 사용됩니다.
이 패턴은 이미 존재하는 객체를 복사해서 새로운 객체를 생성하는 것이 주된 아이디어이며, 복제가 필요한 상황에서 효율적인 솔루션을 제공합니다.


언제 프로토타입 패턴을 사용하나요?

  1. 복잡한 객체의 생성 비용이 높은 경우
    • 기존 객체를 복제하는 것이 새로 생성하는 것보다 빠르고 효율적일 때.
  2. 객체의 구체적인 클래스 정보가 숨겨져야 할 때
    • 구체적인 클래스에 의존하지 않고 객체를 생성할 수 있음.
  3. 객체의 상태를 포함한 동일한 속성을 가진 복제본을 생성해야 할 때.

구성 요소

  1. Prototype(원형) 인터페이스
    • 객체 복제(clone)를 위한 인터페이스를 정의합니다.
  2. Concrete Prototype(구체적 원형)
    • Prototype 인터페이스를 구현하며, 자신을 복제하는 메서드를 제공합니다.
  3. Client(클라이언트)
    • Prototype 객체를 사용하여 복제를 요청합니다.

C++ 예제: 간단한 모양(Shape) 클래스

1. Prototype 인터페이스

  • 역할
    • Shape는 복제 가능한 모든 도형의 공통 인터페이스입니다.
    • Clone() 메서드를 통해 객체를 복제합니다.
    • Draw() 메서드는 객체의 동작을 정의합니다.

2. Concrete Prototype(구체적 원형)

Circle 클래스

 

 

Rectangle 클래스

  • 역할
    • Circle과 Rectangle은 각각 Shape 인터페이스를 구현합니다.
    • Clone() 메서드를 통해 객체를 복제할 수 있습니다.
    • 복제된 객체는 원본 객체와 동일한 속성(color)을 가집니다.

3. Client(클라이언트)


예제 설명

  1. Prototype 인터페이스 (Shape)
    • Clone() 메서드를 통해 복제 기능을 정의했습니다.
    • Draw() 메서드는 도형을 그리는 역할을 합니다.
  2. Concrete Prototype (Circle, Rectangle)
    • Clone() 메서드를 통해 객체 자신을 복제합니다.
    • 복사 생성자를 사용하여 객체의 속성(color)을 복사합니다.
  3. Client 코드
    • 클라이언트는 원본 객체(circle, rectangle)를 복제(clonedCircle, clonedRectangle)하고, 복제된 객체가 원본과 동일한 동작을 수행하는지 확인합니다.
    • 복제된 객체는 원본과 독립적이며, 새로운 메모리 공간에 생성됩니다.

프로토타입 패턴의 장점

  1. 객체 생성 비용 절감
    • 복잡한 객체를 복제함으로써 객체 생성 비용을 줄일 수 있습니다.
  2. 유연성 증가
    • 객체 생성 로직을 숨길 수 있어 클라이언트는 구체적인 클래스 정보에 의존하지 않습니다.
  3. 상태 보존
    • 복제된 객체는 원본의 속성을 그대로 유지하므로 동일한 상태를 가진 객체를 생성할 수 있습니다.

프로토타입 패턴의 단점

  1. 객체 복제의 복잡성
    • 깊은 복사와 얕은 복사 문제를 고려해야 합니다.
  2. 추가적인 메모리 관리 필요
    • 동적으로 생성된 복제본의 메모리를 관리해야 합니다.
  3. 객체 구조 의존
    • 객체 구조가 복잡하거나 순환 참조가 있을 경우 복제 구현이 까다로울 수 있습니다.

프로토타입 패턴의 활용 사례

  • 게임: 게임 내 캐릭터, 아이템, 맵 등 복잡한 객체를 복제하여 성능 최적화.
  • 그래픽 소프트웨어: 복잡한 그래픽 요소를 복제하여 재사용.
  • 데이터 처리: 동일한 구조와 속성을 가진 객체를 다수 생성해야 할 때.

C++ 프로토타입 패턴은 객체 복제를 효율적으로 처리해야 하는 경우 강력한 도구가 될 수 있습니다.

반응형

C++에서 **빌더 패턴(Builder Pattern)**은 생성 패턴(Creational Pattern)의 한 유형으로, 복잡한 객체를 단계적으로 생성하는 데 사용됩니다. 빌더 패턴은 특히 생성 과정에서 객체를 구성하는 세부 사항이 많거나, 동일한 생성 절차로 다양한 표현을 생성해야 할 때 유용합니다. 이 패턴은 객체의 생성과 표현을 분리하여, 동일한 생성 코드에서 다양한 객체를 만들 수 있도록 설계되었습니다.


빌더 패턴의 주요 구성 요소

  1. Builder 인터페이스
    • 객체 생성의 단계를 정의하는 추상 인터페이스입니다.
    • 제품(Product)을 구성하는 여러 부분을 설정하는 메서드를 포함합니다.
  2. ConcreteBuilder(구체적 빌더)
    • Builder 인터페이스를 구현하여 객체를 단계적으로 생성합니다.
    • 완성된 제품(Product)을 반환하는 메서드도 포함합니다.
  3. Director(감독자)
    • Builder 객체를 사용하여 객체를 생성하는 데 필요한 단계를 정의하고 실행하는 역할을 합니다.
    • 객체 생성 순서를 제어하며, 빌더의 메서드를 호출하여 객체를 완성합니다.
  4. Product(제품)
    • 생성되는 최종 객체입니다.
    • 복잡한 구조를 가지며, 빌더에 의해 단계적으로 생성됩니다.

C++ 구현 예제

다음은 간단한 예제로, 빌더 패턴을 사용하여 복잡한 Car 객체를 생성하는 경우를 보여줍니다.

1. Product 클래스

 

  • 역할
    Car는 최종적으로 생성되는 객체입니다.
    여기에는 자동차의 엔진, 바퀴, 색상 등 여러 속성이 정의되어 있습니다.
  • 특징
    ShowSpecifications() 메서드를 통해 생성된 객체의 속성을 확인할 수 있습니다.

 


2. Builder 인터페이스

 

 

  • 역할
    Builder 인터페이스는 Car 객체를 구성하기 위한 구성 단계를 정의합니다.
  • 주요 메서드
    • BuildEngine(): 엔진 설정.
    • BuildWheels(): 바퀴 설정.
    • Paint(): 자동차 색상 설정.
    • GetCar(): 최종적으로 완성된 Car 객체를 반환.
  • 특징
    이 인터페이스를 구현하는 구체적 빌더는 Car의 속성을 단계별로 설정합니다.

 


3. ConcreteBuilder

 

  • 역할
    SportsCarBuilder는 CarBuilder를 구현하여 스포츠카를 단계적으로 생성합니다.
  • 구성 단계
    • BuildEngine(): Car의 engine 속성을 "V8 Engine"으로 설정.
    • BuildWheels(): wheels 속성을 "18 inch Alloy Wheels"로 설정.
    • Paint(): color 속성을 "Red"로 설정.
  • 특징
    • Car* car: 생성 중인 Car 객체를 저장.
    • 객체 생성이 끝나면 GetCar() 메서드를 통해 완성된 객체를 반환합니다.

 


4. Director 클래스

 

  • 역할
    Director는 빌더를 사용하여 Car를 생성하는 순서를 정의합니다.
  • 특징
    • CarBuilder* builder: 사용할 빌더 객체를 보관.
    • Construct(): 빌더의 메서드를 호출하여 객체 생성 과정을 실행.
  • 장점
    • 빌더가 Director와 협력하므로, 객체 생성의 순서와 논리를 분리할 수 있습니다.
    • 동일한 Director를 사용하더라도 다른 ConcreteBuilder를 전달하면 다양한 객체를 생성할 수 있습니다.

 


5. 클라이언트 코드

  • 1단계: 빌더 생성  
    • SportsCarBuilder는 스포츠카를 생성하기 위한 빌더입니다.
       
  • 2단계: Director 생성 및 빌더 전달
    • Director는 빌더를 받아서 객체 생성의 절차를 정의합니다.
  • 3단계: 객체 생성
    • Director가 빌더의 BuildEngine(), BuildWheels(), Paint() 메서드를 호출하여 객체를 단계적으로 구성합니다.
  • 4단계: 완성된 객체 확인
    • 완성된 Car 객체를 반환받고, 속성을 출력합니다.
  • 마지막 단계: 메모리 정리
    • 동적으로 생성된 객체를 명시적으로 삭제하여 메모리 누수를 방지합니다

확장 가능성

  • SportsCarBuilder 외에 다른 빌더(SUVBuilder, TruckBuilder 등)를 추가하면 다양한 유형의 자동차를 생성할 수 있습니다.
  • Director는 동일한 로직으로 다른 빌더를 사용할 수 있으므로, 객체 생성의 유연성이 증가합니다.

빌더 패턴의 장점

  1. 복잡한 객체 생성 단순화
    • 객체 생성의 세부 사항을 캡슐화하여 복잡성을 줄일 수 있습니다.
  2. 객체 생성의 유연성 증가
    • 동일한 생성 과정을 사용하여 다양한 유형의 객체를 생성할 수 있습니다.
  3. 객체의 생성 코드와 표현 분리
    • 객체 생성에 필요한 세부 정보를 클라이언트 코드에서 분리합니다.

빌더 패턴의 단점

  1. 구현 복잡성 증가
    • 객체를 구성하는 단계와 인터페이스를 정의해야 하므로 코드가 다소 복잡해질 수 있습니다.
  2. 단일 제품군에만 적합
    • 동일한 생성 단계를 공유하는 제품군에는 적합하지만, 생성 방식이 크게 다른 경우에는 불편할 수 있습니다.

빌더 패턴은 주로 객체 생성이 복잡하거나 다양한 조합으로 객체를 생성해야 할 때 사용됩니다. 잘 설계된 빌더 패턴은 코드의 가독성과 재사용성을 높이는 데 매우 효과적입니다.

반응형

Abstract Factory는 객체 생성의 추상화를 통해 서로 관련 있거나 독립적인 객체 그룹을 생성할 수 있도록 설계된 생성 패턴입니다. 이 패턴은 구체적인 클래스의 인스턴스를 직접 지정하지 않고, 객체 생성 인터페이스를 제공하여 구현 간의 결합도를 줄이는 데 중점을 둡니다.

C++에서 이 패턴은 다양한 객체 생성 작업을 캡슐화하고 서로 관련된 객체들이 일관되게 생성되도록 보장합니다. 이 과정에서 객체 생성의 구체적인 세부사항을 클라이언트 코드로부터 숨기며, 클라이언트는 단순히 팩토리를 호출하기만 하면 됩니다.


Abstract Factory 패턴의 핵심 원리

1. 객체 생성의 추상화

클라이언트 코드에서 제품 객체를 직접 생성하지 않고, 팩토리를 통해 생성합니다.
즉, 구체적인 제품 클래스는 클라이언트 코드에서 노출되지 않습니다.


구성 요소

  1. Abstract Factory (추상 팩토리)
    • 객체를 생성하기 위한 인터페이스(순수 가상 클래스)를 정의합니다.
    • 제품군(Product Family)에 속하는 객체들을 생성하는 역할을 담당합니다.
  2. Concrete Factory (구체 팩토리)
    • Abstract Factory를 구현하여 구체적인 객체 생성 논리를 제공합니다.
    • 각 팩토리는 특정 플랫폼이나 요구사항에 맞는 제품군 객체를 생성합니다.
     
  3. Abstract Product (추상 제품)
    • 팩토리가 생성할 객체들의 공통 인터페이스를 정의합니다.
    • 이를 통해 제품군 내 객체들 간의 일관성을 유지할 수 있습니다.
  4. Concrete Product (구체 제품)
    • Abstract Product를 실제로 구현한 클래스입니다.
    • 예를 들어, Windows와 Mac 환경에 각각 적합한 Button과 Checkbox를 정의합니다.
  5. Client (클라이언트)
    • Abstract Factory와 Abstract Product에 의존합니다.
    • 구체적인 제품 클래스나 팩토리를 알 필요가 없습니다.

Abstract Factory 작동 흐름

  1. 팩토리 생성
    클라이언트는 특정 환경에 맞는 팩토리 객체를 생성합니다.
    예: WindowsWidgetFactory, MacWidgetFactory.
  2. 객체 생성 요청
    클라이언트는 팩토리 객체를 통해 필요한 제품군의 객체를 생성합니다.
    제품군 내의 객체들(Button, Checkbox 등)은 서로 독립적으로 생성되지만, 같은 컨텍스트(Windows, Mac 등)에 속합니다.
  3. 제품 사용
    생성된 객체는 인터페이스(추상 클래스)를 통해 사용되며, 클라이언트는 구체적인 구현을 알 필요가 없습니다.

실제 예제: 운영 체제 기반 UI

운영 체제에 따라 다른 UI 위젯을 제공하는 예를 들어보겠습니다.

 

 

출력 결과


Abstract Factory의 장점

  1. 일관성 유지:
    제품군 내 객체들은 항상 같은 컨텍스트에 속하므로, 일관성이 유지됩니다.
    • 예: Windows 팩토리는 Windows 스타일의 버튼과 체크박스만 생성.
  2. 유연성 증가:
    클라이언트는 제품의 구체적인 구현에 의존하지 않습니다. 따라서 쉽게 교체하거나 확장할 수 있습니다.
  3. 의존성 역전 원칙(DIP) 준수:
    클라이언트는 구체적인 클래스가 아니라 추상화된 인터페이스에 의존합니다.

Abstract Factory의 단점

  1. 복잡성 증가:
    많은 인터페이스와 클래스가 추가되므로 코드가 복잡해질 수 있습니다.
  2. 확장 시 추가 작업:
    새로운 제품군을 추가하려면 관련된 팩토리와 제품 클래스를 모두 정의해야 합니다.

활용 사례

  • 크로스 플랫폼 UI 라이브러리 (Qt, GTK 등).
  • 데이터베이스 연동 라이브러리 (Oracle, MySQL 등 다양한 드라이버 지원).
  • 게임 개발에서 월드 생성 시스템.
반응형

Factory Method 패턴은 생성 패턴의 한 종류로, 객체 생성에 대한 책임을 서브클래스로 위임하여, 객체 생성 방식을 캡슐화하고 클라이언트가 객체 생성 방식에 독립적으로 동작하도록 합니다. 객체 생성이 필요한 경우 이 패턴을 사용하여 클라이언트 코드의 유연성과 확장성을 높일 수 있습니다. 다음은 C++를 기준으로 Factory Method 패턴의 구조와 코드 예제를 포함하여 자세히 설명합니다.

Factory Method 패턴의 구조

  1. Product (제품 인터페이스): 생성될 객체들이 구현해야 할 인터페이스 또는 추상 클래스입니다.
  2. ConcreteProduct (구체적 제품): Product 인터페이스를 구현하는 실제 클래스들로, 다양한 형태의 Product 객체가 됩니다.
  3. Creator (생성자 인터페이스): 팩토리 메서드 (FactoryMethod())를 정의하는 인터페이스로, 객체 생성 로직을 서브클래스에서 구현하도록 위임합니다.
  4. ConcreteCreator (구체적 생성자): Creator 클래스를 상속하여, 특정 구체적 제품 객체를 생성하는 팩토리 메서드를 구현한 클래스입니다.

동작 과정

  1. 클라이언트 코드Creator의 인스턴스에게 FactoryMethod를 호출하여 Product 타입의 객체 생성을 요청합니다.
  2. ConcreteCreator는 FactoryMethod를 통해 ConcreteProduct의 객체를 생성하고, 이를 Product 타입으로 반환합니다.
  3. 클라이언트는 Product 인터페이스만을 사용하여 객체와 상호작용합니다. 구체적으로 어떤 객체가 생성되었는지 알 필요가 없습니다.

C++ 코드 예제

1. Product 인터페이스와 구체적인 제품 클래스들

 

  • Product는 객체 생성 결과로 얻을 수 있는 공통 인터페이스입니다. 각 제품 클래스는 Operation이라는 순수 가상 함수를 구현해야 합니다.
  • ConcreteProductAConcreteProductB는 Product를 상속받아 구체적인 제품 기능을 제공합니다

 

2. Creator 클래스와 구체적인 생성자 클래스들

  • Creator는 FactoryMethod()라는 순수 가상 함수를 통해 제품 객체를 생성하는 인터페이스입니다. SomeOperation()이라는 함수는 FactoryMethod()를 호출하여 생성된 제품 객체와 작업을 수행합니다.
  • ConcreteCreatorAConcreteCreatorB는 Creator를 상속하며, 각기 다른 ConcreteProduct 객체를 생성하여 반환하는 팩토리 메서드를 구현합니다.

3. 클라이언트 코드

클라이언트는 Creator 인터페이스를 통해 제품을 생성하므로, 실제로 어떤 ConcreteProduct가 반환되는지 알 필요가 없습니다.

이 예제에서는 ClientCode 함수가 Creator 객체를 통해 작업을 수행합니다. SomeOperation() 메서드가 제품 생성 과정을 감추므로, 클라이언트는 구체적인 생성자 클래스가 어떤 제품을 생성하는지 알 필요가 없습니다.

출력 결과

이 코드를 실행하면 다음과 같은 출력이 나옵니다:

패턴의 장점과 단점

장점

  1. 유연성: 클라이언트가 객체 생성에 구체적으로 관여하지 않아, 새로운 제품을 추가할 때 기존 클라이언트 코드를 수정할 필요가 없습니다.
  2. 객체 생성 캡슐화: 객체 생성 로직이 숨겨져 있어 코드가 더 간결하고 유지보수하기 쉽습니다.
  3. 확장성: 새로운 제품을 추가할 때 ConcreteCreator와 ConcreteProduct만 추가하면 되므로 코드의 확장성이 높습니다.

단점

  1. 클래스 수 증가: 새로운 ConcreteProduct와 이를 생성하는 ConcreteCreator가 필요하기 때문에 클래스 수가 늘어나 복잡도가 높아질 수 있습니다.
  2. 설계가 복잡해질 가능성: 팩토리 메서드 패턴을 사용하지 않아도 충분한 경우에도 과도하게 적용하면 코드가 불필요하게 복잡해질 수 있습니다.

활용 사례

  • 다양한 객체 생성이 필요한 상황: 예를 들어, 그래픽 프로그램에서 다양한 도형(원, 사각형, 삼각형 등)을 생성해야 하는 경우 각 도형별로 ConcreteProduct와 ConcreteCreator를 두면 코드 확장성이 높아집니다.
  • 객체 생성 로직이 복잡하거나 특정 조건에 따라 다른 객체를 생성해야 하는 경우: 다양한 요구 사항에 따라 객체 생성 방식이 달라질 때 유용합니다.

이로써 Factory Method 패턴을 C++로 구현하여 클라이언트와 객체 생성 로직을 분리하는 방법을 이해할 수 있습니다.

 

반응형

C++에서 싱글턴(Singleton) 패턴을 구현할 때 고려해야 할 부분들이 많습니다. 특히, 멀티스레드 환경과 메모리 관리, 파괴 시점에 대한 관리 등 여러 가지를 신중히 설계해야 합니다. 보다 자세히 설명하겠습니다.

싱글턴 패턴의 구조

싱글턴 클래스는 다음과 같은 요소들로 구성됩니다:

  1. 정적 인스턴스 포인터: 클래스 내의 static 포인터 변수로 싱글턴 인스턴스를 가리킵니다. 이는 해당 클래스가 단 하나의 객체만을 생성하는 것을 보장하기 위한 핵심 요소입니다.
  2. private 생성자: 생성자를 private으로 선언하여 외부에서 직접 객체를 생성하지 못하게 막습니다.
  3. public 정적 메서드 (getInstance): 싱글턴 객체에 접근하는 유일한 방법으로, 객체가 생성되지 않았을 때만 인스턴스를 생성하고 반환합니다. 이를 통해 전역적으로 접근 가능한 단일 인스턴스를 보장합니다.
  4. 복사 방지: 복사 생성자와 대입 연산자를 delete 처리하여 객체의 복사와 대입을 금지합니다. 이를 통해 중복 인스턴스 생성을 방지할 수 있습니다.
  5. 멀티스레드 안전성: 멀티스레드 환경에서도 싱글턴 인스턴스가 한 번만 생성되도록 mutex와 같은 락을 사용해 동기화합니다.

구체적인 구현 예제

1. 정적 포인터와 Mutex 초기화

싱글턴 클래스의 핵심은 전역적으로 접근할 수 있는 유일한 인스턴스를 갖는 것이므로, 정적 포인터 변수를 통해 객체를 관리합니다.

위 코드에서 getInstance()는 처음 호출될 때 instance가 nullptr인지 확인합니다. nullptr인 경우에만 뮤텍스를 잠근 상태로 인스턴스를 생성합니다. 두 번째 검사(if (instance == nullptr))는 여러 스레드에서 동시에 접근해 인스턴스가 두 번 생성되는 것을 방지하기 위함입니다.

 

2. 메모리 관리 (객체 해제)

싱글턴 패턴을 구현할 때는 메모리 관리에도 주의해야 합니다. C++에서 객체를 할당하고 명시적으로 해제하지 않으면 메모리 누수가 발생할 수 있습니다. 이를 방지하기 위해 정적 멤버 함수를 통해 프로그램 종료 시 인스턴스를 해제하는 방법을 사용할 수 있습니다.

C++11 이후 std::unique_ptr을 이용한 안전한 메모리 관리

C++11 이후에서는 std::unique_ptr을 사용하여 소멸자를 자동 호출해 메모리 관리를 할 수 있습니다.

위 예제에서는 std::unique_ptr을 통해 인스턴스를 자동으로 해제하므로 메모리 누수를 방지할 수 있습니다.

 

3. C++11의 call_once와 once_flag를 이용한 구현

C++11부터는 std::call_once와 std::once_flag를 사용하여 더 간단하고 안전하게 싱글턴을 생성할 수 있습니다.

std::call_once와 std::once_flag를 이용하면 mutex를 직접 사용하지 않고도 인스턴스를 안전하게 생성할 수 있습니다. std::call_once는 getInstance()가 호출될 때 instance가 nullptr일 때만 한 번 실행되므로 멀티스레드 환경에서의 중복 생성 문제를 효과적으로 해결합니다.

 

싱글턴 패턴의 장단점 정리

장점

  • 일관된 접근: 애플리케이션 내에서 단 하나의 인스턴스를 관리하므로 공통된 자원이나 상태를 안전하게 관리할 수 있습니다.
  • 자원 절약: 인스턴스가 하나만 존재하므로 불필요한 메모리 할당을 줄일 수 있습니다.
  • 전역 접근성: 인스턴스를 전역적으로 접근할 수 있어 코드 전반에서 유용하게 사용할 수 있습니다.

단점

  • 의존성 증가: 싱글턴 패턴은 전역 접근을 허용하여 코드의 의존성이 높아지고, 테스트 시 의존 객체를 주입하거나 분리하기 어려울 수 있습니다.
  • 메모리 관리: 명시적으로 객체를 소멸시키지 않으면 프로그램 종료 시까지 인스턴스가 남아 메모리 누수가 발생할 수 있습니다.
  • 멀티스레드 환경의 복잡성: 멀티스레드 환경에서는 잠금 관리나 동기화 처리가 필요해 복잡해질 수 있습니다.

이러한 요소들을 고려하여 필요한 경우에만 싱글턴 패턴을 사용하는 것이 좋습니다.

반응형

1. 디자인 패턴(Design Pattern)이란?

디자인 패턴은 개발하면서 발생하는 반복적인 문제들을 어떻게 해결할 것인지에 대한 해결 방안으로 실제 문제를 해결하기 위해 빈번하게 사용된 구조에 대해 정형화 한 것이다.

 

개발 도중 문제를 해결하기 위해 전체 구조를 설계하고 구현한 뒤, 추가적인 이슈와 문제로 인하여 구조를 계속적으로 변경시키게 된다. 이 때 매번 구조를 변경하게 되면 공수가 많이 필요하고, 혼자서 혹은 한팀 내에서 구조를 변경하게되면, 추가적으로 발생하는 문제들을 전부 고려할 수 없기에 계속적으로 문제를 직면하고, 계속적인 구조 변경이 필요한 경우가 빈번히 발생하게 된다.

 

디자인 패턴은 개발 구조 설계시 직면하는 문제들을 선제적으로 해결된 정형화된 패턴이라고 할 수 있다.

구조 설계시 디자인 패턴 내의 구조를 기반으로 구성하고 설계한다면 많은 시행착오를 줄일 수 있다.

 

만약 당신이 디자인 패턴에 대해 고려하지 않고 개발 구조를 설계하였다면, 그리고 당신의 경험이 전문가 수준이라면,

(동일 개발 구조 설계에 대한 많은 문제점을 인식하고, 선제적으로 문제점을 발생시키지 않는 구조로 설계하였다면)

개인적인 의견으로 디자인 패턴 중에 하나와 매우 유사한 구조로 설계되었지 않을까 싶다.

 

2. 디자인 패턴의 장점

  1. 재사용성 : 반복적인 문제에 대한 일반적인 해결책을 제공하므로, 이를 재사용하여 유사한 상황에서 코드를 더 쉽게 작성할 수 있다.
  2. 가독성 : 일정한 구조로 정리하고 명확하게 작성하여 개발자가 코드를 이해하고 유지보수하기 쉽게 만든다.
  3. 유지보수성 : 코드를 쉽게 모듈화 할 수 있으며, 변경이 필요한 경우 해당 모듈만 수정하여 유지보수가 쉬워진다.
  4. 확장성 : 새로운 기능을 추가하거나 변경할 때 디자인 패턴을 활용하여 기존 코드를 변경하지 않고도 새로운 기능을 통합할 수 있다.
  5. 안정성과 신뢰성 : 수많은 사람들이 인정한 모범 사례로 검증된 솔루션을 제공한다.

 

3. 디자인 패턴의 종류 

생성 패턴(Creational Pattern)

  1. Singleton(싱글톤 패턴) : 하나의 클래스 인스턴스를 전역에서 접근 가능하게 하면서 해당 인스턴스가 한 번만 생성되도록 보장하는 패턴이다.
  2. Factory Method(팩토리 메서드 패턴) : 객체를 생성하기 위한 인터페이스를 정의하고, 서브클래스에서 어떤 클래스의 인스턴스를 생성할지 결정하는 패턴이다.
  3. Abstract Factory(추상 팩토리 패턴) : 관련된 객체들의 집합을 생성하는 인터페이스를 제공하며, 구체적인 팩토리 클래스를 통해 객체 생성을 추상화하는 패턴이다.
  4. Builder(빌더 패턴) : 복잡한 객체의 생성 과정을 단순화하고, 객체를 단계적으로 생성하며 구성하는 패턴이다.
  5. Prototype(프로토타입 패턴): 객체를 복제하여 새로운 객체를 생성하는 패턴으로, 기존 객체를 템플릿으로 사용하는 패턴이다.

 

구조 패턴(Structural Pattern)

  1. Adapter(어댑터 패턴) : 인터페이스 호환성을 제공하지 않는 클래스를 사용하기 위해 래퍼(Wrapper)를 제공하는 패턴이다.
  2. Bridge(브릿지 패턴) : 추상화와 구현을 분리하여 두 가지를 독립적으로 확장할 수 있는 패턴이다.
  3. Composite(컴포지트 패턴) : 개별 객체와 복합 객체를 동일하게 다루어, 트리 구조의 객체를 구성하는 패턴이다.
  4. Decorator(데코레이터 패턴) : 객체에 동적으로 새로운 기능을 추가하여 객체를 확장할 수 있는 패턴이다.
  5. Facade(퍼사드 패턴) : 서브시스템을 더 쉽게 사용할 수 있도록 단순한 인터페이스를 제공하는 패턴이다.
  6. Flyweight(플라이웨이트 패턴) : 공유 가능한 객체를 통해 메모리 사용을 최적화하는 패턴이다.
  7. Proxy(프록시 패턴) : 다른 객체에 대한 대리자(Proxy)를 제공하여 접근 제어, 지연 로딩 등을 구현하는 패턴이다.

 

행위 패턴(Behavioral Pattern)

  1. Observer(옵저버 패턴) : 객체 간의 일대다 종속 관계를 정의하여 한 객체의 상태 변경이 다른 객체들에게 알려지도록 한다.
  2. Strategy(전략 패턴) : 알고리즘을 정의하고, 실행 중에 선택할 수 있게 한다.
  3. Command(커맨드 패턴) : 요청을 객체로 캡슐화하여 요청을 매개변수화 하고, 요청을 큐에 저장하거나 로깅하고 실행을 지연시킨다.
  4. State(상태 패턴) : 객체의 상태를 캡슐화하고, 상태 전환을 관리한다.
  5. Chain of Responsibility(책임 연쇄 패턴) : 요청을 보내는 객체와 이를 처리하는 객체를 분리하여, 다양한 처리자 중 하나가 요청을 처리한다.
  6. Visitor(방문자 패턴) : 객체 구조를 순회하면서 다양한 연산을 수행할 수 있게 한다.
  7. Interpreter(인터프리터 패턴) : 언어나 문법에 대한 해석기를 제공하여, 주어진 언어로 표현된 문제를 해결하는 패턴이다.
  8. Memento(메멘토 패턴) : 객체의 내부 상태를 저장하고 복원할 수 있는 기능을 제공하는 패턴이다.
  9. Mediator(중재자 패턴) : 객체 간의 상호 작용을 캡슐화하여, 객체 간의 직접적인 통신을 방지하는 패턴이다.
  10. Template Method(템플릿 메서드 패턴) : 알고리즘의 구조를 정의하면서 하위 클래스에서 각 단계의 구현을 제공하는 디자인 패턴이다.
  11. Iterator(이터레이터 패턴) : 컬렉션 내의 요소들에 접근하는 방법을 표준화하여 컬렉션의 내부 구조에 독립적으로 접근할 수 있는 패턴이다.
반응형

C++은 변수처리시 String 과 Int 의 변수 처리에 엄격함이 있고, 이로 인하여 Json 파싱시 각 Type 별 예외처리가 없다면,

Crash 가 발생할 확률이 높습니다. 그렇기에 Json 파싱시 경우의 수에 대한 예외처리가 필요하며, 이를 편하게 하기 위한 라이브러리들을 리뷰 해 보도록 할게요~

 

C++에서 JSON 데이터를 안전하게 처리하는 방법은 JSON 라이브러리를 활용하여 예외 처리, 데이터 유효성 검사, 타입 체크 등을 통해 구현할 수 있습니다. 일반적인 방법과 더불어 각 라이브러리에서 제공하는 기능을 사용하여 JSON 데이터를 안전하게 다루는 구체적인 예를 아래에 자세히 설명하겠습니다.

 

1. nlohmann/json 라이브러리

  • 특징: nlohmann/json 라이브러리는 직관적인 문법과 다양한 JSON 데이터 구조 지원으로 많이 사용됩니다.
  • 예외 처리 및 안전한 접근: JSON 파싱 및 데이터 접근에서 발생할 수 있는 오류를 예외 처리로 관리할 수 있습니다.

설치

  • nlohmann/json.hpp 헤더 파일을 프로젝트에 추가하거나, 패키지 관리자를 통해 설치할 수 있습니다 (예: vcpkg install nlohmann-json).

JSON 파싱 예제

 

  • contains 메서드: JSON 데이터가 특정 키를 포함하고 있는지 확인하여 접근 오류를 방지합니다.
  • 타입 체크: .is_string(), .is_number_integer() 등의 메서드를 사용해 값의 타입을 확인하여 안전성을 높입니다.
  • 예외 처리: json::parse_error, json::type_error 등을 통해 다양한 오류 상황을 예외로 처리할 수 있습니다.

 

2. rapidjson 라이브러리

  • 특징: rapidjson은 속도가 매우 빠르며, 메모리 효율이 높아 임베디드 시스템 등에서 많이 사용됩니다.
  • 데이터 접근 전 검증: HasMember와 IsType 메서드를 통해 안전하게 데이터를 다룰 수 있습니다.

설치

  • rapidjson은 헤더 파일로 구성되어 있어 프로젝트에 간단히 추가할 수 있습니다.

JSON 파싱 예제

  • HasMember: JSON 객체에 특정 키가 있는지 확인하여 접근 오류를 방지합니다.
  • 타입 체크: .IsString(), .IsInt() 등의 메서드를 사용하여 데이터 타입을 검증합니다.
  • 오류 검출: Parse 메서드를 통해 JSON 데이터 파싱 시 오류를 확인합니다.

3. picojson 라이브러리

  • 특징: picojson은 작은 크기의 C++ JSON 라이브러리로, 단순한 JSON 작업에 적합합니다.
  • 안전한 데이터 접근: 예외 처리보다는 JSON 데이터 존재 여부와 타입을 사전에 검사하여 안전하게 데이터를 다룹니다.

설치

  • picojson.h 파일을 프로젝트에 추가하면 됩니다.

JSON 파싱 예제

 

 

안전한 JSON 파싱을 위한 일반적인 접근 방법

  1. JSON 구조 검증: JSON 데이터가 원하는 형식을 가지고 있는지 확인합니다.
  2. 키와 타입 존재 여부 검사: 특정 키가 존재하고, 예상한 타입인지 확인합니다.
  3. 예외 처리: 예외를 발생시킬 수 있는 상황에서 try-catch 블록을 통해 안전하게 예외를 처리합니다.
  4. 오류 메시지 및 로그 관리: 오류가 발생할 경우, 사용자에게 의미 있는 오류 메시지를 제공하거나 로그에 기록합니다.

이처럼 C++에서 JSON 데이터를 안전하게 처리하기 위해 다양한 라이브러리와 기법을 활용할 수 있습니다.

 

반응형

C++ 에서 사용하는 Mutex 로 제공하는데, 사용할 때마다 사용 예제를 한번씩 살펴보게 되어 정리해 보았습니다.

std::lock_guard는 C++에서 여러 스레드가 공유하는 리소스에 대한 동기화를 쉽게 관리할 수 있게 해주는 클래스입니다. std::lock_guard는 생성될 때 mutex를 잠그고, 범위를 벗어나면 자동으로 mutex를 해제하여, 수동으로 잠금을 관리할 필요가 없게 만들어줍니다.

다음은 std::lock_guard를 사용한 예제입니다:

 
예제 코드

 

 

코드 상세 설명

  1. Mutex 선언 (std::mutex mtx;)
    mtx는 std::mutex 타입의 객체로, 프로그램 내에서 공유 자원을 안전하게 보호하기 위한 목적으로 사용됩니다. 이 mtx 객체는 잠금(Lock)과 해제(Unlock) 기능을 제공하여 하나의 스레드만 특정 코드 구간에 접근하도록 보장합니다.
  2. std::lock_guard 선언 (std::lock_guard<std::mutex> lock(mtx);)
    • std::lock_guard는 lock 객체가 생성되면서 mtx를 잠급니다. 이로 인해 다른 스레드가 print_numbers 함수에서 mtx를 사용하려고 하면, lock이 해제될 때까지 기다리게 됩니다.
    • lock은 print_numbers 함수가 끝날 때(즉, 스코프를 벗어날 때) 자동으로 소멸됩니다. std::lock_guard의 소멸자(destructor)가 호출되면서 mtx가 자동으로 해제됩니다. 이를 통해 우리가 직접 mtx.unlock()를 호출할 필요가 없어, 코드가 간결해지고 오류를 줄일 수 있습니다.
  3. 스레드 생성 (std::thread t1(print_numbers, 1); std::thread t2(print_numbers, 2);)
    • std::thread를 이용해 두 개의 스레드 t1과 t2를 생성합니다.
    • 각 스레드는 print_numbers 함수의 복사본을 실행하며, id 매개변수를 통해 구분됩니다(첫 번째 스레드는 id가 1이고, 두 번째 스레드는 2).
  4. 출력
    • 두 스레드가 실행되면, std::cout으로 1부터 5까지의 숫자를 출력하게 됩니다.
    • 스레드가 공유 자원인 std::cout에 접근할 때 std::lock_guard가 mtx를 잠그므로, 한 스레드가 std::cout을 통해 출력 중이면 다른 스레드는 대기합니다.
    • 만약 std::lock_guard가 없었다면, 두 스레드가 동시에 std::cout에 접근할 수 있어 출력이 섞이거나 예기치 못한 결과가 발생할 수 있습니다.
  5. 스레드 종료 대기 (t1.join(); t2.join();)
    • t1.join()과 t2.join()은 main 함수가 두 스레드가 끝날 때까지 기다리도록 합니다.
    • join을 호출하지 않으면, main 함수가 스레드가 끝나기 전에 종료될 수 있습니다.

 

std::lock_guard가 필요한 이유

  • 안전한 동시성 보장: std::lock_guard는 스레드가 공유 자원에 접근할 때, 다른 스레드가 해당 자원에 접근하지 못하게 막아줍니다. 이를 통해 스레드 간 충돌을 방지하고 데이터 무결성을 유지합니다.
  • 자동 해제: std::lock_guard는 스코프를 벗어날 때 자동으로 mutex를 해제하므로, 실수로 unlock을 호출하지 않거나, 예외 상황에서 해제를 누락하는 등의 문제를 예방합니다.

이 예제에서는 print_numbers 함수 내의 std::cout 출력을 동기화해, 두 스레드의 출력이 섞이지 않도록 보장하고 있습니다.

반응형

std::vector는 C++ 표준 라이브러리에서 제공하는 동적 배열 클래스입니다. 크기가 가변적이며, 요소 추가 및 삭제 시 자동으로 크기를 조정합니다. std::vector는 내부적으로 연속적인 메모리 블록에 데이터를 저장하므로, 요소에 대한 임의 접근이 빠릅니다.

 

주요 특징

  1. 동적 크기 조정: 필요에 따라 크기를 자동으로 조정합니다.
  2. 임의 접근: 배열처럼 인덱스를 사용하여 요소에 빠르게 접근할 수 있습니다.
  3. 메모리 관리: 메모리 할당 및 해제를 자동으로 처리합니다.
  4. 유연한 사용: 다양한 편의 메서드(push_back, pop_back 등)를 제공하여 사용이 간편합니다.

 

예제

 

 

주요 메서드

  • push_back(value): 벡터 끝에 요소를 추가합니다.
  • pop_back(): 벡터 끝의 요소를 제거합니다.
  • size(): 벡터의 현재 크기를 반환합니다.
  • capacity(): 벡터가 메모리를 재할당하지 않고 저장할 수 있는 최대 요소 수를 반환합니다.
  • empty(): 벡터가 비어 있는지 확인합니다.
  • clear(): 벡터의 모든 요소를 제거합니다.
  • at(index): 인덱스를 사용하여 요소에 접근합니다(범위 검사 포함).

 

장점

  • 배열과 달리 크기를 미리 알 필요가 없습니다.
  • 메모리 관리를 신경 쓸 필요가 없습니다.

 

단점

  • 메모리 재할당 시 성능이 저하될 수 있습니다.
  • 배열보다 메모리 오버헤드가 더 클 수 있습니다.

std::vector는 C++에서 유용하고 많이 사용되는 컨테이너로, 대부분의 상황에서 배열보다 더 유연하게 사용할 수 있습니다.

반응형

라인 드로우를 구현할때, openGL, DX, winDC 등을 사용할 수 없는경우,

바닥부터 맨땅에 코딩을 해야하는 경우가 생기더라.

그때를 위한 참고용 코드.

 

Void DrawLine(int x1, int y1, int x2, int y2)
{
    int aX=0, aY=0;
    int cnt = 0;
    int dx = x2-x1;
    int dy = y2-y1;

    if(dx < 0) {addX = -1; dx = -dx;}
    else addX = 1;
    if(dy < 0) {addY = -1; dy = -dy;}
    else addY = 1;
    
    int x = x1;
    int y = y1;
    if(dx >= dy)
    {
        for(int i = 0 ; i < dx ; i++)
        {
            x += addX;
            cnt += dy;
            if(cnt >= dx)
            {
                y += addY; cnt -= dx;
            }
            drawPoint(x,y);
        }
    }
    else
    {
        for(int i = 0 ; i < dy ; i++)
        {
            y+= addY;
            cnt += dx;
            if(cnt >= dy)
            {
                x += addX; cnt -= dy;
            }
            darwPoint(x,y);
        }
    }
}

 

 

+ Recent posts