Implementando um Padrão de Design Orientado a Objetos

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Bem-vindo a Implementando um Padrão de Design Orientado a Objetos. Este laboratório faz parte do Livro Rust. Você pode praticar suas habilidades em Rust no LabEx.

Neste laboratório, implementaremos o padrão de estado (state pattern) em um design orientado a objetos para criar uma struct de postagem de blog que transita por diferentes estados (rascunho, revisão e publicado) com base em seu comportamento, garantindo que apenas as postagens de blog publicadas possam retornar conteúdo.

Implementando um Padrão de Design Orientado a Objetos

O padrão de estado (state pattern) é um padrão de design orientado a objetos. O cerne do padrão é que definimos um conjunto de estados que um valor pode ter internamente. Os estados são representados por um conjunto de objetos de estado (state objects), e o comportamento do valor muda com base em seu estado. Vamos trabalhar em um exemplo de uma struct de postagem de blog que possui um campo para manter seu estado, que será um objeto de estado do conjunto "rascunho", "revisão" ou "publicado".

Os objetos de estado compartilham funcionalidade: em Rust, é claro, usamos structs e traits em vez de objetos e herança. Cada objeto de estado é responsável por seu próprio comportamento e por governar quando deve mudar para outro estado. O valor que contém um objeto de estado não sabe nada sobre o comportamento diferente dos estados ou quando fazer a transição entre os estados.

A vantagem de usar o padrão de estado é que, quando os requisitos de negócios do programa mudam, não precisaremos alterar o código do valor que contém o estado ou o código que usa o valor. Só precisaremos atualizar o código dentro de um dos objetos de estado para alterar suas regras ou talvez adicionar mais objetos de estado.

Primeiro, vamos implementar o padrão de estado de uma maneira mais tradicional orientada a objetos, então usaremos uma abordagem que é um pouco mais natural em Rust. Vamos mergulhar na implementação incremental de um fluxo de trabalho de postagem de blog usando o padrão de estado.

A funcionalidade final terá a seguinte aparência:

  1. Uma postagem de blog começa como um rascunho vazio.
  2. Quando o rascunho é concluído, uma revisão da postagem é solicitada.
  3. Quando a postagem é aprovada, ela é publicada.
  4. Apenas as postagens de blog publicadas retornam conteúdo para impressão, para que as postagens não aprovadas não possam ser publicadas acidentalmente.

Quaisquer outras alterações tentadas em uma postagem não devem ter efeito. Por exemplo, se tentarmos aprovar um rascunho de postagem de blog antes de solicitar uma revisão, a postagem deverá permanecer um rascunho não publicado.

A Listagem 17-11 mostra este fluxo de trabalho em forma de código: este é um exemplo de uso da API que implementaremos em uma crate de biblioteca chamada blog. Isso ainda não compilará porque ainda não implementamos a crate blog.

Nome do arquivo: src/main.rs

use blog::Post;

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

  2 post.add_text("I ate a salad for lunch today");
  3 assert_eq!("", post.content());

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

  6 post.approve();
  7 assert_eq!("I ate a salad for lunch today", post.content());
}

Listagem 17-11: Código que demonstra o comportamento desejado que queremos que nossa crate blog tenha

Queremos permitir que o usuário crie uma nova postagem de blog de rascunho com Post::new [1]. Queremos permitir que o texto seja adicionado à postagem do blog [2]. Se tentarmos obter o conteúdo da postagem imediatamente, antes da aprovação, não devemos obter nenhum texto porque a postagem ainda é um rascunho. Adicionamos assert_eq! no código para fins de demonstração [3]. Um excelente teste de unidade para isso seria afirmar que uma postagem de blog de rascunho retorna uma string vazia do método content, mas não vamos escrever testes para este exemplo.

Em seguida, queremos habilitar uma solicitação de revisão da postagem [4], e queremos que content retorne uma string vazia enquanto aguarda a revisão [5]. Quando a postagem recebe aprovação [6], ela deve ser publicada, o que significa que o texto da postagem será retornado quando content for chamado [7].

