러스트 객체지향

  • 러스트는 객체지향 언어가 아니어서, 객체지향의 캡슐화, 다형성, 상속 등을 완벽하게 지원하지는 못한다
  • 그러나 trait 같은 형태로 객체지향의 핵심적인 개념을 사용할 수 있다

캡슐화

  • 객체 내부의 정보를 숨겨 외부에서 접근할 수 없도록 제어하는 것
  • pub 키워드를 사용해서 캡슐화 할 수 있다

다형성

  • 객체가 문맥에 따라서 다른 자료형으로 형태를 취할 수 있는 것
  • trait, dyn 키워드로 다형성을 사용할 수 있다

trait

trait Hello {
    fn hello_msg(&self) -> String;
}

fn print_hello(say: &dyn Hello) {
    println!("{}", say.hello_msg());
}

struct Student {}

impl Hello for Student {
    fn hello_msg(&self) -> String {
        String::from("hello student")
    }
}

struct Teacher {}

impl Hello for Teacher {
    fn hello_msg(&self) -> String {
        String::from("hello teacher")
    }
}

fn main() {
    let student = Student {};
    let teacher = Teacher {};

    print_hello(&student);
    print_hello(&teacher);
}
  • trait은 함수를 추상화해 트레잇을 구현하는 구조체들이 같은 이름의 API를 제공하도록 한다
  • dyn은 컴파일러에 트레잇의 구현체를 런타임에 찾을 것을 의미한다
    • cpp의 virtual과 비슷한듯
  • trait은 impl 트레잇 이름 for 구조체 이름 형식으로 정의한다

상속

  • rust에는 상속이 없다
  • 데코레이터나 프록시 패턴으로 상속을 유사하게 사용하는 것이 최선이다

디자인 패턴

  • 디자인 패턴은 3가지 범주로 나눌 수 있다
범주설명주요 패턴
생성객체 생성과 관련된 패턴빌더, 팩토리 메서드, 싱글턴 등
구조객체 간 구조화와 관련된 패턴어댑터, 브릿지, 컴포지트, 데코레이터, 플라이웨이트 등
행동알고리즘, 객체 간 작동에 관련된 패턴반복자, 옵저버, 상태, 전략 등

디자인 패턴 - 생성

팩토리 메서드

trait Pizza {
    fn eat(&self);
}

enum PizzaType {
    Bulgogi,
    Hawaiian,
}

trait PizzaFactory {
    fn create(pizza_type: PizzaType) -> Box<dyn Pizza>;
}

struct ConcretePizzaFactory {}

impl PizzaFactory for ConcretePizzaFactory {
    fn create(pizza_type: PizzaType) -> Box<dyn Pizza> {
        match pizza_type {
            PizzaType::Bulgogi => Box::new(BulgogiPizza {}),
            PizzaType::Hawaiian => Box::new(HawaiianPizza {})
        }
    }
}

struct BulgogiPizza {}

impl Pizza for BulgogiPizza {
    fn eat(&self) {
        println!("eat bulgogi pizza");
    }
}
struct HawaiianPizza {}

impl Pizza for HawaiianPizza {
    fn eat(&self) {
        println!("eat hawaiian pizza");
    }
}

fn main() {
    let bulgogi = ConcretePizzaFactory::create(PizzaType::Bulgogi);
    let hawaiian = ConcretePizzaFactory::create(PizzaType::Hawaiian);

    bulgogi.eat();
    hawaiian.eat();
}
  • 팩토리 메서드 패턴은 객체의 생성을 별도 생성 모듈에 위임해, 객체 생성 과정 및 관리를 생성 모듈이 제어한다

싱글턴 패턴

[dependencies]
lazy_static = "1.4.0"
#[macro_use]
extern crate lazy_static;

lazy_static! {
    static ref INSTANCE: MySingleton = {
        MySingleton::new(String::from("singleton"))
    };
}

struct MySingleton {
    name: String
}

impl MySingleton {
    fn new(name: String) -> MySingleton {
        MySingleton { name }
    }

    fn call(&self) {
        println!("{}", self.name);
    }
}

fn main() {
    INSTANCE.call();
}
  • 싱글턴 패턴은 시스템에 하나의 인스턴스만 생성되도록한다
  • lazy_static 크레이트를 사용해서 구현할 수 있다

빌더 패턴

struct Burger {
    bun: String,
    patties: i32,
    sauce: String,
    extra: Vec<String>
}

struct BurgerBuilder {
    bun: String,
    patties: i32,
    sauce: String,
    extra: Vec<String>
}

impl BurgerBuilder {
    fn new() -> BurgerBuilder {
        BurgerBuilder { bun: String::from(""), patties: 0, sauce: String::from(""), extra: vec![] }
    }

    fn bun(mut self, bun: String) -> BurgerBuilder {
        self.bun = bun;
        self
    }

