IEEE Std에서의 Architecture 정의

1. 시스템의 환경 (HW, SW, OS, Network, 구동 실환경 등..)

2. 하나 이상의 컴포넌트들 간의 관계 (ICD)

3. 설계와 발전 (확장성, 품질, 생명 주기 등..)

1,2,3을 관리하는 원칙으로 이루어진 시스템 구조

 

Gof(Gang of Four)의 Ralph Johnson 의견

1. IEEE std에서 정의한 Architecture의 정의가 거시적임.

2. 실제 소프트웨어를 개발하는 개발자들의 상식이 소프트웨어 프로젝트를 이끄는데 중요하고, 아키텍쳐에 영향을 끼칠 것임. 

 

전문 개발자들은 정보를 공유함.

 

3. 중요한 것은 프로젝트에 대한 이해도가 개발자들 간에 잘 공유되어야함!!!

4. 결정들은 바꾸기 어렵다! (핵심 기능, 프래그래밍 언어 등..)

5. 올바른 결정은 요구하는 시스템에 대한 정보를 알아야한다.

 

6. 지식을 잘 공유하고, 바꾸기 어려운것 것들을 모두 포괄하여 SW를 설계하기 위해서는

시스템에서 무엇이 핵심인지 알아야한다!!

7. SW 아키텍처는 SW 장인 정신 보다는 경제적 관점을 중요시해야한다.

8. 우리의 SW를 사용하는 고객은 SW 좋은 디자인을 보는 사람은 아님, 친절한 UI, 적은 결함이 중요시 함.(사용자 경험 등..)

9. 그리고 좋은 SW 디자인은 장기간으로 봤을 때 유지비용 시간이 적고, 기능추가에 용이해짐!

10. SW의 퀄리티는 크게 2가지로 나뉨, 외부적 퀄리티, 내부적 퀄리티

 - 외부적 퀄리티 : 직관적인 UI, 적은 버그

 - 내부적 퀄리티 : SW 디자인

11. 디자인이 없는 SW는 기능을 추가 할 수록 시간이 매우 많이 들게 될 것임.

12. 꾸준한 리펙토링과 모듈화를 통해 기능을 분리한 SW를 시간이 지나도 SW 기능 추가가 쉬울 것임.

 

영상 링크 : https://www.youtube.com/watch?v=4E1BHTvhB7Y

스테이트 패턴 정의

객체 내부에 구성되어있는 상태에 따라 객체의 행동을 바꾸는 패턴이다.  

 

필요한 상황

스테이트 패턴

- 로봇의 상태에 따라 수행하는 동작을 바꾸고 싶을 때

- GUI 프로그램에서 상태에 따라 버튼의 Enable, Disable 등 여부를 바꾸고 싶을 때

- 상태 변수를 switch로 넣어서 case로 때려 넣은 코드가 있을 때 (리펙토링 대상!!)

 

Class 다이어그램

스테이트 패턴

코드

스테이트 패턴

public interface State {
    void insertQuarter();
    void ejectQuarter();
    void turnCrank();
    void dispense();
}

public class HasQuarterSate : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    public void insertQuarter()=> Console.WriteLine("손잡이를 돌렸습니다.");
        
    public void ejectQuarter(){
        Console.WriteLine("동전이 반환됩니다.");
        gumballMachine.setState(gumballMachine.noQuarterState);
    }
    public void turnCrank() => Console.WriteLine("손잡이를 돌렸습니다.");
    public void dispense()=> Console.WriteLine("알맹이가 나갈수 없습니다.");
}

public class SoldState : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    public void insertQuarter()=> Console.WriteLine("잠깐만 기다려 주세요. 알맹이가 나가고 있습니다.");
    public void ejectQuarter()=> Console.WriteLine("이미 알맹이를 뽑으셨습니다.");
    public void turnCrank(){}=> Console.WriteLine("손잡이는 한 번만 돌려주세요.");
    public void dispense(){
        gumballMachine.releaseBall();
        if ( gumballMachine.count > 0){
            gumballMachine.setState(gumballMachine.noQuarterState);
        }
        else{
            Console.WriteLine("더이상 동전이 없습니다");
            gumballMachine.setState(gumballMachine.soldOutState);
        }
    }
}