Observe que o único tipo com o qual estamos interagindo da crate é o tipo Post. Este tipo usará o padrão de estado e conterá um valor que será um de três objetos de estado representando os vários estados em que uma postagem pode estar --- rascunho, revisão ou publicado. A mudança de um estado para outro será gerenciada internamente dentro do tipo Post. Os estados mudam em resposta aos métodos chamados pelos usuários de nossa biblioteca na instância Post, mas eles não precisam gerenciar as mudanças de estado diretamente. Além disso, os usuários não podem cometer um erro com os estados, como publicar uma postagem antes que ela seja revisada.

Definindo Post e Criando uma Nova Instância no Estado de Rascunho

Vamos começar a implementação da biblioteca! Sabemos que precisamos de uma struct Post pública que contenha algum conteúdo, então começaremos com a definição da struct e uma função new pública associada para criar uma instância de Post, conforme mostrado na Listagem 17-12. Também criaremos um trait State privado que definirá o comportamento que todos os objetos de estado para um Post devem ter.

Então, Post conterá um objeto trait de Box<dyn State> dentro de um Option<T> em um campo privado chamado state para conter o objeto de estado. Você verá por que o Option<T> é necessário em breve.

Nome do arquivo: src/lib.rs

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

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

trait State {}

struct Draft {}

impl State for Draft {}

Listagem 17-12: Definição de uma struct Post e uma função new que cria uma nova instância de Post, um trait State e uma struct Draft

O trait State define o comportamento compartilhado pelos diferentes estados da postagem. Os objetos de estado são Draft, PendingReview e Published, e todos eles implementarão o trait State. Por enquanto, o trait não possui nenhum método, e começaremos definindo apenas o estado Draft porque esse é o estado em que queremos que uma postagem comece.

Quando criamos um novo Post, definimos seu campo state como um valor Some que contém um Box [1]. Este Box aponta para uma nova instância da struct Draft. Isso garante que sempre que criarmos uma nova instância de Post, ela começará como um rascunho. Como o campo state de Post é privado, não há como criar um Post em nenhum outro estado! Na função Post::new, definimos o campo content como uma nova String vazia [2].

Armazenando o Texto do Conteúdo da Postagem

Vimos na Listagem 17-11 que queremos ser capazes de chamar um método chamado add_text e passar a ele um &str que é então adicionado como o conteúdo de texto da postagem do blog. Implementamos isso como um método, em vez de expor o campo content como pub, para que, mais tarde, possamos implementar um método que controlará como os dados do campo content são lidos. O método add_text é bastante simples, então vamos adicionar a implementação na Listagem 17-13 ao bloco impl Post.

Nome do arquivo: src/lib.rs

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

Listagem 17-13: Implementando o método add_text para adicionar texto ao content de uma postagem

O método add_text recebe uma referência mutável a self porque estamos alterando a instância Post na qual estamos chamando add_text. Em seguida, chamamos push_str na String em content e passamos o argumento text para adicionar ao content salvo. Este comportamento não depende do estado em que a postagem está, portanto, não faz parte do padrão de estado. O método add_text não interage com o campo state em absoluto, mas faz parte do comportamento que queremos suportar.

Garantindo que o Conteúdo de uma Postagem em Rascunho esteja Vazio

Mesmo depois de chamarmos add_text e adicionarmos algum conteúdo à nossa postagem, ainda queremos que o método content retorne uma fatia de string vazia porque a postagem ainda está no estado de rascunho, conforme mostrado em [3] na Listagem 17-11. Por enquanto, vamos implementar o método content com a coisa mais simples que atenderá a este requisito: sempre retornando uma fatia de string vazia. Mudaremos isso mais tarde, uma vez que implementarmos a capacidade de alterar o estado de uma postagem para que ela possa ser publicada. Até agora, as postagens só podem estar no estado de rascunho, então o conteúdo da postagem deve sempre estar vazio. A Listagem 17-14 mostra esta implementação de espaço reservado.

Nome do arquivo: src/lib.rs

impl Post {
    --snip--
    pub fn content(&self) -> &str {
        ""
    }
}

Listagem 17-14: Adicionando uma implementação de espaço reservado para o método content em Post que sempre retorna uma fatia de string vazia

Com este método content adicionado, tudo na Listagem 17-11 até a linha em [3] funciona como pretendido.

Solicitar uma Revisão Altera o Estado da Postagem

Em seguida, precisamos adicionar funcionalidade para solicitar uma revisão de uma postagem, o que deve alterar seu estado de Draft para PendingReview. A Listagem 17-15 mostra este código.

