Life Archive

핵심 디자인 패턴 6가지

싱글톤 패턴
전략 패턴
팩토리 패턴
어댑터 패턴
프록시 패턴
옵저버 패턴

 


1. 싱글톤 패턴


프로그램 실행 중에 최대 하나만 있어야 할 경우나 전역적으로 접근이 가능한 개체여야 할 경우,

어떤 클래스에서 만들 수 있는 인스턴스 수를 하나로 제한하기 위해서 사용한다.

static 변수를 직접 쓰는 대신 대신 굳이 싱글톤 패턴을 쓰는 이유는 static 변수는 전역 변수에 가깝지만

싱글톤 객체는 객체지향 패러다임에 맞기 때문 싱글톤은 객체이므로 생성자로 생성 시점을 제어할 수 없다.

 


2. 전략 패턴


간단히 말해서 객체가 할 수 있는 행위들 각각을 전략으로 만들어 놓고, 

동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것만으로 행위의 수정이 가능하도록 만든 패턴입니다.

예를 들어, 기차( Train )와 버스( Bus ) 클래스가 있고, 

이 두 클래스는 Movable 인터페이스를 구현했다고 가정하겠습니다. 

그리고 Train과 Bus 객체를 사용하는 Client도 있습니다.

이 구조를 코드로 표현하면 다음과 같습니다. 기차는 선로를 따라 이동하고, 버스는 도로를 따라 이동합니다. 

그러다 시간이 흘러 선로를 따라 움직이는 버스가 개발되었다고 가정해봅시다.

그러면 Bus의 move() 메서드를 다음과 같이 바꿔주기면 하면 끝납니다. 

public void move(){ System.out.println("선로를 따라 이동"); }

그런데 이렇게 수정하는 방식은 SOLID의 원칙 중 OCP( Open-Closed Principle )에 위배됩니다. 

OCP에 의하면 기존의 move()를 수정하지 않으면서 행위가 수정되어야 하지만, 

지금은 Bus의 move() 메서드를 직접 수정했지요. 

 

또한 지금과 같은 방식의 변경은 시스템이 확장이 되었을 때 유지보수를 어렵게 합니다. 

예를 들어, 버스와 같이 도로를 따라 움직이는 택시, 자가용, 고속버스, 오토바이 등이 추가된다고 할 때, 

모두 버스와 같이 move() 메서드를 사용합니다.

만약에 새로 개발된 선로를 따라 움직이는 버스와 같이, 선로를 따라 움직이는 택시, 자가용, 고속버스 ... 등이 생긴다면, 

택시, 자가용, 고속버스의 move() 메서드를 일일이 수정해야 할 뿐더러, 

같은 메서드를 여러 클래스에서 똑같이 정의하고 있으므로 메서드의 중복이 발생하고 있습니다.

이번에는 위와 같이 선로를 따라 이동하는 버스가 개발된 상황에서 시스템이 유연하게 변경되고 

확장될 수 있도록 전략 패턴을 사용해보도록 하겠습니다.

이제 Train과 Bus 객체를 사용하는 Client를 구현할 차례입니다. Train과 Bus 객체를 생성한 후에, 

각 운송 수단이 어떤 방식으로 움직이는지 설정하기 위해 setMovableStrategy() 메서드를 호출합니다. 

 

그리고 전략 패턴을 사용하면 프로그램 상으로 로직이 변경 되었을 때, 얼마나 유연하게 수정을 할 수 있는지 

살펴보기 위해 선로를 따라 움직이는 버스가 개발되었다는 상황을 만들어 버스의 이동 방식 전략을 수정했습니다.

 

(참고)

 

[디자인패턴] 전략 패턴 ( Strategy Pattern )

전략 패턴 ( Strategy Pattern )객체들이 할 수 있는 행위 각각에 대해 전략 클래스를 생성하고, 유사한 행위들을 캡슐화 하는 인터페이스를 정의하여,객체의 행위를 동적으로 바꾸고 싶은 경우 직접

victorydntmd.tistory.com


3. 팩토리 패턴


팩토리 메서드는 객체를 생성하는 인터페이스는 미리 정의하지만, 

인스턴스를 만들 클래스의 결정은 서브 클래스 쪽에서 내리는 패턴이다. 

 

다시 말해 여러 개의 서브 클래스를 가진 슈퍼 클래스가 있을 때,

인풋에 따라 하나의 자식 클래스의 인스턴스를 리턴해 주는 방식이다.

아래 코드는 버거 자동 주문 기계이다. 

거기서 음료 주문을 할 때 컵 크기를 고른다. 고객은 스몰, 미디엄, 라지를 선택할 뿐 실제 컵 용량(ml)을 모른다

Cup 클래스의 생성자는 private이다. 즉, Cup 클래스의 개체를 직접 생성할 수 없다.

createOrNull() 정적 메서드에서 switch 문을 통해 Cup 개체를 생성해주는 것을 볼 수 있다.

 