public class SoldOutState : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    //인터페이스 구현체..
}

public class NoQuarterState : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    //인터페이스 구현체..
}

인터페이스와 구현체

 

public class GumballMachine {
    State soldOutState {get;set;}
    State noQuarterState{get;set;}
    State hasQuarterSate{get;set;}
    State soldState{get;set;}
    
    State state = soldOutState;
    int count {get;set;};
    
    public GumballMachine(int numberGumballs){
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterSate = new HasQuarterSate(this);
        soldState = new SoldState(this);
        this.count = numberGumballs;
        if (numberGumballs > 0){
            state = noQuarterState;
        }
    }
    
    public void insertQuarter(){
        state.insertQuarter();
    }
    
    public void ejectQuarter(){
        state.ejectQuarter();
    }
    
    public void turnCrank(){
        state.turnCrank();
        state.dispense();
    }
    
    public void dispense(){
        state.dispense();
    }
    public void setState(State state){
        this.state = state;
    }
    
    public void releaseBall(){
        Console.WriteLine("A gumball comes rolling out the solt.....");
        if (count != 0){
            count = count - 1;
        }
    }
}

상태패턴을 이용하는 Context Class

코드 설명

스테이트 패턴

1. 인터페이스와 구현체

public interface State {
    void insertQuarter();
    void ejectQuarter();
    void turnCrank();
    void dispense();
}

Context Class에서 제공하는 기능을 인터페이스 함수로 대체했다.

 

public class HasQuarterSate : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    public void insertQuarter()=> Console.WriteLine("손잡이를 돌렸습니다.");
        
    public void ejectQuarter(){
        Console.WriteLine("동전이 반환됩니다.");
        gumballMachine.setState(gumballMachine.noQuarterState);
    }
    public void turnCrank() => Console.WriteLine("손잡이를 돌렸습니다.");
    public void dispense()=> Console.WriteLine("알맹이가 나갈수 없습니다.");
}

인터페이스의 구현체는 Context Class 인스턴스를 가지고 있으며, 인터페이스 구현체를 직접 실행하면서 상태 변경이 필요한 경우 Context Class 인스턴스의 상태를 직접 변경한다. 이부분에서 Context Class와의 종속성이 생긴다. (결합성이 올라감..)

 

2. 스테이트패턴을 이용하는 Context Class

Context Class는 스테이트 구현체들을 모두 가지고 있으며, 기능 실행이 필요할 때 인터페이스 구현체들의 함수만 호출한다.

설명 보충을 위한 그림 자료

스테이트 패턴

현재 상태 : HasQuarter, 상태별로 구현한 인터페이스 구현체들(슬롯 머신 기능 + 상태 천이 기능)

 

현재 상태 : Sold, 상태별로 구현한 인터페이스 구현체들(슬롯 머신 기능 + 상태 천이 기능)

필요에 따라 추가 설명

x

정리 및 결론

스테이트 패턴은 현재 프로그램의 상태에 따라 기능을 달리해야 할 때 적용할만한 패턴이다. 주요 특징은 아래와 같다.

1. 상태 인터페이스를 두고 그에 맞는 구현체들을 모든 상태 별로 구현한다.

2. 상태 인터페이스 구현체들은 온전히 자신의 기능만을 수행하며 상태 천이가 필요한 경우 Context Class의 상태 천이를 수행한다.

3. 전략 패턴과 유사하다. 

개인적으로 상태 패턴을 실제로 사용한 사례를 보고싶다. 전략 패턴, 옵저버 패턴, 팩토리 패턴, 데코 패턴은 많이 쓰인다고 들었는데 상태 패턴을 사용하는 사례를 보지 못했다. 

'Program > SW Design Patterns' 카테고리의 다른 글

Mediator 패턴 (C++)  (0) 2024.07.31
CH7 어댑터&퍼사드 패턴  (0) 2023.03.19
CH6 커맨드 패턴  (0) 2023.03.07
CH5 데코레이터 패턴  (1) 2023.03.05
CH4 팩토리 패턴  (0) 2023.03.04
class Vector
{

private:
    float x;
    float y;
    float z;

public:
    Vector(float x, float y, float z):
    x(x), y(y), z(z)
    {
    }
    //단항 연산자 -
    Vector operator-() const
    {
        return Vector(-x,-y,-z);
    }
    