Nome do arquivo: src/lib.rs

impl Post {
    --snip--
  1 pub fn request_review(&mut self) {
      2 if let Some(s) = self.state.take() {
          3 self.state = Some(s.request_review())
        }
    }
}

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

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
      5 Box::new(PendingReview {})
    }
}

struct PendingReview {}

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

Listagem 17-15: Implementando os métodos request_review em Post e o trait State

Damos a Post um método público chamado request_review que receberá uma referência mutável a self [1]. Em seguida, chamamos um método interno request_review no estado atual de Post [3], e este segundo método request_review consome o estado atual e retorna um novo estado.

Adicionamos o método request_review ao trait State [4]; todos os tipos que implementam o trait agora precisarão implementar o método request_review. Observe que, em vez de ter self, &self ou &mut self como o primeiro parâmetro do método, temos self: Box<Self>. Esta sintaxe significa que o método é válido apenas quando chamado em um Box que contém o tipo. Esta sintaxe assume a propriedade de Box<Self>, invalidando o estado antigo para que o valor do estado de Post possa se transformar em um novo estado.

Para consumir o estado antigo, o método request_review precisa assumir a propriedade do valor do estado. É aqui que o Option no campo state de Post entra em ação: chamamos o método take para tirar o valor Some do campo state e deixar um None em seu lugar porque o Rust não nos permite ter campos não preenchidos em structs [2]. Isso nos permite mover o valor state de Post em vez de emprestá-lo. Em seguida, definiremos o valor state da postagem para o resultado desta operação.

Precisamos definir state temporariamente como None em vez de defini-lo diretamente com código como self.state = self.state.request_review(); para obter a propriedade do valor state. Isso garante que Post não possa usar o valor state antigo depois de transformá-lo em um novo estado.

O método request_review em Draft retorna uma nova instância, em caixa, de uma nova struct PendingReview [5], que representa o estado quando uma postagem está aguardando uma revisão. A struct PendingReview também implementa o método request_review, mas não faz nenhuma transformação. Em vez disso, ele retorna a si mesmo [6] porque, quando solicitamos uma revisão em uma postagem já no estado PendingReview, ela deve permanecer no estado PendingReview.

Agora podemos começar a ver as vantagens do padrão de estado: o método request_review em Post é o mesmo, independentemente do seu valor state. Cada estado é responsável por suas próprias regras.

Deixaremos o método content em Post como está, retornando uma fatia de string vazia. Agora podemos ter um Post no estado PendingReview, bem como no estado Draft, mas queremos o mesmo comportamento no estado PendingReview. A Listagem 17-11 agora funciona até a linha em [5]!

Adicionando approve para Alterar o Comportamento de content

O método approve será semelhante ao método request_review: ele definirá state para o valor que o estado atual diz que ele deve ter quando esse estado for aprovado, conforme mostrado na Listagem 17-16.

Nome do arquivo: src/lib.rs

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

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

struct Draft {}

impl State for Draft {
    --snip--
    fn approve(self: Box<Self>) -> Box<dyn State> {
      1 self
    }
}

struct PendingReview {}

impl State for PendingReview {
    --snip--
    fn approve(self: Box<Self>) -> Box<dyn State> {
      2 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
    }
}

Listagem 17-16: Implementando o método approve em Post e o trait State

Adicionamos o método approve ao trait State e adicionamos uma nova struct que implementa State, o estado Published.

Semelhante à forma como request_review em PendingReview funciona, se chamarmos o método approve em um Draft, ele não terá efeito porque approve retornará self [1]. Quando chamamos approve em PendingReview, ele retorna uma nova instância, em caixa, da struct Published [2]. A struct Published implementa o trait State, e para os métodos request_review e approve, ele retorna a si mesmo porque a postagem deve permanecer no estado Published nesses casos.

Agora precisamos atualizar o método content em Post. Queremos que o valor retornado de content dependa do estado atual de Post, então vamos fazer com que Post delegue a um método content definido em seu state, conforme mostrado na Listagem 17-17.

Nome do arquivo: src/lib.rs

impl Post {
    --snip--
    pub fn content(&self) -> &str {
        self.state.as_ref().unwrap().content(self)
    }
    --snip--
}

Listagem 17-17: Atualizando o método content em Post para delegar a um método content em State

