본문 바로가기
Technical/Organize

[Technical Organize] 기술면접_디자인패턴

by song.ift 2022. 12. 28.

문제. C++을 이용하여 Decorator 패턴을 템플릿으로 구현하고 추상적 캐릭터 게임 클래스를 구현해 Decorator의 사용 예를 들어라

Decorator 패턴은 특정 객체에 기능을 추가하거나 삭제하는데 있어서 객체에 영향을 미치지 않고, 기능을 추가하는 형태를 취하는 디자인 패턴 중 하나이다. 즉 추가된 기능이 기존의 객체를 꾸며주면서 추가된 기능도 수행함과 동시에 기존의 객체가 가진 기능도 수행되면서 마치 새로운 기능이 추가되는 것처럼 보이도록 만들어 주는 것이다. 기능들 사이의 관계는 참조 연결고리를 통해 연결해 줌으로써 기능의 추가와 삭제를 가능토록했다.

 

문제. 싱글톤에 대해서 아는 대로 설명.

애플리케이션이 시작될 때 어떤 클래스가 최초 한번만 메모리를 할당하고(Static), 그 해당 메모리에 인스턴스를 만들어 사용하는 디자인패턴.
즉, 싱글톤 패턴은 '하나'의 인스턴스만 생성하여 사용하는 디자인 패턴이다.

인스턴스가 필요할 때, 똑같은 인스턴스를 만들지 않고 기존의 인스턴스를 활용하는 것!

생성자가 여러번 호출되도, 실제로 생성되는 객체는 하나이며 최초로 생성된 이후에 호출된 생성자는 이미 생성한 객체를 반환시키도록 만드는 것이다

- 싱글톤 패턴을 쓰는 이유

먼저, 객체를 생성할 때마다 메모리 영역을 할당받아야 한다. 하지만 한번의 new를 통해 객체를 생성한다면 메모리 낭비를 방지할 수 있다.

또한 싱글톤으로 구현한 인스턴스는 '전역'이므로, 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능한 장점이 있다.

- 많이 사용하는 경우는 언제인지

주로 공통된 객체를 여러개 생성해서 사용해야하는 상황

각 액티비티 들이나, 클래스마다 주요 클래스들을 하나하나 전달하는게 번거롭기 때문에 싱글톤 클래스를 만들어 어디서든 접근하도록 설계

또한 인스턴스가 절대적으로 한 개만 존재하는 것을 보증하고 싶을 때 사용함

 

문제. 싱글톤을 사용했을 때 문제점은 없었는가?

객체 지향 설계 원칙 중에 개방-폐쇄 원칙이란 것이 존재한다.

만약 싱글톤 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면 다른 클래스들 간의 결합도가 높아지게 되는데, 이때 개방-폐쇄 원칙이 위배된다. . (=객체 지향 설계 원칙에 어긋남) 결합도가 높아지게 되면, 유지보수가 힘들고 테스트도 원활하게 진행할 수 없는 문제점이 발생한다.

또한, 멀티 스레드 환경에서 동기화 처리를 하지 않았을 때, 인스턴스가 2개가 생성되는 문제도 발생할 수 있다.

따라서, 반드시 싱글톤이 필요한 상황이 아니면 지양하는 것이 좋다고 한다. (설계 자체에서 싱글톤 활용을 원활하게 할 자신이 있으면 괜찮음)

개발을 할때 항상 들어온 goto는 쓰면 안돼! 전역 객체는 안 좋은거야! 라는 말 처럼 꼭 필요한 경우아니면 지양해야함. // 적절히 잘 쓰면 아주 좋음, (그게 어렵지)

 

문제. 템플릿 메소드 패턴

알고리즘의 구조를 변경하지 않고, 알고리즘의 특정 단계를 다시 정의할 수 있게 해주는 패턴.

즉, 공통되는 부분은 추상클래스로 정의된 상위 클래스에서 구현하고, 재정의가 필요한 부분은 추상메소드로 선언한다.

- 왜 사용하나

이유는 간단하다. (그냥 오버라이딩이랑 다를거없음)

코드의 중복을 줄이고, 유연하며 상속을 통한 확장 개발 방법에 좋다.

 

