Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C++ & C#] 2일차 프로그래밍 #26

Open
ggjae opened this issue Nov 7, 2021 · 4 comments
Open

[C++ & C#] 2일차 프로그래밍 #26

ggjae opened this issue Nov 7, 2021 · 4 comments

Comments

@ggjae
Copy link
Owner

ggjae commented Nov 7, 2021

[C++ & C#] 2일차 프로그래밍

@ggjae
Copy link
Owner Author

ggjae commented Nov 8, 2021

개인 공부를 위함

자주 사용되는 코딩 패턴에 이름을 부여한 것을 디자인 패턴이라 한다. for문이 두개 겹치는 걸 이중포문이라고 하고, 우리는 이중 루프라고 부른다. 이것은 이름이 있기 때문에 개발자 간 편리하고 명확한 의사 소통이 가능하다.

용어 공부, template method가 어떤 코드인지 코드 배우기, 장점, 단점, 어떤 때 활용하는 것인지 관점 배우기

Gangs of Four = 디자인 패턴 책의 저자 네명을 갱이라고 부르며, GoF's 디자인 패턴이 됨

기존에 존재하는 코딩 방식에 이름을 부여한 것임.
총 23개의 디자인 패턴을 설명하고 있다.

  1. 객체 지향 디자인의 핵심 원리
  2. GoF 디자인패턴 책에 소개되는 주요 패턴
  3. C++ 진영의 오픈소스가 많이 사용하는 디자인 기법

[Protected]

생성자를 protected로 만드는 이유 ?
자기 자신은 객체를 만들 수 없지만, 내 파생 클래스들은 객체를 만들 수 있게 하겠다.
animal은 추상적이기때문에 동물이란 객체는 없잖아. dog는 분명 현실세계에도 존재하므로 protected생성자는 추상적인 개념들을 모델링할 때 사용합니다. 생성자 호출 순서는 Dog가 먼저 호출되는것이고, 이 생성자 안에서 기반 클래스의 animal의 생성자호출이 일어난다.

소멸자를 protected로 만드는 이유 ?
main에서 객체를 하나 만들면, 파괴될 때 소멸자를 부르지 못하면서 에러가 남..
Stack에는 안되지만, 대신 Heap에는 만들 수 있어요.
Car* p = new Car;

Protected 소멸자를 만들었을때는 객체를 파괴할 때 직접 딜리트하기보다는, 멤버함수를 통해서 delete하라라는 의미가 담겨 있겠죠. => 아주 많이 사용되진 않지만, 의미가 있다

'객체를 스택에 만들 수 없게 할 때 주로 사용하는 기법', ' 참조 계수 기반의 객체 수명관리기법에서 주로 사용됨'

[Upcasting]

C++은 포인터에 대한 타입이 엄격한데, 상속은 동족끼리 묶을때도 많이 쓰고
기반클래스 떠올리면 됨. 점점 기반클래스가 늘어나면서 Composite 패턴이 된다.
다양한것들을 하나로 묶어서 관리하고 싶으면, 계속 기반클래스를 설계하는것.

업캐스팅이란, 자식이 부모를 대체할 수 있었지. 자식으로 생성한 객체를 부모에다가 연결하는 것을 업캐스팅이라고 함.
이 반대가 다운캐스팅이고, 다운캐스팅은 타입을 명시적으로 지정해주어야 한다는 특징이 있다.

기반클래스포인터로 파생클래스를 가리킬 때, 가상함수로 만들어야 재정의된 함수가 호출된다.
가상함수 재정의할땐 "override"를 사용하는것이 좋은 스타일이다.

[순수가상함수와 추상클래스]

디자인패턴에선 중요한 문법 중 하나다. C++에서는 가상함수를 만들 때 뒤에다 구현부를 만들지 않고,
virtual void Draw() = 0;과 같은 순수가상함수를 만들 수 있고, 추상 클래스는 순수가상함수를 한 개 이상 가지고 있는 클래스를 추상 클래스라고 한다.

추상클래스는 객체를 만들 수 없지만, 포인터변수는 만들 수 있다. => 추상클래스의 특징

추상클래스에서 상속받은 순수가상함수를 구현하면, 추상이 아니다. 구현이 없으면 error인데 구현이 있으면 error가 나지 않고 정상적으로 작동합니다. 추상클래스는 만드는 의도가 있어야 함. 구현부를 물려주겠다는것이 아닌 모든 도형을 만드는 사람들은 그 클래스를 사용할 수 있게 하려면 Draw를 만들어야 한다. 라고 함수에서 약속을 하고 만들도록 시키는 것

'추상 클래스의 의도' => 파생 클래스에게 특정 함수를 반드시 구현하라고 지시하는 것

[interface & coupling]

추상클래스의 활용으로, 인터페이스와 커플링을 알아보자.
나중에 추가한 코드에 따라서 기존에 프로그램에 코드 추가가 필요하다면, 그게 좋은 코드일것인가?
HDCam이라는 새로운 코드가 들어오고, 기존 코드가 수정이 되고 있는데 이것은 좋지 않다.
객체지향 디자인의 아주 유명한 다섯가지 원칙 SOLID 중 하나 OCP. Open Close Principle 개방폐쇄의 법칙
기능 확장엔 Open, 코드 수정은 Close한다는 Principle. 기존 코드에는 수정이 없도록 해야한다.
People과 Camera가 강하게 결합되어 있다.
=> Tightly coupling. 서로에 대해서 잘 알고 있는 것(교체, 확장 불가능한 경직된 디자인)

제품을 먼저 만들지 말고, **제품의 인터페이스(규칙, 계약)**을 먼저 설계한다.
class ICamera를 만들어서, 카메라라는 규칙을 만든다.
카메라가 없어도 카메라를 사용하는 코드를 만들 수 있다.

People이 카메라를 사용하면서 take함수에 ICamera에 해당하는 인터페이스를 매개변수로 받음으로써, 새로운 카메라가 등장해도 카메라인터페이스를 따른다면, 코드의 추가가 더 필요 없게 되었죠.
=> 약한 결합 Loosely Coupling, 느슨한 결합 ( 객체가 서로 상호작용하지만, 서로에 대해 잘 알지 못하는 것 )
객체는 상호 간 인터페이스를 통해서 통신해야한다. Client는 구현에 의존하지 말고 인터페이스에 의존한다.
인터페이스를 struct로 구현하시는 분들도 많다.

모든 카메라는 아래 인터페이스를 구현해야 한다. = 아래 클래스로부터 파생되어야 한다.
가상 소멸자를 사용해야하는것도 필요할것이고, ocp를 만족하게 되었다.

[예제로 배우는 객체지향 프로그래밍]

모든 도형을 타입으로 설계하기. Rect, Circle 클래스 만들기
따로따로 보관하게 되면, 겹쳐서 그리면 누가 먼저 그린것인지 모르잖아. 사각형과 원을 한꺼번에 관리하려면, 새 기반 클래스를 만드는것이 맞지 않을까? 모든 도형의 공통 기반클래스가 있다면, 하나의 컨테이너에 담아 관리할 수 있다.
vector에서 기반클래스포인터형으로 만들고, 사각형이나 원 둘다 보관이 가능하므로 이점이 있다.
모든 도형의 공통의 특징은, 기반 클래스에도 무조건 있어야 한다.
virtual을 적지 않거나 override를 언급하지 않으면, 개발자가 봤을 때 코드를 이해하기 더 힘들어지기 때문에 작성해주는 것이 좋은 코드다.

동일한 함수 호출이 상황에 따라 다르게 동작하는 것 : 다형성 = OCP를 만족하는 좋은 코딩 스타일
새로운 것 때문에 코드를 수정하는것은 좋지 않다.

[Prototype Design Pattern] - Clone()

타입코드(멤버변수)를 추가하는 것보다는 가상함수를 만들어서 사용하자.
=> Prototype이라고 불리는 디자인 패턴 (자신의 복사본을 만드는 가상함수)
instance로부터 별도의 instance를 만드는 패턴을 말한다.

변하지 않는 코드에서, 변하는 부분은 분리되어야 한다.

  • 변하는 것을 가상함수로 분리한다.

[Template Method Pattern] - Draw는 템플릿메소드, DrawImp() (훅메소드)

모든 도형에 공통적인 변하지 않는 전체적인 흐름을 기반 클래스에서 제공함
각 도형 별로 변해야 하는 부분을 가상함수로 제공해서, 파생클래스가 재정의 할 수 있게 한다.

템플릿메소드에서 공통적인 특징을 기반클래스에 넣고, 모든 도형은 그리는 방법은 모두 같잖아?
알고리즘의 여러 단계중 일부는 서브클래스에서 구현 - 훅 메소드

알고리즘의 구조를 유지하면서, 서브클래스에서 특정 단계를 재정의하는 것

가상함수를 만들 때, Draw를 재정의하면 안되는데, 파생클래스가 재정의하지 마라라는 문법
=> C++11이 나오면서, 뒤에 final을 붙이면 파생클래스가 재정의할 수 없게 만듬

절대 어떤 메소드를 부르지 못하게 만드려면 어떻게 해야할까?
=> protected같은것으로 외부에서 접근하지 못하게 만들면 되잖아.

객체의 생성과정도 OCP를 만족하지 못하는데, 객체의 생성과정을 OCP 만족시키려면 어떻게 해야할까?
Factory에서 가능하게 만들 수 있다.

[변하는 것을 분리하는 방법]
validation을 위해 한자씩 입력 받기

validation정책은 변경이 가능해야 되는데, edit 클래스보다는 validation 정책을 변경할 수 있어야 한다.
(isdigit) 바꿀 수 있는 방법이 두가지 있는데, 확인해보자.

전체적인 흐름은 변하지 않을때가 많은데, 그 안에서 사용하는 일부 정책은 변경될 때가 있다.
=> 변하지 않는 코드 안에 있는 변해야 하는 부분은 분리 하는 것이 좋다.

  1. 변해야 하는 부분을 별도의 가상 함수로 분리한다.
    장점) validation 정책을 변경하고싶다면, edit의 파생 클래스를 만들어 validate() 가상함수를 재정의 하면 된다.

  2. 변하는 것을 다른 클래스로 분리한다.
    필요 ) 교체가 가능해야 한다. - 약한 결합 (루즐리 커플링), 인터페이스 기반 통신
    IValidator라는 인터페이스를 만들어서, 교체 가능하게 만들어보자.

