러스트로 디자인 패턴 시작하기

March 29, 2024 김백기 조회수 3,116

러스트로 디자인 패턴을 다루는 방법을 설명합니다.

들어가며

디자인 패턴은 소프트웨어 설계에서 반복되는 문제들에 대한 검증되고 효과적인 해결책을 제공하며, 이는 코드 재사용성, 유지보수성, 확장성 향상과 같은 이점을 가져다 줍니다. 디자인 패턴의 활용은 개발자의 생산성을 높이며, 개발자들 사이의 효율적인 의사소통을 가능케 합니다. 그리고 디자인 패턴은 소프트웨어 설계의 품질 향상과 개발 생산성 향상에 핵심적인 역할을 수행합니다.


러스트로 디자인 패턴 배우기

인정하겠습니다.

러스트로 디자인 패턴을 다루는 것은 쉽지 않습니다. 러스트는 객체지향 언어의 특징들을 가지고 있으나 완벽한 객체지향 언어가 아니며 메모리 안정성과 최적화를 추구하는 언어로 디자인 패턴의 재사용 가능한 추상화는 러스트의 모토인 무비용 추상화와 깔끔하게 어울리지는 않습니다.

디자인 패턴은 재사용 가능한 설루션으로서 소프트웨어 개발에서 반복적으로 발생하는 문제를 효과적으로 해결하는 방안을 제시합니다. 디자인 패턴은 소프트웨어를 설계하는 사람으로 하여금 당면한 문제를 일반화하여 쉽게 문제를 보다 쉽게 해결할 수 있는 방안을 제시합니다.


디자인 패턴은 아래와 같이 3가지 범주로 분류할 수 있습니다.



범주


설명


주요 패턴


생성


객체의 생성과 관련된 패턴


빌더, 팩토리 메서드, 싱글톤 등...


구조


객체간의 구조화와 관련된 패턴


어댑터, 브리지, 컴포지트, 데코레이터, 플라이웨이트 등...


행동


알고리즘이나 객체간 동작에 관련된 패턴


반복자, 옵저버, 상태, 전략 등...

표 1. 디자인 패턴의 범주


디자인 패턴은 만능이 아니므로 당면한 문제에 따라 적절히 취사선택하여야 합니다. 부적절하게 사용된 패턴은 오히려 문제를 더 복잡하게 만들 수 있습니다. 그럼에도 불구하고 디자인 패턴을 연구하고 사용하는 것은 소프트웨어 엔지니어링의 핵심 요소이며 소프트웨어의 품질, 유지 보수성과 재사용성을 개선하는 데 도움이 될 수 있습니다.

디자인 패턴은 내용이 방대하기 때문에 본 블로그에서는 디자인 패턴의 일부분만 다룹니다.



팩토리 메서드 패턴

팩토리 메서드(factory method) 패턴은 객체의 생성을 별도의 생성 모듈에게 위임하여 객체의 생성과정과 관리를 생성 모듈이 제어하는 패턴을 말합니다.

아래 예제는 불고기 피자와 하와이안 피자를 생성하는 피자팩토리를 정의합니다. 생산할 피자의 타입을 PizzaType이라는 enum으로 정의합니다. 그리고 피자의 공통 인터페이스를 Pizza라는 트레잇으로 관리합니다.



예제 1. 팩토리 메서드로 피자를 생산하는 예제

싱글톤 패턴

싱글톤(singleton) 패턴은 시스템에 하나의 인스턴스만 생성되도록 하는 디자인 패턴입니다.

러스트에서는 lazy_static 크레이트를 사용하여 싱글톤 패턴을 구현할 수 있습니다.

먼저 Cargo.toml에 lazy_static 의존성을 추가합니다.



그리고 lazy_static! 매크로를 사용하여 MySingleton이라는 싱글톤을 추가합니다.



​예제 2. 싱글톤 패턴 예제

빌더 패턴

빌더(builder) 패턴은 복잡한 파라미터를 가진 객체를 빌더를 사용하여 쉽게 만들 수 있도록 하는 디자인 패턴입니다.

아래 예는 빌더 패턴의 예시입니다. BurgerBuilder의 모든 함수는 자기 자신을 반환하고 있는데 이 방식을 통해 사용하는 쪽에서는 복잡한 옵션을 일종의 체인과 같은 형태로 추가할 수 있습니다.


예제 3. 빌더 패턴 예제

어댑터 패턴

어댑터(adapter) 패턴은 기존 구현체가 시스템의 인터페이스와 호환되지 않은 상황에서 인터페이스를 동일하게 유지하고 싶을 때 사용합니다. 그래서 객체를 새로운 인터페이스로 래핑함으로써 호환되지 않는 인터페이스를 함께 작동하게 하고 복잡한 시스템의 구현을 단순화할 수 있습니다.


