빌더 패턴

2001년과 같은 중매



우리는 Cake의 John McCrea가 약간 비꼬고 이상한 꿈을 꾸는 여자를 찾도록 도울 것입니다. 그는 특별한 사람입니다.



네, 여러분과 저는 모두 그렇게 생각합니다. 이것은 Rust 컴파일러의 작업처럼 들립니다. 이 밴드는 정말 시대를 앞서갔습니다. 문제를 모델링해 보겠습니다.

/// Girl type
struct Girl {}

impl Girl {
    /// Construct a Girl
    fn new() -> Self {
        Self {}
    }
}

/// Determine whether given girl matches spec
fn is_dream_girl(girl: &Girl) -> bool {
    // we don't know anything about spec yet, so odds are no
    false
}

fn main() {
    let girl = Girl::new();
    println!("Match: {}", is_dream_girl(&girl));
}

cargo run 와 함께 이것을 실행하면 예상되는 출력이 생성됩니다: Match: false .

그래서, 우리가 구체적으로 무엇을 찾고 있습니까? 운 좋게도, 우리 남자는 선호도에서 바로 시작합니다. 첫 번째 줄에서 그는 "다이아몬드 같은 마음을 가진 여자"를 원한다고 말합니다. 테스트할 멤버 필드를 추가해 보겠습니다.

#[derive(Clone, Copy, PartialEq)]
enum Mind {
    Computer,
    Diamond,
    Garden,
    Scalpel,
    Sieve,
    Unknown,
}

struct Girl {
    mind: Mind,
}

impl Girl {
    fn new(mind: Mind) -> Self {
        Self { mind }
    }
}


이제 테스트 함수는 요청된 변형을 확인할 수 있습니다.

fn is_dream_girl(girl: &Girl) -> bool {
    girl.mind == Mind::Diamond
}

fn main() {
    let girl = Girl::new(Mind::Diamond);
    println!("Match: {}", is_dream_girl(&girl));
}


엄청난! 이제 이 Match: true 를 전달할 때 Girl 를 얻습니다. 잠깐만요. 몇 가지 기준이 더 있습니다. 다음으로 "최고를 아는 소녀"가 필요합니다. 그것은 아주 쉽습니다. 그녀가 하든 하지 않든:

struct Girl {
    mind: Mind,
    knows_best: bool,
}

impl Girl {
    fn new(mind: Mind, knows_best: bool) -> Self {
        Self { mind, knows_best }
    }
}

fn is_dream_girl(girl: &Girl) -> bool {
    girl.mind == Mind::Diamond && girl.knows_best
}


매개변수 목록에 추가하기만 하면 됩니다.

fn main() {
    let girl = Girl::new(Mind::Diamond, true);
    println!("Match: {}", is_dream_girl(&girl));
}


그루비. 이제 우리는 "잘라진 신발과 담배처럼 타는 눈"이 필요합니다. 몇 가지 문자열 쌍을 연결해야 할 것 같습니다.

type Attribute = (String, String);

/// Girl type
struct Girl {
    items: Vec<Attribute>,
    mind: Mind,
    knows_best: bool,
}


속성은 ("shoes", "cut") 와 같은 튜플입니다. 생성자에서 신발과 눈 속성을 요청할 수 있습니다.

impl Girl {
    fn new(mind: Mind, knows_best: bool, shoes: &str, eyes: &str) -> Self {
        let mut ret = Self {
            items: Vec::new(),
            mind,
            knows_best,
        };
        ret.push_item("shoes", shoes);
        ret.push_item("eyes", eyes);
        ret
    }

    fn push_item(&mut self, item_name: &str, attribute: &str) {
        self.items.push((item_name.into(), attribute.into()));
    }
}


우리는 우리가 원하는 것을 얻기 위해 항목을 확인할 것입니다:

fn is_dream_girl(girl: &Girl) -> bool {
    let mut found_shoes = false;
    let mut found_eyes = false;
    for item in &girl.items {
        if item.0 == "shoes" && item.1 == "cut" {
            found_shoes = true;
        } else if item.0 == "eyes" && item.1 == "burn like cigarettes" {
            found_eyes = true;
        }
    }
    girl.mind == Mind::Diamond && girl.knows_best && found_shoes && found_eyes
}