장점과 단점을 생각해봅시다...
템플릿 메소드는 상속 기반의 디자인 방법이다.
Strategy(전략패턴)은 구성의 디자인 방법이다.
평생 이 정책을 사용해야 하는게 아니라, 정책을 실행시간에 바꿀 수 있죠.

상황을 보고, 하는 작업이 뭔가 분리해 내긴 해야겠다. => 그 때 재사용성이 필요하고, 실행시간에 교체가 필요하다면 전략패턴이고, 그게 필요 없다면 템플릿메소드 방법을 사용하면 좋다.

[Strategy Pattern]
알고리즘의 군을 정의하고, 각각을 캡슐화해서 교환하여 사용할 수 있도록 만든다.
Strategy 패턴을 활용하면 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.
interface나 새로운 클래스를 통해서 validation을 사용했던 것.
디자인패턴이 23개가 있다 하는데, 사실은 거의 비슷한 것이 많다.
게임 프로그래밍에서, 게임 캐릭터가 자신이 처한 상황에 따라 공격이나 행동하는 방식을 바꾸고 싶을 때 스트레티지 패턴은 매우 유용하다.

크게 보면, 템플릿 메소드랑 비슷한 것 / strategy와 비슷한 것.
이 두가지를 명확히 알아놓으면 두 가지 기법들을 굉장히 유사하다고 느낄 수 있다.