아래 예제는 어댑터 패턴을 구현한 예제입니다. Adaptee에는 벤더가 정의한 API가 있고 인터페이스는 시스템과 맞지 않습니다. 그래서 Adapter를 두어 우리가 정의한 API로 벤더의 API를 사용할 수 있게 만듭니다.



그림 1. 어댑터 패턴 설명


예제 4. 어댑터 패턴 예제

컴포지트 패턴

컴포지트(composite) 패턴은 객체 그룹을 단일 객체와 동일한 방식으로 처리할 수 있는 디자인 패턴입니다. 객체 그룹은 트리 구조로 구성되며, 트리의 각 노드는 객체 그룹 또는 단일 객체를 나타냅니다. 컴포지트 패턴은 여러 개의 객체 모음을 하나의 객체로 취급하여 작업해야 할 때 유용합니다.

아래 예제는 컴포지트 패턴을 러스트로 구현한 예제입니다. Control 트레이트는 일종의 트리의 노드 역할을 합니다.



그림 2. 컴포지트 패턴


draw라는 공통 인터페이스를 가지고 있습니다. Panel은 자식 컨트롤들을 관리하는 트리의 노드입니다. draw가 호출되면 자식 노드들의 draw를 호출합니다.



​예제 5. 컴포지트 패턴 예제

데코레이터 패턴

데코레이터(decorator) 패턴은 주어진 상황이나 용도에 따라 객체에 책임을 부여하는 패턴입니다. 데코레이터 패턴을 사용하면 상속을 사용하지 않고도 상속과 비슷한 효과를 낼 수 있습니다. 그뿐만 아니라 같은 클래스의 다른 객체의 동작에 영향을 주지 않고 개별 객체에 동작을 추가할 수 있습니다.

아래 예제는 데코레이터 패턴을 사용하여 버튼에 다양한 속성을 부여하는 예제입니다.



예제 6. 데코레이터 패턴 예제

플라이웨이트 패턴

플라이웨이트(flyweight) 패턴은 객체 간의 공통 부분이나 객체 전체를 공유하여 시스템 메모리 사용량을 최소화하는 방법입니다.

아래 예제는 캐싱 기법을 사용하여 Flyweight 객체 전체를 공유하는 방법을 보여줍니다.


예제 7. 플라이웨이트 패턴 예제

옵저버 패턴

옵저버(observer) 패턴은 객체의 상태가 변경되면 모든 관찰자에 알림을 전송하는 패턴으로 일종의 구독 메커니즘을 정의하는 디자인 패턴입니다. 러스트에서 옵저버 패턴은 Rc나 Arc 등을 사용하여 구현할 수 있습니다.

아래 예제는 옵저버 패턴을 러스트로 구현한 예제입니다. 이벤트가 발생하면 subscribe로 구독된 모든 Listener 인스턴스에게 이벤트가 전달됩니다.



그림 3. 옵저버 패턴



예제 8. 옵저버 패턴 예제

전략 패턴

전략(strategy) 패턴은 상황에 맞춰 런타임에 동작을 선택할 수 있도록 하는 디자인 패턴입니다. 전략 패턴을 사용하여 런타임에 알고리즘을 선택함으로써 코드를 보다 유연하게 만들고 복잡한 시스템의 구현을 단순화할 수 있습니다.

아래 예제는 전략 패턴을 사용하여 런타임에 렌더링 엔진을 Html과 Markdown 방식으로 선택하는 예제입니다.



그림 4. 전략 패턴



예제 9. 전략 패턴 예제

상태 패턴

상태(state) 패턴은 내부 상태가 변경될 때 해당 객체가 그 행동을 변경할 수 있게 하는 디자인 패턴입니다. 상태 패턴은 내부 상태에 따라 다양한 동작을 하는 객체를 만들어야 할 때 유용합니다.

아래는 상태 패턴의 예제입니다. 상태는 시작 상태, 동작 상태, 정지 상태가 있고 각 상태에 따라 동작이 달라집니다. 시작 상태는 동작 상태로 상태를 변경하고, 동작 상태는 정지 상태로 상태를 변경합니다.


예제 10. 상태 패턴 예제

결론

러스트로 디자인 패턴을 다루는 방법을 배웠습니다. 러스트는 디자인 패턴을 사용하기에 좋은 언어는 아니지만 기존의 패턴들을 충분히 잘 활용할 수 있습니다. 그래서 러스트의 강점인 안정성을 토대로 여러분의 프로젝트의 재사용성을 크게 높일 수 있습니다. 이 포스팅이 러스트 개발자에게 많은 도움이 되길 바라며, 이만 줄이도록 하겠습니다.

이전 포스팅 ‘러스트로 객체 지향 프로그래밍 시작하기' 와 ‘러스트로 크로스 플랫폼과 GUI 프로그래밍 시작하기’도 참조해 주시기를 바랍니다.


VD사업부 S/W Platform Lab의 김백기입니다.

감사합니다.




저자

김백기

S/W Platform Lab(VD)

이메일 문의하기