Como o objetivo é manter todas essas regras dentro das structs que implementam State, chamamos um método content no valor em state e passamos a instância da postagem (ou seja, self) como um argumento. Em seguida, retornamos o valor que é retornado ao usar o método content no valor state.

Chamamos o método as_ref em Option porque queremos uma referência ao valor dentro de Option em vez da propriedade do valor. Como state é um Option<Box<dyn State>>, quando chamamos as_ref, um Option<&Box<dyn State>> é retornado. Se não chamássemos as_ref, receberíamos um erro porque não podemos mover state de &self emprestado do parâmetro da função.

Em seguida, chamamos o método unwrap, que sabemos que nunca entrará em pânico porque sabemos que os métodos em Post garantem que state sempre conterá um valor Some quando esses métodos forem concluídos. Este é um dos casos sobre os quais falamos em "Casos em que você tem mais informações do que o compilador" quando sabemos que um valor None nunca é possível, embora o compilador não consiga entender isso.

Neste ponto, quando chamamos content em &Box<dyn State>, a coerção de deref entrará em vigor no & e no Box, para que o método content seja, em última análise, chamado no tipo que implementa o trait State. Isso significa que precisamos adicionar content à definição do trait State, e é aí que colocaremos a lógica para qual conteúdo retornar dependendo de qual estado temos, conforme mostrado na Listagem 17-18.

Nome do arquivo: src/lib.rs

trait State {
    --snip--
    fn content<'a>(&self, post: &'a Post) -> &'a str {
      1 ""
    }
}

--snip--
struct Published {}

impl State for Published {
    --snip--
    fn content<'a>(&self, post: &'a Post) -> &'a str {
      2 &post.content
    }
}

Listagem 17-18: Adicionando o método content ao trait State

Adicionamos uma implementação padrão para o método content que retorna uma fatia de string vazia [1]. Isso significa que não precisamos implementar content nas structs Draft e PendingReview. A struct Published substituirá o método content e retornará o valor em post.content [2].

Observe que precisamos de anotações de tempo de vida neste método, como discutimos no Capítulo 10. Estamos pegando uma referência a um post como um argumento e retornando uma referência a parte desse post, então o tempo de vida da referência retornada está relacionado ao tempo de vida do argumento post.

E terminamos---tudo na Listagem 17-11 agora funciona! Implementamos o padrão de estado com as regras do fluxo de trabalho da postagem do blog. A lógica relacionada às regras reside nos objetos de estado em vez de ser espalhada por todo o Post.

Por que não um Enum?

Você pode estar se perguntando por que não usamos um enum com os diferentes estados possíveis da postagem como variantes. Essa é certamente uma solução possível; experimente e compare os resultados finais para ver qual você prefere! Uma desvantagem de usar um enum é que todo lugar que verifica o valor do enum precisará de uma expressão match ou semelhante para lidar com todas as variantes possíveis. Isso pode ficar mais repetitivo do que esta solução de objeto de trait.

Prós e Contras do Padrão de Estado

Mostramos que o Rust é capaz de implementar o padrão de estado orientado a objetos para encapsular os diferentes tipos de comportamento que uma postagem deve ter em cada estado. Os métodos em Post não sabem nada sobre os vários comportamentos. Da maneira como organizamos o código, precisamos olhar apenas em um lugar para conhecer as diferentes maneiras como uma postagem publicada pode se comportar: a implementação do trait State na struct Published.

Se fôssemos criar uma implementação alternativa que não usasse o padrão de estado, poderíamos, em vez disso, usar expressões match nos métodos em Post ou mesmo no código main que verifica o estado da postagem e altera o comportamento nesses lugares. Isso significaria que teríamos que olhar em vários lugares para entender todas as implicações de uma postagem estar no estado publicado! Isso só aumentaria quanto mais estados adicionássemos: cada uma dessas expressões match precisaria de outro braço.

Com o padrão de estado, os métodos Post e os lugares onde usamos Post não precisam de expressões match, e para adicionar um novo estado, só precisaríamos adicionar uma nova struct e implementar os métodos de trait nessa struct.

A implementação usando o padrão de estado é fácil de estender para adicionar mais funcionalidades. Para ver a simplicidade de manter o código que usa o padrão de estado, experimente algumas dessas sugestões:

  • Adicione um método reject que altere o estado da postagem de PendingReview de volta para Draft.
  • Exija duas chamadas para approve antes que o estado possa ser alterado para Published.
  • Permita que os usuários adicionem conteúdo de texto somente quando uma postagem estiver no estado Draft. Dica: faça com que o objeto de estado seja responsável pelo que pode mudar sobre o conteúdo, mas não seja responsável por modificar o Post.

Uma desvantagem do padrão de estado é que, como os estados implementam as transições entre os estados, alguns dos estados são acoplados uns aos outros. Se adicionarmos outro estado entre PendingReview e Published, como Scheduled, teríamos que alterar o código em PendingReview para fazer a transição para Scheduled em vez disso. Seria menos trabalho se PendingReview não precisasse mudar com a adição de um novo estado, mas isso significaria mudar para outro padrão de design.

Outra desvantagem é que duplicamos alguma lógica. Para eliminar parte da duplicação, podemos tentar fazer implementações padrão para os métodos request_review e approve no trait State que retornam self. No entanto, isso não funcionaria: ao usar State como um objeto de trait, o trait não sabe qual será o self concreto exatamente, então o tipo de retorno não é conhecido no tempo de compilação.

Outra duplicação inclui as implementações semelhantes dos métodos request_review e approve em Post. Ambos os métodos delegam à implementação do mesmo método no valor no campo state de Option e definem o novo valor do campo state para o resultado. Se tivéssemos muitos métodos em Post que seguissem esse padrão, poderíamos considerar a definição de uma macro para eliminar a repetição (consulte "Macros").

Ao implementar o padrão de estado exatamente como ele é definido para linguagens orientadas a objetos, não estamos aproveitando totalmente os pontos fortes do Rust como poderíamos. Vamos analisar algumas alterações que podemos fazer no crate blog que podem transformar estados e transições inválidos em erros de tempo de compilação.

Codificando Estados e Comportamento como Tipos

Mostraremos como repensar o padrão de estado para obter um conjunto diferente de compensações. Em vez de encapsular os estados e transições completamente para que o código externo não tenha conhecimento deles, codificaremos os estados em tipos diferentes. Consequentemente,, o sistema de verificação de tipos do Rust impedirá tentativas de usar postagens de rascunho onde apenas postagens publicadas são permitidas, emitindo um erro do compilador.

Vamos considerar a primeira parte de main na Listagem 17-11:

Nome do arquivo: src/main.rs

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

    post.add_text("I ate a salad for lunch today");
    assert_eq!("", post.content());
}

Ainda permitimos a criação de novas postagens no estado de rascunho usando Post::new e a capacidade de adicionar texto ao conteúdo da postagem. Mas, em vez de ter um método content em uma postagem de rascunho que retorna uma string vazia, faremos com que as postagens de rascunho não tenham o método content de forma alguma. Dessa forma, se tentarmos obter o conteúdo de uma postagem de rascunho, receberemos um erro do compilador informando que o método não existe. Como resultado, será impossível para nós exibir acidentalmente o conteúdo da postagem de rascunho em produção, porque esse código nem mesmo compilará. A Listagem 17-19 mostra a definição de uma struct Post e uma struct DraftPost, bem como métodos em cada uma.

Nome do arquivo: src/lib.rs

pub struct Post {
    content: String,
}

pub struct DraftPost {
    content: String,
}

impl Post {
  1 pub fn new() -> DraftPost {
        DraftPost {
            content: String::new(),
        }
    }

  2 pub fn content(&self) -> &str {
        &self.content
    }
}

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

Listagem 17-19: Um Post com um método content e um DraftPost sem um método content

Tanto as structs Post quanto DraftPost têm um campo content privado que armazena o texto da postagem do blog. As structs não têm mais o campo state porque estamos movendo a codificação do estado para os tipos das structs. A struct Post representará uma postagem publicada e tem um método content que retorna o content [2].

Ainda temos uma função Post::new, mas em vez de retornar uma instância de Post, ela retorna uma instância de DraftPost [1]. Como content é privado e não há funções que retornem Post, não é possível criar uma instância de Post no momento.

A struct DraftPost tem um método add_text, então podemos adicionar texto a content como antes [3], mas observe que DraftPost não tem um método content definido! Portanto, agora o programa garante que todas as postagens comecem como postagens de rascunho, e as postagens de rascunho não têm seu conteúdo disponível para exibição. Qualquer tentativa de contornar essas restrições resultará em um erro do compilador.

Implementando Transições como Transformações em Tipos Diferentes

Então, como obtemos uma postagem publicada? Queremos impor a regra de que uma postagem de rascunho precisa ser revisada e aprovada antes que possa ser publicada. Uma postagem no estado de revisão pendente ainda não deve exibir nenhum conteúdo. Vamos implementar essas restrições adicionando outra struct, PendingReviewPost, definindo o método request_review em DraftPost para retornar um PendingReviewPost e definindo um método approve em PendingReviewPost para retornar um Post, conforme mostrado na Listagem 17-20.

Nome do arquivo: src/lib.rs

impl DraftPost {
    --snip--
    pub fn request_review(self) -> PendingReviewPost {
        PendingReviewPost {
            content: self.content,
        }
    }
}

pub struct PendingReviewPost {
    content: String,
}

impl PendingReviewPost {
    pub fn approve(self) -> Post {
        Post {
            content: self.content,
        }
    }
}

Listagem 17-20: Um PendingReviewPost que é criado chamando request_review em DraftPost e um método approve que transforma um PendingReviewPost em um Post publicado

Os métodos request_review e approve assumem a propriedade de self, consumindo assim as instâncias DraftPost e PendingReviewPost e transformando-as em um PendingReviewPost e um Post publicado, respectivamente. Dessa forma, não teremos nenhuma instância DraftPost persistente depois de chamarmos request_review nelas, e assim por diante. A struct PendingReviewPost não tem um método content definido, portanto, tentar ler seu conteúdo resulta em um erro do compilador, como com DraftPost. Como a única maneira de obter uma instância Post publicada que tenha um método content definido é chamar o método approve em um PendingReviewPost, e a única maneira de obter um PendingReviewPost é chamar o método request_review em um DraftPost, agora codificamos o fluxo de trabalho da postagem do blog no sistema de tipos.

Mas também precisamos fazer algumas pequenas alterações em main. Os métodos request_review e approve retornam novas instâncias em vez de modificar a struct em que são chamados, então precisamos adicionar mais atribuições de sombreamento let post = para salvar as instâncias retornadas. Também não podemos ter as asserções sobre os conteúdos das postagens de rascunho e revisão pendente sendo strings vazias, nem precisamos delas: não podemos compilar código que tenta usar o conteúdo de postagens nesses estados. O código atualizado em main é mostrado na Listagem 17-21.

Nome do arquivo: src/main.rs

use blog::Post;

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

    post.add_text("I ate a salad for lunch today");

    let post = post.request_review();

    let post = post.approve();

    assert_eq!("I ate a salad for lunch today", post.content());
}

Listagem 17-21: Modificações em main para usar a nova implementação do fluxo de trabalho da postagem do blog

As alterações que precisávamos fazer em main para reatribuir post significam que essa implementação não segue mais o padrão de estado orientado a objetos: as transformações entre os estados não são mais encapsuladas inteiramente dentro da implementação Post. No entanto, nosso ganho é que os estados inválidos agora são impossíveis por causa do sistema de tipos e da verificação de tipos que acontece no tempo de compilação! Isso garante que certos bugs, como a exibição do conteúdo de uma postagem não publicada, sejam descobertos antes de chegarem à produção.

Experimente as tarefas sugeridas no início desta seção no crate blog como está após a Listagem 17-21 para ver o que você pensa sobre o design desta versão do código. Observe que algumas das tarefas podem já estar concluídas neste design.

Vimos que, embora o Rust seja capaz de implementar padrões de design orientados a objetos, outros padrões, como a codificação de estado no sistema de tipos, também estão disponíveis no Rust. Esses padrões têm diferentes compensações. Embora você possa estar muito familiarizado com padrões orientados a objetos, repensar o problema para aproveitar os recursos do Rust pode fornecer benefícios, como evitar alguns bugs no tempo de compilação. Os padrões orientados a objetos nem sempre serão a melhor solução no Rust devido a certos recursos, como propriedade, que as linguagens orientadas a objetos não possuem.

Resumo

Parabéns! Você concluiu o laboratório Implementando um Padrão de Design Orientado a Objetos. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.