레이블이 design pattern인 게시물을 표시합니다. 모든 게시물 표시
레이블이 design pattern인 게시물을 표시합니다. 모든 게시물 표시

2019년 9월 15일 일요일

SOLID 원칙 - SRP, OCP, LSP

개발에 있어서 구조(Architecture)는 매우 중요하다.
수정에 있어서 용이하게 구현을 해놓는것이 곧 생산성과 직결되며 유능한 개발자로 보여질 수 있다.

Robert C. Martin은 5가지 원칙(S.O.L.I.D)을 제시하였다. 
해당 원칙은 디자인 패턴에 녹아있으며 
가독성과 유지보수가 좋은 프로그램을 개발할 수 있게 도와준다.

1. Single Responsibility Principle (단일 책임 원칙)
- 각 클래스는 단 한가지의 책임을 부여받아 수정할 이유가 한가지여야 한다.

제목 하나에 내용이 들어있는 메모장과 메모장의 내용을 파일에 저장하는 기능을 개발한다고 하자.
class Journal {
public :
    explicit Journal(const string &title) :title(title){}
    ~Journal() {}
 
    string title;
    vector<string> entries;
 
    void add(const string& entry) {
        entries.push_back(entry);
    }
    void save(const string& filename) {
        ofstream ofs(filename);
        for (auto& s : entries)
            ofs << s << endl;
    }
};
cs

위와 같이 add,save함수를 이용하여 구현할 수 있다.
하지만 Journal class의 목적은 메모 항목들을 기입/관리하는 것이지 디스크에 쓰는것은 아니다.
앞선 방법으로 구현하게 되면 차후 수정사항에 대해 고통스러운 상황에 처할 수 있다.

따라서 Journal class의 저장 기능을 새로운 class에서 수행하는것이 옳다.
class Journal {
public :
    explicit Journal(const string &title) :title(title){}
    ~Journal() {}
 
    string title;
    vector<string> entries;
 
    void add(const string& entry) {
        entries.push_back(entry);
    }
};
 
class PersistenceManager {
public:
    static void save(const Journal &journal, const string& filename) {
        ofstream ofs(filename, std::ofstream::out);
        for (auto& s : journal.entries)
            ofs << s << endl;
    }
};
int main() {
    Journal memo("design pattern");
    memo.add("builder pattern");
    memo.add("factory pattern");
 
    PersistenceManager::save(memo, "Design Pattern.txt");
    return 0;
}
cs
 
2. Open-Closed Principle (열림-닫힘 원칙)
- 확장에는 열려있지만 수정에는 닫혀있어야 한다.

단순히 파일에만 저장하는 방식이 아닌 서버에 저장하거나 다른 종류의 저장공간에 저장하는것을 구현한다고 하자.
class PersistenceManager {
public:
    static void save_file(const Journal &journal, const string& filename) {
        ofstream ofs(filename, std::ofstream::out);
        for (auto& s : journal.entries)
            ofs << s << endl;
    }
    static void save_server(const Journal& journal, const Server& server) {
        // ...
    }
    static void save_blabla(const Journal& journal, ...) {
        // ...
    }
};
cs

위의 구현은 OCP를 만족하지 않고있다.
새로운 저장방식이 추가됨에 따라 해당 클래스를 수정해야하기 때문이다.
class SavingMethod {
public:
    virtual void saving(const Journal &journal, const string &filename) = 0;
};
class FileSave : public SavingMethod{
public :
    void saving(const Journal& journal, const string& filename) override{
        ofstream ofs(filename, std::ofstream::out);
        for (auto& s : journal.entries)
            ofs << s << endl;
    }
};
class ServerSave :public SavingMethod {
public :
    void saving(const Journal& journal, const string& filename) override {
        // ...
    }
};
class PersistenceManager {
public:
    static void save(const Journal &journal, const string& filename, SavingMethod &saving_method) {
        saving_method.saving(journal, filename);
    }
};
int main() {
    Journal memo("design pattern");
    memo.add("builder pattern");
    memo.add("factory pattern");
 
    SavingMethod *file_save = new FileSave;
    SavingMethod *server_save = new ServerSave;
 
    PersistenceManager::save(memo, "Design Pattern.txt"*file_save);
    PersistenceManager::save(memo, "Design Pattern.jar"*server_save);
 
    delete file_save;
    delete server_save;
    return 0;
}
cs
SavingMethod class를 이용해서 기존의 PersistenceManager의 save 기능은 유지하되(수정)
추가 저장 기능이 요구될때(확장) 인터페이스를 건드리지 않는 방식을 제시할 수 있다.


3. Liskov Substitution Principle (리스코프 치환 원칙)
- 객체는 프로그램의 정확성을 깨지 않으면서 하위 타입의 인터페이스 객체로 교체될 수 있어야한다.

상당히 많은 글에서 Rectangle과 Square를 예시로 리스코프 치환 원칙을 설명한다.

class Rectangle {
public:
Rectangle() = default;
void set_width(int width) {
this->width_ = width;
}
void set_height(int height) {
this->height_ = height;
}
protected:
int width_, height_;
};
class Square : public Rectangle {
public:
Square() = default;
void set_width(int width) {
this->width_ = width;
this->height_ = width;
}
void set_height(int height) {
this->height_ = height;
this->width_ = height;
}
};
// Client method
void SetRectangle(Rectangle *obj, int width, int height) {
obj->set_width(width);
obj->set_height(height);
}

위와 같이 Rectangle의 하위 클래스를 Square로 상속받게하면 width와 height을 저장할 때 문제가 발생한다.
다른 Client에서 Rectangle의 원소값을 설정할 때 Square의 너비와 높이가 다르게 설정이 될 수 있다.




2019년 2월 6일 수요일

C++ 디자인패턴 (Singleton pattern)

Singleton Pattern
생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 유일하게끔 사용하는 디자인 패턴

#include <iostream>
#include <string>
using namespace std;
class Singleton {
public:
    static Singleton* New(string name) {
        static Singleton *instance = nullptr;
        if (instance == nullptr) instance = new Singleton(name);
        return instance;
    }
    void print() {
        cout << "my name is " << _name << "\n";
    }
 
private:
    string _name = "";
    Singleton() {}
    Singleton(string name) :_name(name) {}
};
 
int main() {
    Singleton *instance = Singleton::New("jihoon");
    Singleton *instance2 = Singleton::New("taehoon");
    
    instance->print();
    instance2->print();
    return 0;
}
cs

2018년 11월 9일 금요일

C++ 디자인패턴 (Composite Pattern)

Composite Pattern
클라이언트에서 단일 객체와 복합 객체 모두 동일하게 다루기 위해 사용하는 디자인 패턴
#include <bits/stdc++.h>
using namespace std;
class Component {
public:
    Component() {}
    virtual ~Component() {}
    virtual void print(int= 0;
};
class Directory : public Component{
public:
    ~Directory() {}
    Directory() {}
    Directory(string id) :m_id(id) {}
    void add(Component *input) {
        m_comp.push_back(input);
    }
    void print(int tap = 0) {
        for (int i = 0; i < tap; i++printf("\t");    // tap
        printf("directory : %s\n", m_id.c_str());
        
        for (auto dir : m_comp) {
            dir->print(tap + 1);
        }
    }
 
private:
    vector<Component*> m_comp;
    string m_id;
};
class File :public Component {
public:
    ~File() {}
    File() {}
    File(string id) :m_id(id) {}
    void print(int tap = 0) {
        for (int i = 0; i < tap; i++printf("\t");    // tap
        printf("file : %s\n", m_id.c_str());
    }
private:
    string m_id;
};
int main() {
    Directory *dir_engineering = new Directory("Engineering");
    Directory *dir_computer_science = new Directory("Computer Science");
    Directory *dir_algorithm = new Directory("Algorithm");
    Directory *dir_AI = new Directory("AI");
 
    dir_algorithm->add(new File("Binary Search"));
    dir_algorithm->add(new File("BFS"));
    dir_algorithm->add(new File("Dinic Algorithm"));
    dir_computer_science->add(dir_algorithm);
 
    dir_AI->add(new File("Reinforcement Learning"));
    dir_AI->add(new File("CNN"));
    dir_computer_science->add(dir_AI);
 
    dir_engineering->add(dir_computer_science);
    dir_engineering->print();
    return 0;
}
cs

[출력값]

composite 패턴은 파일 시스템을 예시로 들면 이해하기 쉽다.
directory는 file을 담는것 뿐만 아니라 directory 또한 담을 수 있는데 다형성을 이용해 관리해준다.

2018년 9월 16일 일요일

C++ 디자인패턴 (Command Pattern)

Command Pattern

요구사항, 요청사항을 객체로 캡슐화하여 다양한 종류를 넣을 수 있다. 
또한 작업취소(undo)기능도 가능하다.

클래스 다이어그램과 코드는 wiki에서 가져왔다.

#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Command {
public:
    virtual void execute(void= 0;
    virtual ~Command(void) {};
};
class Ingredient : public Command {
public:
    Ingredient(string amount, string ingredient) {
        _ingredient = ingredient;
        _amount = amount;
    }
    void execute(void) {
        cout << " *Add " << _amount << " of " << _ingredient << endl;
    }
private:
    string _ingredient;
    string _amount;
};
class Step : public Command {
public:
    Step(string action, string time) {
        _action = action;
        _time = time;
    }
    void execute(void) {
        cout << " *" << _action << " for " << _time << endl;
    }
private:
    string _time;
    string _action;
};
class CmdStack {
public:
    void add(Command *c) {
        commands.push_back(c);
    }
    void createRecipe(void) {
        for (vector<Command*>::size_type x = 0; x<commands.size(); x++) {
            commands[x]->execute();
        }
    }
    void undo(void) {
        if (commands.size() >= 1) {
            commands.pop_back();
        }
        else {
            cout << "Can't undo" << endl;
        }
    }
private:
    vector<Command*> commands;
};
int main(void) {
    CmdStack list;
    //Create ingredients
    Ingredient first("2 tablespoons""vegetable oil");
    Ingredient second("3 cups""rice");
    Ingredient third("1 bottle""Ketchup");
    Ingredient fourth("4 ounces""peas");
    Ingredient fifth("1 teaspoon""soy sauce");
    //Create Step
    Step step("Stir-fry""3-4 minutes");
    //Create Recipe
    cout << "Recipe for simple Fried Rice" << endl;
    list.add(&first);
    list.add(&second);
    list.add(&step);
    list.add(&third);
    list.undo();
    list.add(&fourth);
    list.add(&fifth);
    list.createRecipe();
    cout << "Enjoy!" << endl;
    return 0;
}
cs

2018년 9월 4일 화요일

C++ 디자인패턴 (Strategy Pattern)

Strategy Pattern
클라이언트 객체에서 서로 교환가능한 구현(전략)들을 의존성 없이 변경 가능하도록 하는 패턴


#include <iostream>
using namespace std;
class Strategy {
    public:
        Strategy() {}
        ~Strategy() {}
        virtual void execute() = 0;
};
class A_strategy : public Strategy {
    protected:
        virtual void execute() {
            printf("A strategy!\n");
        }
};
class B_strategy : public Strategy {
    protected:
        virtual void execute() {
            printf("B strategy!\n");
        }
};
class Character {
    public:
        Character() {}
        ~Character() {}
        void ChangeStrategy(Strategy *s) {
            if (this->stg != nullptr) {
                delete this->stg;
            }
            this->stg = s;
        }
        void Perform() {
            if (this->stg == nullptr) {
                printf("no strategy!\n");
                return;
            }
            this->stg->execute();
        }
    private:
        Strategy *stg;
};
int main() {
    Character *ch = new Character();
    ch->Perform();

    ch->ChangeStrategy(new A_strategy());
    ch->Perform();
    ch->ChangeStrategy(new B_strategy());
    ch->Perform();
    return 0;
}
cs

ch객체에서 A라는 전략을 수행한 후 전략을 바꾸어 B라는 전략을 수행하고있다.

(18.11.26) - python 버전 추가

class Strategy:
    _name = None
    def action(self):
        pass
 
    """ sub class """
class FirstStrategy(Strategy):
    def __init__(self, name):
        self._name = name
    def action(self):
        print(self._name,' First algorithm start!')
        
    """ sub class """
class SecondStrategy(Strategy):
    def __init__(self, name):
        self._name = name
    def action(self):
        print(self._name,' Second algorithm start!')
 
class Unit:
    _strategy = Strategy()
    _name = None
    def __init__(self, name):
        self._name = name
 
    def ChangeStrategy(self, strg):
        self._strategy = strg
 
    def Show(self):
        print('my name is ',self._name)
        self._strategy.action()
        print()
 
jihoon = Unit('JiHoon')
 
jihoon.ChangeStrategy(FirstStrategy('fly'))
jihoon.Show()
 
jihoon.ChangeStrategy(SecondStrategy('kill'))
jihoon.Show()
cs






파이썬 버전도 기본 logic은 동일하다.