    //단항 연산자 +
    Vector operator+() const
    {
        return *this;
    }
    
    //이항 연산자 +
    Vector operator+(const Vector& v) const
    {
        return Vector(this.x + v.x, this.y + v.y, this.z + v.z);
    }
    
    //이항 연산자 -
    Vector operator-(const Vector& v) const
    {
        return Vector(this.x - v.x, this.y - v.y, this.z - v.z);
    }
    
    //스칼라 이항 연산자 *
    Vector operator*(float v) const
    {
        return Vector(this.x * v, this.y * v, this.z * v);
    }
    
    //Vector 이항 연산자 * (dot product)
    Vector operator*(Vector v) const
    {
        return this.x * v.x +  this.y * v.y + this.z * v.z;
    }
    
    //이항 연산자 /
    Vector operator/(float v) const
    {
        return Vector(this.x / v, this.y / v, this.z / v);
    }
    
    //전위 연산자 --
    Vector& operator--()
    {
        --x;
        --y;
        --z;
        return *this;
    }
    
    //전위 연산자 ++
    Vector& operator++()
    {
        ++x;
        ++y;
        ++z;
        return *this;
    }
    
    //후위 연산자 ++
    Vector& operator++(int)
    {
        Vector temp = *this;
        ++(*this);
        return temp;
    }
    
    friend Vector operator*(float v1, const Vector& v2);
    
    //후위 연산자 --
    Vector& operator--(int)
    {
        Vector temp = *this;
        --(*this);
        return temp;
    }   
}

Vector operator*(float v1, const Vector& v2)
{
    return Vector(v2.x * v1, v2.y * v1, v3.z * v1);
}

int main()
{
    const Vector v1(1,1,1);
    const Vector v2(2,2,2);
    
    Vector v3 = +v1;
    Vector v4 = -v1;
    Vector v5 = ++v3;
    Vector v6 = --v4;
    Vector v7 = v1++;
    Vector v8 = v1--;
    Vector v9 = v1 + v2;
    Vector v10 = v1 - v2;
    Vector v11 = v1 * 8.0f;
    Vector v12 = v1 / 8.0f;
    float v13 = v11 * v12
    
    Vector v14 = 3.0f * v12; 
}

구현한 연산자 오버로딩은 사칙 연산을 부분적으로 구현한 것이다.

 

몇가지 유의사항은 아래와 같다.

 

1.  - 오버로딩 함수

Vector operator-() const
{
    return Vector(-x,-y,-z);
}

- 오버로딩 함수에 const 를 붙이는 이유는 const this 포인터를 사용하기 위함이다. (사용자가 const 객체를 이용할 경우를 대비함)

 

2. 후위 연산자 오버로딩 함수

//후위 연산자 ++
Vector& operator++(int)
{
    Vector temp = *this;
    ++(*this);
    return temp;
}

후위 연산자 오버로딩 함수는 한번의 복사가 필요하다. 

 

 

3. 만든 객체의 연산자 오버로딩 다른 방법

class Vector
{
    //code...
    friend Vector operator*(float v1, const Vector& v2);
}

우선 전역에 함수를 정의 하기전에 Vector class의 private으로 숨긴 데이터를 이용하기 위해 friend 키워드로 접근 가능하게 한다.

Vector operator*(float v1, const Vector& v2)
{
    return Vector(v2.x * v1, v2.y * v1, v3.z * v1);
}

전역으로 위와같이 함수를 구현하면 아래와 같이 순서를 바꿔서도 만들 수 있다. (but 코드구조가 별로 인듯..)

Vector v14 = 3.0f * v12;

 

 

쿠키 코딩 끝!

 

 

 

'Program > C,C++' 카테고리의 다른 글

멤버 함수 포인터  (0) 2023.06.28
const 멤버 함수  (0) 2023.06.28
this 포인터를 이용한 빌더 패턴  (0) 2023.06.26
Class 전방 선언  (0) 2023.06.26
#include <iostream>
#include <functional>

using namespace std;

class Person
{
public:
    void print(int i)
    {
        cout << i << endl;
    }
    
};


int main()
{
    function<void(Person*, int)> func = &Person::print;
    
    Person person;
    func(&person, 1);
}