문제. 팩토리 메소드 패턴

설명 : 객체를 만드는 부분을 Sub class 맡기는 패턴.

객체 생성 하는 코드를 분리하여 클라이언트 코드와 결합도(의존성)를 낮추어 코드를 건드리는 횟수를 최소화 하기 위한 패턴

팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만들게 하는 패턴

- 구현

생성하는 클래스를 따로 만듬...

그 클래스의 반환형은 CGameObject

name이나 임시 객체로 건너오는 값에 따라서, 생성되는 인스턴스가 다르게 설계됨.

정리하면, 생성하는 객체를 별도로 둔다. 그리고, 그 객체에 넘어오는 값에 따라서, 다른 객체를 만들어 낸다.

- 장점

객체를 생성하는 코드를 추상화하여 코드를 한곳에서 관리하지 않으면, 변화(생성,수정,삭제)가 발생 했을 때 해당 클라이언트 코드를 전부 수정해줘야 한다.

즉, 객체지향 디자인패턴 원칙 확장에 대해서는 열려고있고 변화에 대해서는 닫혀있어야 한다.

때문에 변화가 일어날 수 있는 객체 생성 담당하는 클래스를 만들어 한곳에서 관리하여 결합도를 줄이기 위해 사용하는 패턴이다.

 

문제. 옵저버 패턴

상태를 가지고 있는 주체 객체 & 상태의 변경을 알아야 하는 관찰 객체

(1 대 1 or 1 대 N 관계)

서로의 정보를 주고받는 과정에서 정보의 단위가 클수록, 객체들의 규모가 클수록 복잡성이 증가하게 된다. 이때 가이드라인을 제시해줄 수 있는 것이 '옵저버 패턴'

구독자, 고객들은 정보를 얻거나 받아야 하는 주체와 관계를 형성하게 된다. 관계가 지속되다가 정보를 원하지 않으면 해제할 수도 있다. (잡지 구독을 취소하거나 우유 배달을 중지하는 것처럼)

이때, 객체와의 관계를 맺고 끊는 상태 변경 정보를 Observer에 알려줘서 관리하는 것을 말한다.

 

문제. 브릿지 패턴에 대해서 설명.

구현부에서 추상층을 분리하여 각자 독립적으로 변형이 가능하고 확장이 가능하도록 만드는 패턴이다. 즉 기능과 구현에 대해서 두 개를 별도의 클래스로 구현을 한다.

두개의 다른 계층(하나는 추상, 하나는 구현인 서로다른 계층의 커플링을 약화시키며 협력은 가능하도록 하는 패턴

- 설계

추상 클래스에 구현클래스를 포함관계로 가지고 있는다.

          

문제. 원형(Prototype) 패턴에 대해서 설명.

미리 만들어진 객체를 복사하여 객체를 생성하는 패턴이다.

다수의 객체 생성시에 발생되는 객체의 생성 비용을 효과적으로 줄일 수 있다.

- 사용 했을 때 예시

몬스터를 복사 생성하는 예제를 들었다.

몬스터를 다수 생성 시 미리 만들어놓은 몬스터 객체를 통한 복사 생성을 통해 객체를 넘겨줌으로써 생성에 필요한 오버헤드를 줄일 수 있다.

다른 데이터는 클론할 시 void* 형으로 특정 객체에는 원하는 데이터를 넘겨줄수 있게 했다.

- 단점

원본 객체는 필요가 없게 된다. 메모리의 낭비가 발생한다.

 

문제. 메디에이터 패턴에 대해서 설명

각 오브젝트 객체들의 서로의 값을 참조해야 할 때, 관계를 지어줘야 할 객체의 포인터를 해당 클래스마다 선언해야 한다. 이렇게 되면 소슼코드의 가독성은 물론 객체의 연결이 복잡해지므로 퍼포먼스에도 문제가 될 수 있다.

하지만 필요한 각 객체들의 포인터를 갖는 하나의 클래스로 관리를 하게 된다면 객체간의 통신에 있어서 1:1의 관계를 가지게 되어 소스코드가 간결해짐은 물론이거니와 이해가 훨씬 쉬워진다.

 

문제. 어댑터 패턴에 대해서 설명

용도 : 클래스를 바로 사용할 수 없는 경우가 있음 (다른 곳에서 개발했다거나, 수정할 수 없을 때) 중간에서 변환 역할을 해주는 클래스가 필요 → 어댑터 패턴

사용 방법 : 상속

호환되지 않은 인터페이스를 사용하는 클라이언트 그대로 활용 가능

향후 인터페이스가 바뀌더라도, 변경 내역은 어댑터에 캡슐화 되므로 클라이언트 바뀔 필요X

Ex) 아이폰의 이어폰을 생각해보자. 가장 흔한 이어폰 잭을 아이폰에 사용하려면, 잭 자체가 맞지 않는다. 따라서 우리는 어댑터를 따로 구매해서 연결해야 이런 이어폰들을 사용할 수 있다

이처럼 어댑터는 필요로 하는 인터페이스로 바꿔주는 역할을 한다

이처럼 업체에서 제공한 클래스가 기존 시스템에 맞지 않으면?

기존 시스템을 수정할 것이 아니라, 어댑터를 활용해 유연하게 해결하자

어댑터 패턴은 객체를 내부에 선언하는 객체 어댑터 패턴과 인터페이스를 상속받아 사용하는 클래스 아댑터 패턴으로 나뉜다.

 

문제. 스테이트 패턴이란

상태들을 상태라는 클래스를 추상적인 두고 추상의 클래스를 상속받아 필요한 객체들마다 가져 쓰는 각 객체마다 머신을 가져서 머신은 항상 하나의 행동을 가지게 끔해서 원하는 상태를 컨트롤 했다.

 

 

Dessign Pattern 프로젝트에서 언제 사용?

ž   싱글톤

- 어떤 클래스를 하나의 인스턴스를 갖도록 보장하고 이에 전역적인 접근을 제공해 불필요한 객체 생성을 막기 위해 사용 (Object Mgr, Device Mgr 등)

ž   추상 팩토리

- 구체적인 클래스 지정이 없이 서로 관련성이 있는 클래스의 생성을 위한 인터페이스를 제공하기 위해 사용

- 객체를 생성 시 팩토리에 매개변수를 다르게 주어 다양한 객체들을 팩토리 내에서 생성하게 구현

ž   프로토타입

- 객체의 원형을 생성하고 그 원형을 복사해 객체를 생성함으로 생성 시 파일 입출력 등 오버헤드가 큰 연산을 줄이기 위해 사용 (Resource, Monster 등)

ž   메디에이터(중재자)

- 여러 객체 간의 상호작용을 캡슐화해 Mediator 객체가 관리할 수 있도록 함

- 모든 Object를 관리하는 Object Manager를 중재자로 두어서 구현 (복잡한 관계 개선)

ž   스테이트

- 유한 상태에 대해 입력에 따라 단일 상태로 지정해야 할 때 FSM 기반의 패턴을 사용 (AI)

ž   이터레이터(반복자)

- 동일한 자료형의 객체가 모여있을 시 같은 기능을 순차적으로 반복 수행하기 위해 사용

ž   옵저버(관찰자)

- 여러 대상(Subject)과 관찰자(Observer)를 두어 참조 없이 알림으로만 필요한 정보를 받을 수 있게 함 (UI에서 Player의 Status 정보를 알림 받아 표현)

ž   브릿지

- 구현층과 추상층을 분리해 기능이 독립적으로 변형과 확장이 가능할 때 사용

- 원거리, 근거리 Monster를 공격이란 구현을 만들어 각자 객체에 알맞게 행동하도록 구현

ž   컴포넌트

- 하나의 객체를 이루는 요소들을 각각 컴포넌트화해 관리함으로써 클래스 간의 결합도를 낮추기 위해 사용 (Transform, Render, Shader, Texture, Buffer 등)

ž   템플릿 메서드

- 같은 구조의 클래스지만 부분적으로 다른 구문으로 구성된 코드 중복을 최소화 할 때 서브클래스로 캡슐화해 사용 (TimerA::Fool(), TimerB::Fool())

댓글