@ggjae
Copy link
Owner Author

ggjae commented Nov 8, 2021

이전에 알고가야 할 것
Function Template - 함수 템플릿
자료형을 모호하게 두는 것, 자바에서의 제네릭과 비슷할까?
template을 사용함으로써 하나의 함수를 다양한 자료형으로 이용할 수 있게 만든다.

ex)

template<typename T> class List{
public:
    void push_front(const T& a){
        //.....
    }
}

inline

인라인 함수를 사용하면, 함수 내부의 코드를 재사용할 수 있고, 인스턴트 코드보다 함수에서 코드를 변경하거나 업데이트하기가 더 쉽다. 함수 이름을 통해 코드가 무엇을 의미하는지 이해가기 더 쉽고, 함수는 함수 호출 인수가 함수 매개 변수와 일치하는지 확인하기 위해 타입 검사를 한다.

=> 함수 호출이 함수 자체의 내용 복사본으로 대체되어 함수 오버헤드가 제거된다.
사실 최근에는 inline을 컴파일러 자체에서 자동으로 넣어주는 편입니다

[Policy Base Design]

단위 전략 디자인

list의 전방 삽입 알고리즘은 변하지 않지만, 동기화 정책은 교체 가능해야지.

변하는 것을 분리하는 방법 : 변하는 것을 가상함수로, 다른클래스로.
재사용성을 극대화시키기위해서는, 동기화정책을 전략패턴으로 구현시킬 수 있겠죠?
lock과 unlock을 해주는 interface