    fn patties(mut self, patties: i32) -> BurgerBuilder {
        self.patties = patties;
        self
    }

    fn sauce(mut self, sauce: String) -> BurgerBuilder {
        self.sauce = sauce;
        self
    }

    fn add_extra(mut self, extra: String) -> BurgerBuilder {
        self.extra.push(extra);
        self
    }

    fn build(self) -> Burger {
        Burger { bun: self.bun, patties: self.patties, sauce: self.sauce, extra: self.extra }
    }
}

fn main() {
    let burger = BurgerBuilder::new()
                                .bun(String::from("bun"))
                                .patties(2)
                                .sauce(String::from("sauce"))
                                .add_extra(String::from("extra"))
                                .build();
}
  • 빌더 패턴은 복잡한 파라미터를 가진 객체를 체인과 같은 형태로 쉽게 만들 수 있게 해준다

디자인 패턴 - 구조

adapter 구조

struct Adaptee {}

impl Adaptee {
    fn new() -> Adaptee {
        Adaptee {}
    }

    fn specific_api(&self) {
        println!("벤더가 정의한 API")
    }
}

struct Adapter {}

impl Adapter {
    fn new() -> Adapter {
        Adapter {}
    }

    fn call_api(&self) {
        Adaptee::new().specific_api();
    }
}

fn main() {
    let adapter = Adapter::new();
    adapter.call_api();
}
  • 기존 구현체(Adaptee)가 시스템 인터페이스와 호환되지 않는 경우
  • 기존 구현체의 인터페이스를 감싸는 Adapter를 정의해서 사용

Composite 패턴

trait Control {
    fn draw(&self) -> String;
}

struct Button {
    name: String
}

impl Button {
    fn new(name: String) -> Box<Button> {
        Box::new(Button { name })
    }
}

impl Control for Button {
    fn draw(&self) -> String {
        format!("Button - {}", self.name)
    }
}

struct Panel {
    name: String,
    controls: Vec<Box<dyn Control>>
}

impl Panel {
    fn new(name: String) -> Box<Panel> {
        Box::new(Panel { name, controls: vec![] })
    }
    fn add_control(&mut self, control: Box<dyn Control>) {
        self.controls.push(control);
    }
}

impl Control for Panel {
    fn draw(&self) -> String {
        let mut txt = format!("Panel - {}", self.name);
        for control in self.controls.iter() {
            txt = format!("{}\n\t{}", txt, control.draw())
        }
        txt
    }
}

fn main() {
    let mut root = Panel::new(String::from("root"));
    root.add_control(Button::new(String::from("button 1")));
    
    let mut panel = Panel::new(String::from("panel 1"));
    panel.add_control(Button::new(String::from("button 2")));

    root.add_control(panel);

    println!("{}", root.draw());
}
  • 객체 그룹을 단일 객체와 동일한 방식으로 처리할 수 있는 디자인 패턴
  • 객체 그룹은 트리 형태로 구성된다
  • 각 트리의 노드들은 객체 그룹이거나 단일 객체이다

decorator 패턴

trait Control {
    fn draw(&self) -> String;
}

struct Button {
    name: String,
    controls: Vec<Box<dyn Control>>
}

impl Button {
    fn new(name: String) -> Box<Button> {
        Box::new(Button { name, controls: vec![] })
    }
    fn add_decorator(&mut self, control: Box<dyn Control>) {
        self.controls.push(control);
    }
}

impl Control for Button {
    fn draw(&self) -> String {
        let mut txt = format!("{}", self.name);
        for control in self.controls.iter() {
            txt = format!("{} and {}", txt, control.draw())
        }
        txt
    }
}

struct Deco {
    name : String
}

impl Control for Deco {
    fn draw(&self) -> String {
        format!("{}", self.name)
    }
}

impl Deco {
    fn new(name: String) -> Box<Deco> {
        Box::new(Deco {name})
    }
}

fn main() {
    let mut button = Button::new(String::from("AAA"));
    button.add_decorator(Deco::new(String::from("BBB")));
    button.add_decorator(Deco::new(String::from("CCC")));

    println!("{}", button.draw());
}
  • 기존 객체에 컴포지션을 활용해서 새로운 기능을 동적으로 추가 및 확장
  • 같은 클래스 다른 객체 작동에 영향을 주지 않고 비슷한 효과를 낼 수 있음
  • 데코레이터 패턴을 사용하면 상속과 비슷한 효과를 낼 수 있음

Flyweight 패턴

use std::{collections::HashMap, rc::Rc};

trait Flyweight {
    fn get_name(&self) -> String;
}

struct ConcreteFlyweight {
    name: String
}

impl Flyweight for ConcreteFlyweight {
    fn get_name(&self) -> String {
        self.name.clone()
    }
}

struct FlyweightFactory {
    flyweights: HashMap<String, Rc<Box<dyn Flyweight>>>
}

impl FlyweightFactory {
    fn new() -> FlyweightFactory {
        FlyweightFactory { flyweights: HashMap::new() }
    }

    fn get_flyweight(&mut self, name: String) -> Rc<Box<dyn Flyweight>> {
        if let Some(instance) = self.flyweights.get(&name) {
            return instance.clone();
        }

        let instance = Box::new(ConcreteFlyweight {
            name: name.clone()
        });

        let instance = Rc::new(instance as Box<dyn Flyweight>);
        self.flyweights.insert(name, instance.clone());

        instance.clone()
    }
}

fn main() {
    let mut factory = FlyweightFactory::new();

    let flyweight1 = factory.get_flyweight(String::from("AAA"));
    let flyweight2 = factory.get_flyweight(String::from("BBB"));
    let flyweight3 = factory.get_flyweight(String::from("AAA"));

    println!("{}, {}, {}", flyweight1.get_name(), flyweight2.get_name(), flyweight3.get_name());
}
  • Flyweight 패턴은 객체 간 공통 부분이나 객체 전체를 공유해 시스템 메모리를 절약하는 패턴이다
  • 위 예제에서는 hash map으로 캐싱하여 flyweight 객체 전체를 공유하였다

디자인 패턴 - 행동

옵저버 패턴

use std::rc::Rc;

#[derive(PartialEq)]
struct Listener {}

impl Listener {
    fn on_event(&self, data: &str) {
        println!("event: {}", data);
    }
}

struct Subject {
    observers: Vec<Rc<Listener>>
}

impl Subject {
    fn subscribe(&mut self, observer: Rc<Listener>) {
        self.observers.push(observer);
    }

    fn unsubscribe(&mut self, observer: Rc<Listener>) {
        if let Some(index) = self.observers.iter().position(|o| *o == observer) {
            self.observers.remove(index);
        }
    }

    fn notify(&self, data: &str) {
        for observer in &self.observers {
            observer.on_event(data);
        }
    }
}

fn main() {
    let mut subject = Subject {observers: vec![]};

    let observer1 = Rc::new(Listener {});
    let observer2 = Rc::new(Listener {});

    subject.subscribe(observer1.clone());
    subject.subscribe(observer2.clone());

    subject.notify("event 1");

    subject.unsubscribe(observer1);

    subject.notify("event 2");
}
  • 객체의 상태가 변경되었을 때 모든 관찰자에게 event를 전송하는 패턴

strategy 패턴

trait Render {
    fn render(&self, title: String, body: String);
}

struct HTMLRender {}

impl Render for HTMLRender {
    fn render(&self, title: String, body: String) {
        println!("<html><title>{}</title><body>{}</body></html>", title, body);
    }
}

struct MarkDownRender {}

impl Render for MarkDownRender {
    fn render(&self, title: String, body: String) {
        println!("# {}\n{}", title, body);
    }
}

struct Page<T: Render> {
    renderer: T
}

impl<T: Render> Page<T> {
    fn new(renderer: T) -> Page<T> {
        Page { renderer }
    }

    fn render(&self, title: String, body: String) {
        self.renderer.render(title, body);
    }
}

fn main() {
    let html = Page::new(HTMLRender {});
    html.render(String::from("title"), String::from("body"));
}
  • 상황에 맞춰 필요한 동작을 런타임에서 선택하는 패턴

상태 패턴

trait State {
    fn on_state_changed(self: Box<Self>) -> Box<dyn State>;
}

struct Start;
impl State for Start {
    fn on_state_changed(self: Box<Self>) -> Box<dyn State> {
        println!("current: Start");
        Box::new(Running)
    }
}

struct Running;
impl State for Running {
    fn on_state_changed(self: Box<Self>) -> Box<dyn State> {
        println!("current: Running");
        Box::new(Stop)
    }
}

struct Stop;
impl State for Stop {
    fn on_state_changed(self: Box<Self>) -> Box<dyn State> {
        println!("current: Stop");
        self
    }
}

struct Boot {
    state: Option<Box<dyn State>>
}

impl Boot {
    fn new() -> Boot {
        Boot {state: Some(Box::new(Stop {}))}
    }

    fn boot(&mut self) {
        self.state = Some(Box::new(Start {}))
    }

    fn next(&mut self) {
        if let Some(cur) = self.state.take() {
            self.state = Some(cur.on_state_changed())
        }
    }
}

fn main() {
    let mut machine = Boot::new();
    machine.boot();
    machine.next();
    machine.next();
    machine.next();
}
  • 상태 패턴은 내부 상태가 변경될 때 마다 해당 객체의 행동을 변경할 수 있도록 하는 디자인 패턴이다
  • 내부 상태에 따라 다양한 작동을 하는 객체를 만들 때 유용하다

0개의 댓글