functional 라이브러리에 포함되어 있는 function <t>를 사용하여 객체의 멤버함수 포인터를 얻는 방법이다.

 

function<void(Person*, int)> func = &Person::print;

위의 코드에서 &Person::print는 pring 함수의 주소 값을 의미한다.

 

func(&person, 1);

func 안에 &person 을 넣는 이유는 클래스 내부의 멤버함수는 항상 암시적으로 this 포인터가 첫 번째 매개 변수로 들어가기 때문이다.

 

배운 것

1. function 을 이용한 class 멤버 함수 포인터 정의 간략화

'Program > C,C++' 카테고리의 다른 글

연산자 오버로딩  (0) 2023.07.02
const 멤버 함수  (0) 2023.06.28
this 포인터를 이용한 빌더 패턴  (0) 2023.06.26
Class 전방 선언  (0) 2023.06.26
class Person
{
private:
    const string _name;
    float _weight;
    float _height;
public:
    Person(const string& name, float weight, float height)
        : _name(name), _weight(weight), _height(height)
    {
    }
    
    float getWeight(/*const Person* this */) const
    {
        return _weight;
    }
};

int main()
{
    const Person person0("Donghee", 84.f, 178.8.f);
    person0.getWeight();
}

Person class의 getWeight에 const 키워드를 붙임으로써 Class 사용자는 Person Class를 Const로 선언하고 해당 멤버함수를 사용할 수 있다.

 

새로 배운 점

1. Class의 멤버함수는 암시적으로 this 포인터를 받는다.

2. Class를 const로 선언하는 사용하는 것을 대비할 때에는 get 함수에 대해 const 키워드를 붙이자. 

'Program > C,C++' 카테고리의 다른 글

연산자 오버로딩  (0) 2023.07.02
멤버 함수 포인터  (0) 2023.06.28
this 포인터를 이용한 빌더 패턴  (0) 2023.06.26
Class 전방 선언  (0) 2023.06.26
struct Transaction
{
    const int txID;
    const int fromID;
    const int toID;
    const int value;
}

class TransactionBuilder
{
private:
    int _fromID;
    int _toID;
    int _value;
    
public:
    Transaction build()
    {
    	int txID = _fromID ^ _toID ^ _value;
        return Transaction {txID, _fromID, _toID, _value};
    }
    
    TransactionBuilder& setFromID(int fromID)
    {
    	_fromID = fromID;
        return *this;
    }
    
    TransactionBuilder& settoID(int toID)
    {
    	_toID = toID;
        return *this;
    }
    
    TransactionBuilder& setvalue(int value)
    {
    	_value = value;
        return *this;
    }
}

int main()
{
    Transaction ts = TransactionBuilder().
    setFromID(10).
    settoID(20).
    setvalue(30).
    build();
}

 

 

this 포인터를 이용한 빌더 패턴이다.

 

set 함수들의 반환이 객체의 참조를 반환하는데 이때 현재 객체의 포인터 지정자를 반환하여 자기 자신을 반환하는 원리이다.

'Program > C,C++' 카테고리의 다른 글

연산자 오버로딩  (0) 2023.07.02
멤버 함수 포인터  (0) 2023.06.28
const 멤버 함수  (0) 2023.06.28
Class 전방 선언  (0) 2023.06.26

Village.h 파일 코드

#include <vector>

Class Person;
Class Village
{
private:
	std::vector<Person> persons;
public:
	void add(Person);
}

Village.cpp 파일 코드

#include "Village.h"
#include "Person.h"

void Village::add(Person person)
{
	persons.push_back(person)
}

 

1. Village 는 Person class를 사용한다. 하지만 Person의 Header 파일을 사용하지 않는다.

-> 의존 관계를 풀기 위해 -> Person의 코드가 수정될 경우 Village 까지 연쇄적으로 재빌드 해야함.

2. Village의 add 함수는 Village의 Header파일에서 선언만하고 정의는 Village.cpp 파일에서 정의함.

 

 

'Program > C,C++' 카테고리의 다른 글

연산자 오버로딩  (0) 2023.07.02
멤버 함수 포인터  (0) 2023.06.28
const 멤버 함수  (0) 2023.06.28
this 포인터를 이용한 빌더 패턴  (0) 2023.06.26

어댑터 패턴 정의

한 클래스의 인터페이스를 다른 클래스의 인터페이스로변환시켜주는 기능을 제공하는 패턴

퍼사드 패턴 정의

여러 클래스의 인터페이스들을 하나의 통합 클래스로 통합한 패턴 

필요한 상황

어댑터 패턴

- 새로운 인터페이스를 이미 개발되어 있는 인터페이스로 변환시키고 싶을 때 (Array -> List 적용 등..)

퍼사드 패턴

- 나열되어 있는 여러 인터페이스를 통합하여 동시 실행 등 다양한 기능을 제공하고 싶을 때 (매크로 기능..)

Class 다이어그램

어댑터 패턴

퍼사드 패턴

코드

어댑터 패턴

서로 다른 인터페이스 코드

public interface VoltOf110
{
    void volt110On();
    void volt110Off();
}

public interface VoltOf220
{
    void volt220On();
    void volt220Off();
}

기존의 VoltOf110 인터페이스로 구현된 코드

public class DVDPlayer : VoltOf110
{
    public void volt110Off()
    {
        System.Console.WriteLine("DVD Player Off by 110Volt Adapter");
    }
    
    public void volt110On()
    {
        System.Console.WriteLine("DVD Player On by 110Volt Adapter");
    }
}

110Volt To 220 Volt 클래스

public class Converter110To220 : VoltOf220
{
    VoltOf110 iVoltOf110;

    public Converter110To220(VoltOf110 iVoltOf110)
    {
        this.iVoltOf110 = iVoltOf110;
    }

    public void volt220Off()
    {
        iVoltOf110.volt110Off();
    }

    public void volt220On()
    {
        iVoltOf110.volt110On();
    }
}

220 volt 사용 클래스 코드

public class Controller
{
	VoltOf220 ivoltOf220;
	public Controller(VoltOf220 ivoltOf220)
	{
		this.ivoltOf220 = ivoltOf220;
	}
	
	public void turnOn()
	{
		ivoltOf220.volt220On();
	}
	
	public void turnOff()
	{
		ivoltOf220.volt220Off();
	}
}

메인 코드

DVDPlayer dvdPlayer = new DVDPlayer();
Converter110To220 converter110To220 = new Converter110To220(dvdPlayer);
Controller controller = new Controller(converter110To220);
controller.turnOn();
controller.turnOff();

퍼사드 패턴

다양한 인터페이스 코드

public interface Something1
{
    void doSomeThing1();
}

public interface Something2
{
    void doSomeThing2();
}

public interface Something3
{
    void doSomeThing3();
}

public interface Something4
{
    void doSomeThing4();
}

다양한 인터페이스 구현 코드

public class Something1_object : Something1
{
    public Something1_object()
    {
    }
    
    public void doSomeThing1()
    {
        System.Console.WriteLine("DoOne");
    }
}

public class Something2_object : Something2
{
    public Something2_object()
    {
    }
    
    public void doSomeThing2()
    {
        System.Console.WriteLine("DoTwo");
    }
}

public class Something3_object : Something3
{
    public Something3_object()
    {
    }
    
    public void doSomeThing3()
    {
        System.Console.WriteLine("DoThree");
    }
}

public class Something4_object : Something4
{
    public Something4_object()
    {
    }
    
    public void doSomeThing4()
    {
        System.Console.WriteLine("DoFour");
    }
}

모든 인터페이스를 통합 실행하는 클래스 코드

public class AllInOne
{
    Something1 isomething1;
    Something2 isomething2;
    Something3 isomething3;
    Something4 isomething4;

    public AllInOne(
    Something1 isomething1,
    Something2 isomething2,
    Something3 isomething3,
    Something4 isomething4)
    {
        this.isomething1 = isomething1;
        this.isomething2 = isomething2;
        this.isomething3 = isomething3;
        this.isomething4 = isomething4;
    }

    public void doAll()
    {
        this.isomething1.doSomeThing1();
        this.isomething2.doSomeThing2();
        this.isomething3.doSomeThing3();
        this.isomething4.doSomeThing4();
    }
}

메인코드

Something1_object something1 = new Something1_object();
Something2_object something2 = new Something2_object();
Something3_object something3 = new Something3_object();
Something4_object something4 = new Something4_object();
AllInOne allInOne = new AllInOne(something1, something2, something3, something4);
allInOne.doAll();