가상함수는 조금의 성능 저하를 일으키는데, 첫번째로 인터페이스를 사용하면 가상함수를 사용하게 되는데 성능 저하가 동반됩니다.

정책클래스를 인터페이스 기반으로 놓던가, 템플릿 인자로 놓던가.... 이런 디자인 기법을 단위 전략 디자인이라고 불러요.
template 인자를 사용해서 정책 클래스를 교체하는 기술, C++ 기반 라이브러리에서 많이 볼 수 있는 디자인 기술
Modern C++ design 서적 참고

전략 패턴은 가상함수기반이기때문에, 정책 교체가 가능하지만 살짝 느려요.

하지만 단위 전략디자인은 인라인치환이 가능하고, 빠르지만 실행 시간에 교체할 수 없어요.

컴파일하고나면, 이미 Typename으로 지정했기 때문에 변경이 불가능하죠.
동기화정책을 바꾸는 것은 policy base 가 더 좋다는 거죠.

클래스 전방선언 - 포인터는 먼저 쓰게 해달라..

CPP 코드가 어떻게 진행되는지 알아? 전역변수가 생성된 이후 -> 기반 클래스 생성자 후 main으로 들어가게 되는데,. 전역변수 생성자가 먼저 실행이 되고나서 main이 실행된다.

[함수와 정책]

멤버 함수에서 변하는 것
변하는 것을 가상 함수로 분리 / 변하는 것을 다른 클래스로 분리

멤버가 아닌 일반 함수에서 변하는 것
사용자가 바꿀 수 있는 것은, 함수의 인자밖에 없다.
변해야 하는 것(정책)을 함수 인자화 합니다.

일반 함수에서도 변하지 않는 알고리즘 안에 변해야 하는 부분이 있다면, 분리를 해야 합니다!!!
요즘엔 함수포인터로 넘기는 것보단, 람다함수를 통해서 넘기곤 해요.

함수인자로 정책을 담은 코드를 전달하는 방법

  • 함수 포인터
  • 함수 객체, 람다 표현식을 전달하고 템플릿으로 받는다.

@ggjae
Copy link
Owner Author