대박! 새 속성으로 Girl를 구성하기만 하면 됩니다.

fn main() {
    let girl = Girl::new(Mind::Diamond, true, "cut", "burn like cigarettes");
    println!("Match: {}", is_dream_girl(&girl));
}


괜찮아. 기다리다. 여기서 문제가 보이나요? 미리 훑어보자...

I want a girl with the right allocations
Who's fast and thorough
And sharp as a tack
She's playing with her jewelry
She's putting up her hair
She's touring the facility
And picking up slack
...


그것은 거기에서 계속됩니다! 이 Girl 생성자는 이미 손을 떼고 있으며 첫 번째 스탠자에서 겨우 겨우 만들었습니다. 존이 마음을 바꾼다면? 그는 무언가가 그다지 중요하지 않다고 판단하거나 새로운 기준을 추가할 수 있습니다. 이 코드는 그런 변경을 할 수 없습니다. 모든 호출 사이트는 이 정확한 순서로 제공된 이 정확한 매개변수 목록에 의존하지만 사람들은 그렇게 작동하지 않습니다. 모든 종류의 변형이 있을 수 있습니다.

패턴



빌더 패턴을 활용하여 이 프로그램을 다시 구현해 보겠습니다. Girl가 처음 생성될 때, 우리는 단지 몇 가지 합리적인 기본값으로 시작하기를 원합니다:

struct Girl {
    items: Vec<Attribute>,
    mind: Mind,
    knows_best: bool,
}

impl Girl {
    fn new() -> Self {
        Self::default()
    }
}

impl Default for Girl {
    fn default() -> Self {
        Self { mind: Mind::Unknown, knows_best: false, items: Vec::new() }
    }
}


다른 모든 것은 백지입니다. 이런 식으로 매개변수 없이 Girl::new()를 사용하고 시작점을 얻을 수 있습니다. 더 추가하려면 메서드를 정의할 수 있습니다.

impl Girl {
    // ..

    fn set_mind(&mut self, mind: Mind) -> &mut Self {
        self.mind = mind;
        self
    }
}


이 메서드는 변경 가능한 참조를 가져와 하나를 반환하므로 먼저 구성하고 나중에 조정할 수 있습니다.

fn main() {
    let mut girl = Girl::new();
    girl.set_mind(Mind::Diamond);
    println!("Match: {}", is_dream_girl(&girl));
}


나머지를 추가해 보겠습니다.

impl Girl {
    fn push_item(&mut self, item_name: &str, attribute: &str) {
        self.items.push((item_name.into(), attribute.into()));
    }

    fn toggle_knows_best(&mut self) -> &mut Self {
        self.knows_best = !self.knows_best;
        self
    }
}


이제 우리는 생성 당시에 무엇을 알고 있었는지에 상관없이 한 번에 하나씩 추가할 수 있습니다.

fn main() {
    let mut girl = Girl::new();
    girl.set_mind(Mind::Diamond);
    girl.toggle_knows_best();
    girl.push_item("shoes", "cut");
    girl.push_item("eyes", "burn like cigarettes");
    println!("Match: {}", is_dream_girl(&girl));
}


사양이 증가하고 발전함에 따라 작업하기가 훨씬 쉽습니다.

더 복잡한 시나리오에서는 GirlBuilder와 같은 별도의 유형을 사용하고 각 단계에서 소유권을 가져와야 할 수 있습니다. 이렇게 하면 한 줄로 이 모든 작업을 수행할 수 있습니다. let girl = GirlBuilder::new().set_mind(Mind::Diamond).toggle_knows_best(); 이렇게 하면 구성 옵션이 제한됩니다. 예를 들어 if 표현식에서 일부 빌더 메서드를 조건부로 호출하려는 경우입니다. 가능하다면 비소유 패턴이 더 유연합니다.

이 시간이 지난 후 McCrea 씨가 마침내 안정을 취하도록 도울 수 있기를 바랍니다.

도전



이 노래가 '올디'가 될 지금부터 5년을 준비하라.

좋은 웹페이지 즐겨찾기