Study/TIL(Today I Learned)

24.08.30 CS, C++

에린_1 2024. 8. 30. 19:31
728x90

CS

상태(State) 패턴

  • 상태 패턴은 객체가 특정 상태에 따라 행위를 달리하는 상황에서, 상태를 조건문으로 검사해서 행위를 달리하는 것이 아닌, 상태를 객체화하여 행동을 할 수 있도록 위임하는 패턴을 말한다.
  • 객체 지향 프로그래밍에서의 클래스는 꼭 사물/생물 만을 표현하는 고체 형태의 데이터만 표현 할 수 있는게 아니다. 경우에 따라 무형태의 행위/동작도 클래스로 묶어 표현할 수 있다.
  • 그래서 상태를 클래스로 표현하면 클래스를 교체해서 상태의 변화를 표현할 수 있고, 객체 내부 상태 변경에 따라 객체의 행동을 상태에 특화된 행동들로 분리해 낼 수 있으며, 새로운 행동을 추가하더라도 다른 행동에 영향을 주지 않는다.
    • 여기서 상태란 객체가 가질 수 있는 어떤 조건이나 상황을 의미한다.
    • 이처럼 객체가 특정 상태에 따라 행위를 달리하는 상황에서 사용되는 최적의 패턴이 state pattern 이라고 보면 된다.
  • 전략 패턴(Strategy Pattern)이 전략 알고리즘을 클래스로 표현한 패턴이라면, 상태 패턴은 객체 상태를 클래스로 표현한 패턴이라고 보면 된다. 그래서 상태 패턴의 클래스 다이어그램을 보면 전략 패턴과 유사한 점을 볼 수 있는데 전략 패턴은 전략을 객체화 한거고, 상태 패턴은 상태를 객체화 한 것인데 둘 다 똑같은 클래스 묶음이기 때문이다.

상태 패턴 구조

  • State 인터페이스
    • 상태를 추상화한 고수준 모듈
  • ConcreteState
    • 구체적인 각각의 상태를 클래스로 표현. State 역할로 결정되는 인터페이스(API)를 구체적으로 구현한다. 다음 상태가 결정되면 Context에 상태 변경을 요청하는 역할도 한다.
  • Context
    • State를 이용하는 시스템. 시스템 상태를 나타내는 State 객체를 합성(composition)하여 가지고 있다. 클라이언트로부터 요청받으면 State 객체에 행위 실행을 위임한다.
  • 상태 클래스는 싱글톤 클래스로 구성한다. 전략 패턴의 전략 객체 같은 경우 매개 값에 따라 알고리즘 수행 형태가 달라질 수 있지만, 상태는 그 객체의 현 폼을 나타내는 것이기 때문에 대부분의 상황에서 유일하게 있어야 한다.

