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년 7월 17일 수요일

다각형 내부 점 판단 알고리즘 (Point in polygon algorithm)

<알고리즘에 대한 정보는  http://geomalgorithms.com/a03-_inclusion.html를 많이 참고하였다.>

P가 주어질 때 다각형 내부 또는 외부에 속해있는 지 판별하는 알고리즘이다.


1) Crossing number algorithm
2) Winding number algorithm
크게 위의 두 가지 알고리즘으로 구현할 수 있다.




<Crossing number algorithm>
Crossing number algorithm은 점 P에서 반직선을 그어 다각형의 변과 접하는 횟수를 계산해
짝수면 외부, 홀수면 내부로 판별하는 알고리즘이다.

다각형의 경계선(boundary)은 다각형을 내부에서 외부로 분리해주는 역할이기 때문에 
이 알고리즘은 보다 직관적으로 이해할 수 있다.

홀짝만 판단하기 때문에 구현또한 쉬울것 같지만 여러 예외상황이 존재한다.

1) 점P의 반직선이 다각형의 한점에서 접점하는 경우


위의 경우가 이에 해당된다.
CN 알고리즘은 다각형의 경계선과의 접점횟수로 결과를 내기 때문에 
위와같이 다각형의 한 점에서 접점하는 경우 판단하기 힘들다.


2) non-simple 다각형내에 점P가 존재하는 경우


이와 같은 경우도 엄밀히 말하면 점P는 다각형 내부에 포함되어 있지만 외부로 판단된다.


3) 점P의 반직선과 다각형의 경계선이 수평으로 접하는 경우


이와 같은 경우도 수평으로 접하는것을 한번의 횟수로 단정짓기 어렵다.


이러한 예외경우를 없애기 위해 Edge Crossing Rule이라는 규칙을 정의한다.


- upward edge 시작점은 포함, 끝점은 배제
- downward edge 시작점은 배제, 끝점은 포함
- 반직선과 수평선은 배제
- edge-ray 교차점은 점 P의 오른쪽에 위치한 점으로만 정의됨

위의 규칙에 따르면 simple polygon에 대해서는 point in polygon 문제를 해결할 수 있다.




[출처]
http://geomalgorithms.com/a03-_inclusion.html

2019년 5월 13일 월요일

C++ Reference

C++ reference에 대해서 잘못 알고있는 부분과 자세히 몰랐던 부분이 있어서
이 부분에 대해 공부한 후 포스팅을 해보려고 한다.





- Reference

C++에서는 Reference(&)라는 변수타입이 제공된다.
간단히 말해서 별명을 붙혀주는 기능이다.


int &ret = dp[curr];
cs
알고리즘 포스팅중 dp - top down으로 구현한 코드에 대부분 등장하는 코드이다.


ret = 30;
printf("dp[curr] : %d\n",dp[curr]);         // 30
cs
dp[curr]ret이라는 별명을 붙혀주었고 이 값을 30으로 변경했기 때문에  30으로 출력이 된다.





- Reference type

이러한 Reference는 세 가지 종류의 참조형을 제공한다.

  •  non-const value
  •  const value
  •  r-value (using &&)

int a = 10;
int& ref1 = a;            // okay [non-const l-value]
const int b = 20;
int& ref2_1 = b;        // not okay
const int& ref2_2 = b;    // okay [const l-value]
int&& ref3 = 30;        // okay [r-value]
cs

l-value, r-value에 대해서는 다음 포스팅에서 다뤄보도록 하겠다.





- Pass by reference

reference를 함수 인자부분에 사용하는 것을 말한다.
인자 변수가 복사되어 생성되지 않기 때문에 Pass by value에 비해 속도가 빠르다.




int Square(const int& input) {
    int ret = input * input;
    return ret;
}
cs
const 키워드를 붙여서 함수 내부에 변경이 없도록 사용하는 경우가 많다.





- Return by Reference

이 부분은 조심해서 사용해야 한다.
reference를 함수 반환값으로 사용하는 것을 말한다.
이 기능을 수행할 때는 reference의 원본이 메모리 상에 유지되어야 한다. 
(당연한 말이지만 무심코 이 원칙을 지키지 않을 수 있다.)


#include <bits/stdc++.h>
using namespace std;
int& function() {
    int val = 10
    return val;
}
int& function2() {
    int val2 = 20;
    return val2;
}
int main(){
    int& ref = function(); 
    int& ref2 = function2();
    cout << ref << endl;        // output : 20
}
cs
위의 예시에서는 10이 출력될것 처럼 보이지만 20이 출력이된다.



처음 function()이 호출 되면 다음과 같은 주소에 값이 들어갈 것이다.



 그 다음 function() 함수가 종료되면 val변수는 사라졌지만 여전히 같은 주소를 지니게 된다.



 function2() 함수가 호출되면 ref, ref2변수 모두 val2의 주소값을 가지게된다.



결국 마지막에는 이런 결과를 얻게된다.



위의 과정은 심각한 오류를 초래할 수 있으므로 사용하지 말도록 하자.

아니면 static 변수로 선언하여 이 문제를 해결할 수 있다.

static 변수는 global 변수와 같이 data영역에 할당이 되고 
함수, 함수의 지역변수는 heap영역에 메모리 할당과 해제가 이루어 진다.



static 변수는 선언과 동시에 data영역에 존재해 함수 호출 후에도 남아있게 되기때문에 
위의 문제를 해결할 수 있다.


#include <bits/stdc++.h>
using namespace std;
int& function() {
    static int val = 10
    return val;
}
int& function2() {
    static int val2 = 20;
    return val2;
}
int main(){
    int& ref = function(); 
    int& ref2 = function2();
    cout << ref << endl;        // output : 10
}
cs




[출처]
https://boycoding.tistory.com/207 /
https://snbosoft.tistory.com/entry/%EC%A0%9C2%EC%9E%A5-C-%EA%B8%B0%EB%B3%B8-Reference%EB%A5%BC-return%ED%95%98%EB%8A%94-%ED%95%A8%EC%88%98%EC%9D%98-%EC%A0%95%EC%9D%98 /
https://www.tutorialspoint.com/cplusplus/returning_values_by_reference.htm /

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