생성자 대신 정적 메서드를 사용하는 것의 장점 null을 반환 가능 생성자는 생성이 불가능한 경우 예외를 던질 수밖에 없음 반환형이 없기 때문 아직 팩토리 메서드가 완성된 것은 아니다.

여기서 만약 모든 나라에서 사용할 수 있는 기계를 만든다면 어떻게 해야 할까?

즉 나라마다 small, medium, large의 크기가 다르다. 같은 large여도 나라마다 다르기 때문에 이를 구현하고 싶은 것이다.

createOrNull()을 다형적으로 만든다.

그러나 static 메서드를 다형적으로 만들 수 없다 그래서 자식 클래스를 만들어야 한다.

 

다형적으로 개체 생성 가능 따라서 이 패턴을 가상 생성자 패턴이라고도 함 생성자에서 오류 상황 감지 시 null 반환 가능 클라이언트는 본인에게 익숙한 인자를 통해 개체 생성 가능 팩토리 메서드 패턴은 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며(Loosely Coupled), 확장을 쉽게 한다.

 

이렇듯 팩토리 메서드 패턴을 사용하게 되면 인스턴스를 필요로 하는 Application에서 Computer의 Sub 클래스에 대한 정보는 모른 채 인스턴스를 생성할 수 있게 된다.

이렇게 구현한다면 앞으로 Computer 클래스에 더 많은 Sub 클래스가 추가된다 할지라도 getComputer()를 통해 인스턴스를 제공받던 Application의 코드는 수정할 필요가 없게 된다.


4. 어댑터 패턴


외부 라이브러리 클래스의 메소드 형태가 우리의 프로그램에서 사용하기 적당하지 않을 때,

외부 라이브러리 클래스의 메소드를 변경하지 않고, 새로운 클래스를 만들어 기존 클래스를 감싸는 방식으로 해결하는 방법 또한 기존 클래스에 없는 기능을 추가하려고 할 때 사용한다.

 

기본적인 패턴은 A 클래스의 어떤 getA()라는 메서드 시그니처가 있는데 마음에 안들어서 바꾼다고 해보자.

그러면 클래스 B를 만들고 클래스 B안에 클래스 A를 포함하는 것이다.

 

정확히 말하면 클래스 A로부터 만든 개체를 포함하는 것이다.

그리고 앞으로 클래스 B에 있는 getB() 메서드를 호출할 건데 이게 알아서 A의 getA()를 호출해주는 것이다.

즉, 내가 호출할 때는 B만 사용하고 내부적으로는 getA()를 어떻게서든 사용하는 것!

 


5. 프록시 패턴


프록시 패턴이 이루려는 목적도 비슷하다. 클래스 안에서 어떤 상태를 유지하는 게 여의치 않은 경우가 있다.

데이터가 너무 커서 미리 읽어 두면 메모리가 부족하고 개체 생성 시 데이터를 로딩하면 시간이 꽤 걸린다.

그리고 개체는 만들었으나 그 속의 데이터를 사용하지 않을 수도 있다.

 

이럴 경우 다음과 같은 방법을 통해 불필요한 데이터 로딩을 방지한다.

개체 생성 시에는 데이터 로딩에 필요한 정보만(예: 파일 위치) 기억해 둠

클라이언트가 실제로 데이터를 요청할 때 메모리에 로딩함 언제 지연 로딩을 사용하고 언제 즉시 로딩을 사용해야 할까?

 

요즘은 클라이언트가 내부 동작방법을 분명히 알고 그에 적합한 UI를 보여주는 방법이 더 사랑받는다.

따라서 요즘 세상에는 클래스가 남몰래 프록시 패턴을 사용하는 것보다

클라이언트에게 조작 권한을 주는 게 좋을 수 있다. 


6. 옵저버 패턴


어느 순간 A가 바뀌면 B가 그것을 깨닫고 행동을 하게 되는 것이다 그런데 B가 한명만 있는 게 아니다. 

여러 개가 있을 수 있다. B, C, D, E도 감시를 하고 있을 수도 있다. 하지만 A는 하나이다.

A를 감시하며 A가 변하면 감시하고 있는 것들도 바뀌는 것이다.

 

이때 쓰는 패턴이 옵저버 패턴이다.

 

발행-구독(pub-sub) 패턴이라고도 부를 수 있다. pub-sub 패턴과의 차이는 발행자가 하나이다.

즉, pub-sub은 다대다 관계(many-to-many)이고 그걸 조율해주는 중간 클래스가 있을 뿐인 것이다. 

 

 

출처 : 김성주님

'게임 개발 학습 > CS' 카테고리의 다른 글

[CS] Getter Setter  (0) 2023.09.25
[CS] Pure Function  (0) 2023.09.25
[CS] 객체 지향 설계 5원칙 (SOILD 원칙)  (0) 2023.09.25
[CS] OOP 스터디 (설계 5원칙과 Layer)  (0) 2023.09.25