코드 설명

어댑터 패턴

서로 다른 인터페이스 코드 : 기존 110 volt 인터페이스와 새롭게 교체할 220 volt의 인터페이스이다.

기존의 VoltOf110 인터페이스로 구현된 코드 : 기존 110 volt의 인터페이스를 상속받아 구현한 클래스이다.

110 Volt To 220 Volt 클래스 : 기존의 110 volt 구현체를 받아서 220 volt의 구현 함수로 매칭시켜준다.

메인 코드 : 어뎁터 패턴의 사용 방법을 보여준 코드이다.

퍼사드 패턴

다양한 인터페이스 코드 : 서로 다른 인터페이스를 보여준다.

다양한 인터페이스 구현 코드 : 서로 다른 인터페이스들의 구현체들을 보여준다.

모든 인터페이스를 통합 실행하는 클래스 코드 : 서로 다른 인터페이스를 모두 생성자에서 등록한 후 하나의 함수로 실행시켜 준다.

메인 코드 : 퍼사드 패턴의 사용 방법을 보여준 코드이다.

설명 보충을 위한 그림 자료

어댑터 패턴

110 volt 어뎁터, 110 volt To 220 volt 변환 어뎁터, 220 volt 콘센트이다.

어뎁터 패턴은 이처럼 기존에 맞기 않는 인터페이스 규격을 맞춰주는 패턴이다. 

퍼사드 패턴

퍼사드 패턴은 복잡하게 산재되어 있는 인터페이스들을 취합하여 실행하고 관리를 도와주는 패턴이다. 

어뎁터 패턴과 달리 하나의 인터페이스를 다룬다기보다는 여러 인터페이스를 받아서 좀 더 고도화시키는 패턴이다. 매크로 기능이랑 비슷하다.

필요에 따라 추가 설명

x

정리 및 결론

어댑터 패턴은 기존에 규격에 맞지 않는 경우 사용하는 패턴이다. 내 개인적인 생각으로 이 패턴은 매우 많이 쓰면 관리가 힘들 것 같다. 어댑터 패턴은 단방향?으로 구조여서 변환해야 할 클래스가 늘어날수록 코드가 많아질 것이다. 따라서 어댑터 패턴을 적용하기 전에 한 번쯤 몇 개의 클래스를 변환시켜야 하는지 판단해야 할 것 같다.

퍼사드 패턴은 여러 개로 산재되어 있는 인터페이스 구현체들을 가져와서 하나로 통합하는 패턴이다. 매크로 기능?? 을 수행할 때 필요할 것 같다. 하지만 굳이..? 커맨드 패턴에서 이미 매크로 같은 기능을 구현했는데 이 부분은 잘 모르겠다. 지금 알 수 있는 다른 점은 커맨드 패턴은 하나의 커맨드 인터페이스로 구현된 다양한 구현체를 다룰 때 사용했고, 퍼사드 패턴은 여러개의 인터페이스를 다룰때 사용했다.

'Program > SW Design Patterns' 카테고리의 다른 글

Mediator 패턴 (C++)  (0) 2024.07.31
CH8 스테이트 패턴  (0) 2023.12.04
CH6 커맨드 패턴  (0) 2023.03.07
CH5 데코레이터 패턴  (1) 2023.03.05
CH4 팩토리 패턴  (0) 2023.03.04

 

 

커맨드 패턴 정의

요청 사항을 객체로 캡슐화하여 요청 사항 인스턴스를 체계적으로 관리하는 패턴이다. 체계적인 관리를 통해 요청 사항 실행뿐만 아니라 로그 기록, Undo 기능 등을 관리할 수 있다.

필요한 상황

1. Undo 기능, Log 기록, Macro 기능을 구현하고 관리하고 싶을때

2. 여러 요청 사항을 하나의 인스턴스에서 관리하고 싶을 때

3. 고객사의 요청 사항이 지속적으로 늘어나는 프로그램인 경우

Class 다이어그램

코드

Icommand 인터페이스

public interface Icommand
{
    void excute();
    void undo();
}

요청 사항 객체들

public class NullCommand : Icommand
{
    public void excute()
    {
        Console.WriteLine("It's a null slot");
    }
    
    public void undo()
    {
        Console.WriteLine("It's a null slot");
    }
}

public class ExampleOneClass : Icommand
{
    public void excute()
    {
        Console.WriteLine("It's ExampleOneClass excute method");
    }
    
    public void undo()
    {
        Console.WriteLine("It's ExampleOneClass undo method");
    }
}

public class ExampleTwoClass : Icommand
{
    public void excute()
    {
        Console.WriteLine("It's ExampleTwoClass excute method");
    }
    
    public void undo()
    {
        Console.WriteLine("It's ExampleTwoClass undo method");
    }
}

public class MacroClass : Icommand
{
    List<Icommand> icommands; 
    public MacroClass(List<Icommand> icommands)
    {
        this.icommands = icommands;
    }
    
    public void excute()
    {
        this.icommands.ForEach(x => x.excute());
    }
    
    public void undo()
    {
        this.icommands.ForEach(x => x.undo());
    }
}

중앙 관리 클래스

public class Invoker
{
	List<Icommand> excuters;
	int preSlot;
	public Invoker(int slotSize)
	{
		excuters = new List<Icommand>();
		
		for (int i = 0; i < slotSize; ++i)
		{
			excuters.Add(new NullCommand());
		}
	}
	
	public void setCommand(int slot, Icommand command)
	{
		excuters[slot] = command;
	}
	
	public void undo()
	{
		excuters[preSlot].undo();
	}
	
	public void excute(int slot)
	{
		excuters[slot].excute();
		this.preSlot = slot;
	}
}

main 코드

Invoker invoker = new Invoker(3);
invoker.setCommand(0, new ExampleOneClass());
invoker.setCommand(1, new ExampleTwoClass());
invoker.excute(0);
invoker.excute(1);
invoker.undo();
MacroClass macroClass = new MacroClass(new List<Icommand>() { new ExampleOneClass(), new ExampleTwoClass(), new ExampleOneClass() }); 
invoker.setCommand(2, macroClass);
invoker.excute(2);
invoker.undo();

코드 설명

1. Icommand interface를 통해 중앙 관리 클래스 (Invoker class)는 다른 요청 사항 클래스들을 관리한다. 중요한 건 중앙 관리 클래스는 요청 사항 클래스들의 Icommand interface 에 의해 이용할 수 있는 함수만 알고 다른 것은 관여하지 않는다.

2. 요청 사항 클래스들은 Icommand interface 를 상속받기 때문에 Icommand interface의 규격을 맞춰서 코딩한다. 이때 Log 기능, Undo 기능, Excute 기능들을 구현해야 한다.

3. 중앙 관리 클래스는 Icommand interface를 리스트로 저장하고 있다가 실행이 필요할 때 리스트 안에 저장되어 있는 인스턴스를 실행시키면 된다. 중요 한건 요청 사항 클래스들의 내부 변수, 특징을 알 필요 없다는 것이다.

4. main 코드를 통해 해당 패턴을 어떻게 사용하는지 확인할 수 있다.

설명 보충을 위한 그림 자료

중앙 관리 클래스에게 3번째 인덱스의 매크로 요청 사항 클래스를 실행하는 경우

필요에 따라 추가 설명

x

정리 및 결론

1. 커맨드 패턴도 자주 사용하는 패턴이다. 대표적인 사용 사례로 WPF의 ICommand이다. 이를 통해 View와 이벤트 함수를 바인딩한다.

2. 다양한 요청 사항에 대해 Undo, Log, Macro 기능이 필요한 경우 해당 패턴을 사용하면 좋다.

3. 커맨드 패턴은 중앙 관리 클래스 <-> 요청 사항 인터페이스 <-> 요청 사항 클래스로 구분하여 구성과 상속을 적절히 사용한 패턴이다. 

 

'Program > SW Design Patterns' 카테고리의 다른 글

CH8 스테이트 패턴  (0) 2023.12.04
CH7 어댑터&퍼사드 패턴  (0) 2023.03.19
CH5 데코레이터 패턴  (1) 2023.03.05
CH4 팩토리 패턴  (0) 2023.03.04
CH3 옵저버 패턴  (0) 2023.03.03

데코레이터 패턴 정의

파생클래스의 생성자 파라미터에 덮어씌우고자 하는 인스턴스를 매개변수로 넣음으로써 재귀적으로 현재 인스턴스의 상태를 추가하는 방법이다.  

필요한 상황

1. Open-Closed-Principle(OCP) 원칙을 지키면서 인스턴스의 현재 상태를 추가하고 싶을 때

2. 인스턴스의 상태를 자유롭게 누적시키는 로직이 필요할 때

Class 다이어그램

코드

1. Pizza class 코드

public abstract class Pizza
{
    protected string? strDescription;
    
    public abstract String getDescription();
    
    public abstract double cost();
}

2. Topping class 코드

public abstract class Topping : Pizza
{
    public override String getDescription() => this.strDescription;
}

3. Pizza class의 파생 코드

public class CheesePizza : Pizza
{
    public CheesePizza()
    {
        base.strDescription = "CheesePizza";
    }
    
    public override double cost()
    {
        return 2.33;
    }
    
    public override string getDescription()
    {
        return base.strDescription;
    }
}

public class MeatLoverPizza : Pizza
{
    public MeatLoverPizza()
    {
        base.strDescription = "MeatLoverPizza";
    }
    
    public override double cost()
    {
        return 3.5;
    }
    
    public override string getDescription()
    {
        return base.strDescription;
    }
}

4. Topping class의 파생 코드

public class CheeseTopping : Topping
{
	Pizza pizza;
	public CheeseTopping(Pizza pizza)
	{
		this.pizza = pizza;
	}
	
	public override double cost() => (pizza.cost() + 0.5);
	public override String getDescription() => (pizza.getDescription() + " CheeseTopping");
}

public class TomatoTopping : Topping
{
	Pizza pizza;
	public TomatoTopping(Pizza pizza)
	{
		this.pizza = pizza;
	}
	
	public override double cost() => this.pizza.cost() + 0.2;
	
	public override String getDescription() => this.pizza.getDescription() + " Tomato Topping";

}

5. main 코드

Pizza pizza = new CheesePizza();
Console.WriteLine($"{pizza.getDescription()} : {pizza.cost()}");
pizza = new CheeseTopping(pizza);
Console.WriteLine($"{pizza.getDescription()} : {pizza.cost()}");
pizza = new CheeseTopping(pizza);
Console.WriteLine($"{pizza.getDescription()} : {pizza.cost()}");
pizza = new TomatoTopping(pizza);
Console.WriteLine($"{pizza.getDescription()} : {pizza.cost()}");

코드 설명

1. Pizza class는 파생 클래스 모두 공통으로 사용할 클래스이다.

2. Topping class 는 Pizza class의 파생 클래스이다. Topping class는 계속 이전 인스턴스를 인자로 받아서 추가로 데이터를 추가한다.

3. CheesePizza, MeatLoverPizza class들은 Pizza class의 파생 클래스로써 기본 Pizza의 대분류로 나눈 것이다. 그 이상 그 이하도 없다.

4. Cheese Topping, Tomato Topping class들은 Topping class의 파생 클로스로써 중요한 점은 Pizza class의 인스턴스를 생성자에서 받아서 이전 상태의 인스턴스를 저장한다. 저장한 인스턴스는 다음 함수 호출 시 사용한다. 이는 결국 재귀적으로 내부 Pizza class의 인스턴스를 재귀적으로 호출하는 역할을 수행하게 된다.

5. 사용하는 코드이다.

설명 보충을 위한 그림 자료

클래스 다이어그램 하단에 별도 기입..

필요에 따라 추가 설명

딱히 어려운 건 없다.

정리 및 결론

데코레이터 패턴도 다양한 언어, API에서 제공하는 패턴 중 하나이다. 하지만 재귀함수적 특징으로 인해 코드의 가독성이 떨어지는 단점이 있다. 사용할지 말지는 본인 판단!!

'Program > SW Design Patterns' 카테고리의 다른 글

CH7 어댑터&퍼사드 패턴  (0) 2023.03.19
CH6 커맨드 패턴  (0) 2023.03.07
CH4 팩토리 패턴  (0) 2023.03.04
CH3 옵저버 패턴  (0) 2023.03.03
CH2 싱글턴 패턴  (0) 2023.03.02

+ Recent posts