ggjae commented Nov 8, 2021

[State Pattern]

상태에 따라 변경되는 동작들을 다른 클래스로 분리한다.
동작의 인터페이스를 정의하고, item에 따른 run(), attack() 함수의 동작을 정의한 클래스를 별도로 제공한다.
객체의 속성은 유지하지만, 동작을 변경할 수 있다.
클래스가 아닌 객체에 대한 변화

상태 패턴 정리
의도 : 객체 자신 내부상태에 따라, 행위를 변경하도록 한다. 객체는 마치 클래스를 바꾸는 것 처럼 보인다.
캐릭터들은 state에 의존한다. 전략패턴과 달라보이는 게 없어...

-- 의도가 다름. 상태 패턴은 동작을 변경

전략패턴은 알고리즘을 다 클래스로 캡슐화하여 알고리즘의 대체가 가능하도록 한다.
-- 전략패턴은 알고리즘을 변경

변하지 않는 코드에서 변해야 하는 부분은 분리되어야 한다.

일반 함수에서 변하는 것

  • 함수 인자로 분리 (함수 포인터, 함수 객체, 람다 표현식)

멤버 함수에서 변하는 것

  1. 가상 함수로 분리 - template method
  • 실행시간에 교체할 수 없고, 변하는 코드를 재사용할 수 없다.
  • 상속기반의 패턴
  1. 다른 클래스로 분리
  • 변하는 코드를 재사용할 수 있다.
  • 정책 클래스를 교체하는 2가지 방법
    1. 인터페이스로 교체 - Strategy, State 패턴
  • 1-1. 실행시간 교체 가능, 가상함수 기반이라 좀 느리다.
    1. 템플릿 인자로 교체 - Policy Base Design
  • 2-1. 실행시간 교체 불가능, 인라인 치환 가능이라 좀 빠르다.

@ggjae
Copy link
Owner Author

ggjae commented Nov 8, 2021

[Composite Pattern]
일을 하는 메뉴들을 MenuItem, 선택했을 때 하위 메뉴를 보여주는 것들을 PopupMenu라고 하자.
하위 항목들을 담을 수 있는 vector를 만들고, 하위항목안에는 메뉴 아이템들도 다 포함될 수 있어야하니까, BaseMenu라는 기반 클래스를 생성하자.

기반 클래스인 BaseMenu만들고 기본 멤버변수 추가

객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들 수 있다.
개별 객체와 복합 객체를 구별하지 않고 동일한 방법으로 다룰 수 있다.

메뉴바가 가지고 있는 0,1,2에 해당하는 메뉴를 얻어낼 수 있도록 getSubmenu를 만들어보자.
menubar->getsubmenu(1)

생성자 상속이 가능해.. => using MenuItem::MenuItem; 을 사용하면, 생성자 상속이 가능하다.
MenuItem을 만들때 사용했던 생성자를 그대로다가 다른 클래스에서도 쓸 수 있네요.
인자 두가지면 인자 두가지 다.. 가능...

메뉴 선택시 하는 일을 가상 함수로 분리하는 경우에는, 파생 클래스의 개수가 너무 많아진다.
이를 좀 완화하기 위해선, 변하는 것을 다른 클래스로 뽑는 것. => Strategy 패턴.
인터페이스 만들어서 메뉴 메세지를 처리하려면, 아래 인터페이스를 정의해야한다고 약속한다.
메뉴가 선택됐을 때 호출될 함수를 만들고 전략 패턴의 기본적인 모양인 포인터를 하나 넣고, 이후 메뉴가 선택됐을 때 함수 부르기.

메뉴 선택시 하는 일을 다른 클래스에게 위임하는 것. Listener라는 이름을 사용하는 기술
** 이 방법을 이용할 땐 ID를 받아서 이용합니다.. **
switch 방식의 단조로운게 커질 수 있지만, C++에서는 이런 방법말고도 또 많다. 더 살펴보자.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant