After reading Rust book chapter 17

rust

객체지향 언어의 특징

1-1. 데이터와 행위를 정의하는 객체


1-2. 캡슐화


pub struct AveragedCollection {
	list: Vec<i32>,
	average: f64,
}

impl AveragedCollection {
    pub fn add(&mut self, value: i32) {
        self.list.push(value);
        self.update_average();
    }

    pub fn remove(&mut self) -> Option<i32> {
        let result = self.list.pop();
        match result {
            Some(value) => {
                self.update_average();
                Some(value)
            }
            None => None,
        }
    }

    pub fn average(&self) -> f64 {
        self.average
    }

    fn update_average(&mut self) {
        let total: i32 = self.list.iter().sum();
        self.average = total as f64 / self.list.len() as f64;
    }
}


1.3. 타입 시스템으로서의 상속, 코드 공유를 위한 상속


상속을 택하는 이유?

  pub trait Summary {
      fn summarize(&self) -> String {
          String::from("(lRead more...)")
      }
  }

  pub struct NewsArticle {
      pub headline: String,
      pub location: String,
      pub author: String,
      pub content: String,
  }

  impl Summary for NewsArticle {}

  let article = NewsArticle {
      headline: String::from("Penguins win the Stanley Cup Championship!"),
      location: String::from("Pittsburgh, PA, USA"),
      author: String::from("Iceburgh"),
      content: String::from(
          "The Pittsburgh Penguins once again are the best \
           hockey team in the NHL.",
      ),
  };

  println!("New article available! {}", article.summarize());





다른 타입을 허용하는 트레이트 객체

2-1. 트레이트: 공통 행위를 정의

pub trait Draw {
    fn draw(&self);
}

// (1) 트레이트만을 활용한 객체
// : 여러 타입을 저장할 수 있다.
pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

// (2) 제네릭과 트레이트 경계를 활용한 객체
// : 같은 종류의 타입에 대한 컬렉션을 지원한다.
// pub struct Screen<T: Draw> {
//     pub components: Vec<T>
// }

// impl<T> Screen<T>
//     where T: Draw {
//     pub fn run(&self) {
//         for component in self.components.iter() {
//             component.draw();
//         }
//     }
// }


2-2. 트레이트 구현

pub trait Draw {
    fn draw(&self);
}

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>
}

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}


// Button이 아닌 다른 구조체일 경우 다른 필드가 정의될 수 있다.
// 다른 타입이여도 Draw 트레이트를 구현하고 있다.
pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}

impl Draw for Button {
    fn draw(&self) {
        println!("Draw Button");
    }
}
use gui::Draw;

struct SelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        println!("Draw SelectBox")
    }
}

use gui::{Button, Screen};

fn main() {
    let screen = Screen {
        components: vec![
            Box::new(SelectBox {
                width: 75,
                height: 10,
                options: vec![
                    String::from("Yes"),
                    String::from("Maybe"),
                    String::from("No"),
                ],
            }),
            Box::new(Button {
                width: 50,
                height: 10,
                label: String::from("OK"),
            })
        ],
    };


    // (Duck typing과 유사)
    // Screen 구조체의 run 메서드: draw 메서드만 호출하면 되기 때문에 각 컴포넌트의 실제 타입은 상관 없다.
    screen.run();
}


2-3. 동적 호출을 수행하는 트레이트 객체


2-4. 객체 안전성을 요구하는 트레이트 객체



객체지향 디자인 패턴 구현

상태 패턴 (state pattern)

3-1~5. Blog, Post 상태 패턴 구현해보기

// lib.rs
pub struct Post {
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    pub fn new() -> Post {
        Post {
            state: Some(Box::new(Draft {})),
            content: String::new(),
        }
    }

    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }

    pub fn content(&self) -> &str {
        // self.state의 소유권은 필요하지 않으므로
        // state의 참조를 얻기 위해 as_ref()를 호출한다.
        self.state.as_ref().unwrap().content(&self)
    }

    pub fn request_review(&mut self) {
        // 구조체의 필드에 값을 대입하지 않는 것을 허용하지 않기 때문에
        // Some 값일 때에만 그 값의 소유권을 가져온다.
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review())
        }
    }

    pub fn approve(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.approve())
        }
    }
}

trait State {
    // self를 Box<Self> 타입으로 둔 이유
    // : Box<Self> 타입의 소유권을 가져와 상태를 새로운 상태로 변경하기 위해
    fn request_review(self: Box<Self>) -> Box<dyn State>;
    fn approve(self: Box<Self>) -> Box<dyn State>;
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        ""
    }
}

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        // 리뷰를 기다리는 상태로 변경된다.
        Box::new(PendingReview {})
    }

    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }
}

struct PendingReview {}

impl State for PendingReview {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        // 이미 리뷰를 기다리는 상태이기 때문에 self를 반환한다.
        // Post 구조체의 request_review 메서드는 현재 상태값이 어떻든 같은 코드를 동작하고,
        // 상태 변환에 대한 것은 각 상태에 위임한다.
        self
    }

      fn approve(self: Box<Self>) -> Box<dyn State> {
        Box::new(Published {})
    }
}

struct Published {}

impl State for Published {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        self
    }

    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }

    // 왜 수명 어노테이션이 필요할까?
    // post의 참조를 인수로 전달받고, post의 일부를 참조로 반환해야 하므로
    // 반환하는 참조의 수명은 인수로 전달받은 수명과 관련이 있다.
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        &post.content
    }
}
// main.rs
use blog::Post;

fn main() {
    let mut post = Post::new();

    post.add_text("나는 오늘 점심으로 샐러드를 먹었다.");
    assert_eq!("", post.content());

    post.request_review();
    assert_eq!("", post.content());

    post.approve();
    assert_eq!("나는 오늘 점심으로 샐러드를 먹었다.", post.content());
}


3-6. 상태 패턴의 트레이드 오프