상태 패턴 특징

  • 상태 패턴 사용 시기
    • 객체의 행동(메서드)가 상태(state)에 따라 각기 다른 동작을 할 때.
    • 상태 및 전환에 걸쳐 대규모 조건 분기 코드와 중복 코드가 많을 경우
    • 조건문의 각 분기를 별도의 클래스에 넣는것이 상태 패턴의 핵심
    • 런타임단에서 객체의 상태를 유동적으로 변경해야 할때
  • 상태 패턴 장점
    • 상태(State)에 따른 동작을 개별 클래스로 옮겨서 관리할 수 있다.
    • 상태(State)와 관련된 모든 동작을 각각의 상태 클래스에 분산시킴으로써, 코드 복잡도를 줄일 수 있다.
    • 단일 책임 원칙을 준수할 수 있다.(특정 상태와 관련된 코드를 별도의 클래스로 구성)
    • 개방 폐쇄 원칙을 준수할 수 있다.(기존 State 클래스나 컨텍스트를 변경하지 않고 새 State를 도입할 수 있다.
    • 하나의 상태 객체만 사용하여 상태 변경을 하므로 일관성 없는 상태 주입을 방지하는데 도움이 된다.
  • 상태 패턴 단점
    • 상태 별로 클래스를 생성하므로, 관리해야할 클래스 수 증가
    • 상태 클래스 갯수가 많고 상태 규칙이 자주 변경된다면, Context의 상태 변경 코드가 복잡해지게 될 수 있다.
    • 객체에 적용할 상태가 몇 가지 밖에 없거나 거의 상태 변경이 이루어지지 않는 경우 패턴을 적용하는 것이 과도할 수 있다.

State vs Strategy

  • 패턴 유사점
    • 전략 패턴과 상태 패턴은 클래스 다이어그램이 거의 동일하고 코드 사용법도 비슷하다.
    • 둘다 난잡한 조건 분기를 극복하기 위해 전략 / 상태 형태를 객체화한
    • 둘다 합성(composition)을 통해 상속의 한계를 극복한다
    • 둘다 객체의 일련의 행동이 캡슐화되어 객체 지향 원칙을 준수한다.
    • State는 Strategy의 확장으로 간주될 수도 있다.
  • 패턴 차이점
    • 전략 패턴과 상태 패턴의 구조는 거의 같지만 어떤 목적을 위해서 사용되는가에 따라 차이가 있다.
    • 전략 패턴은 알고리즘을 객체화 하여 클라이언트에서 유연적으로 전략을 제공 / 교체를 한다. 상태 패턴은 객체의 상태를 객체화하여 클라이언트와 상태 클래스 내부에서 다른 상태로 교체를 한다.
    • 전략 패턴의 전략 객체는 그 전략만의 알고리즘 동작을 정의 및 수행한다. (만일 전략을 상태화 하면 클래스 폭발이 일어날 수 있다) 상태 패턴의 상태 객체는 상태가 적용되는 대상 객체가 할수있는 일련의 모든 행동들을 정의 및 수행한다.
    • 전략 패턴의 전략 객체는 입력값에 따라 전략 형태가 다양하게 될 수 있으니 인스턴스로 구성한다. 상태 패턴의 상태 객체는 정의된 상태를 서로 스위칭 하기에 메모리 절약을 위해 싱글톤으로 구성한다.

참조

https://inpa.tistory.com/entry/GOF-💠-상태State-패턴-제대로-배워보자

C++

NEW

  • new와 malloc 모두 동적으로 할당하지만 new의 경우객체를 동적으로 생성하면서 동시에 자동으로 생성자도 호출해준다.

소멸자(Destrucotr)

  • 우리가 명확히 delete를 지정하지 않는 한 자동으로 delete가 되는 경우는 없다. 그렇게 된다면 메모리 누수(Memory Leak)이라는 문제점이 발생하게 된다.
  • 우리가 생성했던 객체가 소멸 될 때 자동으로 호출되는 함수 - 마치 객체가 생성될 때 자동으로 호출 되었던 생성자 처럼 소멸 될 때 자동으로 호출되는 함수가 소멸자 이다.
  • 생성자가 클래스의 이름과 똑같이 생겼다면 소멸자는 그 앞에 ~ 만 붙여주면 된다.
  • 우리가 따로 생성자를 정의하지 않더라도 디폴트 생성자가 있었던 것 처럼, 소멸자도 디폴트 소멸자(Default Destructor)가 있다. 물론, 디폴트 소멸자 내부에선 아무런 작업도 수행하지 않는다. 만일 소멸자가 필요없는 클래스라면 굳이 소멸자를 따로 써줄 필요 없다.

복사 생성자

  • C++에서 복사 생성자(Copy Constructor)는 객체를 복사하여 새로운 객체를 생성할 때 사용되는 생성자이다. 복사 생성자는 이미 존재하는 같은 클래스의 객체를 복사하여 새로운 객체를 만들 수 있게 한다.
  • 복사 생성자의 정의와 특징
    • 복사 생성자는 다음과 같은 형태로 정의된다:
    cpp코드 복사
    ClassName(const ClassName &other);
    
    
    • 여기서 ClassName은 클래스의 이름이고, other는 복사할 원본 객체에 대한 참조(reference)이다.
  • 복사 생성자의 기본 동작
    • C++ 컴파일러는 기본 복사 생성자를 자동으로 제공한다. 이 기본 복사 생성자는 멤버wise copy를 수행하며, 이는 객체의 모든 멤버 변수를 원본 객체의 해당 멤버 변수 값으로 복사한다는 의미이다.
  • 사용자 정의 복사 생성자
    • 경우에 따라 기본 복사 생성자가 충분하지 않을 수 있다. 예를 들어, 동적 메모리를 사용하는 객체가 있을 때 얕은 복사(Shallow Copy)로 인해 메모리 누수나 예기치 않은 동작이 발생할 수 있다. 이런 경우 사용자 정의 복사 생성자가 필요하다.
  • 언제 복사 생성자가 호출될까?
    1. 객체를 다른 객체로 초기화할 때
    2. 객체가 함수로 전달될 때
    3. 함수에서 객체를 반환할 때
  • 복사 생성자 깊은 복사 vs 얕은 복사
    • 얕은 복사(Shallow Copy)는 기본 복사 생성자가 수행하며, 멤버 변수의 메모리 주소를 복사한다. 이는 포인터 멤버 변수가 있는 클래스에서 문제가 될 수 있다.
    • 깊은 복사(Deep Copy)는 사용자 정의 복사 생성자가 수행하며, 포인터 멤버가 가리키는 실제 데이터를 복사하고 별도의 메모리를 할당한다.
    • #include <iostream> class MyClass { public: int* data; // 기본 생성자 MyClass(int value) { data = new int(value); } // 사용자 정의 복사 생성자 (깊은 복사) MyClass(const MyClass& other) { data = new int(*(other.data)); // 깊은 복사 수행 } // 소멸자 ~MyClass() { delete data; } }; int main() { MyClass obj1(10); MyClass obj2 = obj1; // 복사 생성자가 호출된다. // obj1.data와 obj2.data는 다른 메모리 주소를 가진다. std::cout << "obj1 data: " << *(obj1.data) << std::endl; std::cout << "obj2 data: " << *(obj2.data) << std::endl; *(obj2.data) = 20; // obj2의 데이터를 변경한다. // obj1의 데이터는 변경되지 않는다. std::cout << "obj1 data after change: " << *(obj1.data) << std::endl; std::cout << "obj2 data after change: " << *(obj2.data) << std::endl; return 0; }
  • 위 예제에서 깊은 복사를 수행하는 복사 생성자가 정의되어 있으므로, obj1과 obj2는 각각 다른 메모리를 가진다. 따라서 하나의 객체를 수정해도 다른 객체에 영향을 주지 않는다.
728x90

'Study > TIL(Today I Learned)' 카테고리의 다른 글

24.09.03 CS, C++  (0) 2024.09.03
24.09.02 CS, 언리얼  (1) 2024.09.02
24.08.29 CS, C++  (0) 2024.08.29
24.08.27 CS, C++  (0) 2024.08.27
24.08.26 CS, C++  (0) 2024.08.26