]>
Commit | Line | Data |
---|---|---|
13cf67c4 XL |
1 | ## Implementing an Object-Oriented Design Pattern |
2 | ||
3 | The *state pattern* is an object-oriented design pattern. The crux of the | |
4 | pattern is that a value has some internal state, which is represented by a set | |
5 | of *state objects*, and the value’s behavior changes based on the internal | |
6 | state. The state objects share functionality: in Rust, of course, we use | |
7 | structs and traits rather than objects and inheritance. Each state object is | |
8 | responsible for its own behavior and for governing when it should change into | |
9 | another state. The value that holds a state object knows nothing about the | |
10 | different behavior of the states or when to transition between states. | |
11 | ||
12 | Using the state pattern means when the business requirements of the program | |
13 | change, we won’t need to change the code of the value holding the state or the | |
14 | code that uses the value. We’ll only need to update the code inside one of the | |
15 | state objects to change its rules or perhaps add more state objects. Let’s look | |
16 | at an example of the state design pattern and how to use it in Rust. | |
17 | ||
18 | We’ll implement a blog post workflow in an incremental way. The blog’s final | |
19 | functionality will look like this: | |
20 | ||
21 | 1. A blog post starts as an empty draft. | |
22 | 2. When the draft is done, a review of the post is requested. | |
23 | 3. When the post is approved, it gets published. | |
24 | 4. Only published blog posts return content to print, so unapproved posts can’t | |
25 | accidentally be published. | |
26 | ||
27 | Any other changes attempted on a post should have no effect. For example, if we | |
28 | try to approve a draft blog post before we’ve requested a review, the post | |
29 | should remain an unpublished draft. | |
30 | ||
31 | Listing 17-11 shows this workflow in code form: this is an example usage of the | |
32 | API we’ll implement in a library crate named `blog`. This won’t compile yet | |
33 | because we haven’t implemented the `blog` crate yet. | |
34 | ||
35 | <span class="filename">Filename: src/main.rs</span> | |
36 | ||
74b04a01 XL |
37 | ```rust,ignore,does_not_compile |
38 | {{#rustdoc_include ../listings/ch17-oop/listing-17-11/src/main.rs:all}} | |
13cf67c4 XL |
39 | ``` |
40 | ||
41 | <span class="caption">Listing 17-11: Code that demonstrates the desired | |
42 | behavior we want our `blog` crate to have</span> | |
43 | ||
44 | We want to allow the user to create a new draft blog post with `Post::new`. | |
45 | Then we want to allow text to be added to the blog post while it’s in the draft | |
46 | state. If we try to get the post’s content immediately, before approval, | |
47 | nothing should happen because the post is still a draft. We’ve added | |
48 | `assert_eq!` in the code for demonstration purposes. An excellent unit test for | |
49 | this would be to assert that a draft blog post returns an empty string from the | |
50 | `content` method, but we’re not going to write tests for this example. | |
51 | ||
52 | Next, we want to enable a request for a review of the post, and we want | |
53 | `content` to return an empty string while waiting for the review. When the post | |
54 | receives approval, it should get published, meaning the text of the post will | |
55 | be returned when `content` is called. | |
56 | ||
57 | Notice that the only type we’re interacting with from the crate is the `Post` | |
58 | type. This type will use the state pattern and will hold a value that will be | |
59 | one of three state objects representing the various states a post can be | |
60 | in—draft, waiting for review, or published. Changing from one state to another | |
61 | will be managed internally within the `Post` type. The states change in | |
62 | response to the methods called by our library’s users on the `Post` instance, | |
63 | but they don’t have to manage the state changes directly. Also, users can’t | |
64 | make a mistake with the states, like publishing a post before it’s reviewed. | |
65 | ||
66 | ### Defining `Post` and Creating a New Instance in the Draft State | |
67 | ||
68 | Let’s get started on the implementation of the library! We know we need a | |
69 | public `Post` struct that holds some content, so we’ll start with the | |
70 | definition of the struct and an associated public `new` function to create an | |
71 | instance of `Post`, as shown in Listing 17-12. We’ll also make a private | |
72 | `State` trait. Then `Post` will hold a trait object of `Box<dyn State>` | |
73 | inside an `Option<T>` in a private field named `state`. You’ll see why the | |
74 | `Option<T>` is necessary in a bit. | |
75 | ||
76 | <span class="filename">Filename: src/lib.rs</span> | |
77 | ||
78 | ```rust | |
74b04a01 | 79 | {{#rustdoc_include ../listings/ch17-oop/listing-17-12/src/lib.rs}} |
13cf67c4 XL |
80 | ``` |
81 | ||
82 | <span class="caption">Listing 17-12: Definition of a `Post` struct and a `new` | |
83 | function that creates a new `Post` instance, a `State` trait, and a `Draft` | |
84 | struct</span> | |
85 | ||
86 | The `State` trait defines the behavior shared by different post states, and the | |
87 | `Draft`, `PendingReview`, and `Published` states will all implement the `State` | |
88 | trait. For now, the trait doesn’t have any methods, and we’ll start by defining | |
89 | just the `Draft` state because that is the state we want a post to start in. | |
90 | ||
91 | When we create a new `Post`, we set its `state` field to a `Some` value that | |
92 | holds a `Box`. This `Box` points to a new instance of the `Draft` struct. This | |
93 | ensures whenever we create a new instance of `Post`, it will start out as a | |
94 | draft. Because the `state` field of `Post` is private, there is no way to | |
95 | create a `Post` in any other state! In the `Post::new` function, we set the | |
96 | `content` field to a new, empty `String`. | |
97 | ||
98 | ### Storing the Text of the Post Content | |
99 | ||
100 | Listing 17-11 showed that we want to be able to call a method named | |
101 | `add_text` and pass it a `&str` that is then added to the text content of the | |
102 | blog post. We implement this as a method rather than exposing the `content` | |
103 | field as `pub`. This means we can implement a method later that will control | |
104 | how the `content` field’s data is read. The `add_text` method is pretty | |
105 | straightforward, so let’s add the implementation in Listing 17-13 to the `impl | |
106 | Post` block: | |
107 | ||
108 | <span class="filename">Filename: src/lib.rs</span> | |
109 | ||
110 | ```rust | |
74b04a01 | 111 | {{#rustdoc_include ../listings/ch17-oop/listing-17-13/src/lib.rs:here}} |
13cf67c4 XL |
112 | ``` |
113 | ||
114 | <span class="caption">Listing 17-13: Implementing the `add_text` method to add | |
115 | text to a post’s `content`</span> | |
116 | ||
117 | The `add_text` method takes a mutable reference to `self`, because we’re | |
118 | changing the `Post` instance that we’re calling `add_text` on. We then call | |
119 | `push_str` on the `String` in `content` and pass the `text` argument to add to | |
120 | the saved `content`. This behavior doesn’t depend on the state the post is in, | |
121 | so it’s not part of the state pattern. The `add_text` method doesn’t interact | |
122 | with the `state` field at all, but it is part of the behavior we want to | |
123 | support. | |
124 | ||
125 | ### Ensuring the Content of a Draft Post Is Empty | |
126 | ||
127 | Even after we’ve called `add_text` and added some content to our post, we still | |
128 | want the `content` method to return an empty string slice because the post is | |
532ac7d7 | 129 | still in the draft state, as shown on line 7 of Listing 17-11. For now, let’s |
13cf67c4 XL |
130 | implement the `content` method with the simplest thing that will fulfill this |
131 | requirement: always returning an empty string slice. We’ll change this later | |
132 | once we implement the ability to change a post’s state so it can be published. | |
133 | So far, posts can only be in the draft state, so the post content should always | |
134 | be empty. Listing 17-14 shows this placeholder implementation: | |
135 | ||
136 | <span class="filename">Filename: src/lib.rs</span> | |
137 | ||
138 | ```rust | |
74b04a01 | 139 | {{#rustdoc_include ../listings/ch17-oop/listing-17-14/src/lib.rs:here}} |
13cf67c4 XL |
140 | ``` |
141 | ||
142 | <span class="caption">Listing 17-14: Adding a placeholder implementation for | |
143 | the `content` method on `Post` that always returns an empty string slice</span> | |
144 | ||
532ac7d7 | 145 | With this added `content` method, everything in Listing 17-11 up to line 7 |
13cf67c4 XL |
146 | works as intended. |
147 | ||
148 | ### Requesting a Review of the Post Changes Its State | |
149 | ||
150 | Next, we need to add functionality to request a review of a post, which should | |
151 | change its state from `Draft` to `PendingReview`. Listing 17-15 shows this code: | |
152 | ||
153 | <span class="filename">Filename: src/lib.rs</span> | |
154 | ||
155 | ```rust | |
74b04a01 | 156 | {{#rustdoc_include ../listings/ch17-oop/listing-17-15/src/lib.rs:here}} |
13cf67c4 XL |
157 | ``` |
158 | ||
159 | <span class="caption">Listing 17-15: Implementing `request_review` methods on | |
160 | `Post` and the `State` trait</span> | |
161 | ||
162 | We give `Post` a public method named `request_review` that will take a mutable | |
163 | reference to `self`. Then we call an internal `request_review` method on the | |
164 | current state of `Post`, and this second `request_review` method consumes the | |
165 | current state and returns a new state. | |
166 | ||
167 | We’ve added the `request_review` method to the `State` trait; all types that | |
168 | implement the trait will now need to implement the `request_review` method. | |
169 | Note that rather than having `self`, `&self`, or `&mut self` as the first | |
170 | parameter of the method, we have `self: Box<Self>`. This syntax means the | |
171 | method is only valid when called on a `Box` holding the type. This syntax takes | |
172 | ownership of `Box<Self>`, invalidating the old state so the state value of the | |
173 | `Post` can transform into a new state. | |
174 | ||
175 | To consume the old state, the `request_review` method needs to take ownership | |
176 | of the state value. This is where the `Option` in the `state` field of `Post` | |
177 | comes in: we call the `take` method to take the `Some` value out of the `state` | |
178 | field and leave a `None` in its place, because Rust doesn’t let us have | |
179 | unpopulated fields in structs. This lets us move the `state` value out of | |
180 | `Post` rather than borrowing it. Then we’ll set the post’s `state` value to the | |
181 | result of this operation. | |
182 | ||
183 | We need to set `state` to `None` temporarily rather than setting it directly | |
184 | with code like `self.state = self.state.request_review();` to get ownership of | |
185 | the `state` value. This ensures `Post` can’t use the old `state` value after | |
186 | we’ve transformed it into a new state. | |
187 | ||
188 | The `request_review` method on `Draft` needs to return a new, boxed instance of | |
189 | a new `PendingReview` struct, which represents the state when a post is waiting | |
190 | for a review. The `PendingReview` struct also implements the `request_review` | |
191 | method but doesn’t do any transformations. Rather, it returns itself, because | |
192 | when we request a review on a post already in the `PendingReview` state, it | |
193 | should stay in the `PendingReview` state. | |
194 | ||
195 | Now we can start seeing the advantages of the state pattern: the | |
196 | `request_review` method on `Post` is the same no matter its `state` value. Each | |
197 | state is responsible for its own rules. | |
198 | ||
199 | We’ll leave the `content` method on `Post` as is, returning an empty string | |
200 | slice. We can now have a `Post` in the `PendingReview` state as well as in the | |
201 | `Draft` state, but we want the same behavior in the `PendingReview` state. | |
532ac7d7 | 202 | Listing 17-11 now works up to line 10! |
13cf67c4 XL |
203 | |
204 | ### Adding the `approve` Method that Changes the Behavior of `content` | |
205 | ||
206 | The `approve` method will be similar to the `request_review` method: it will | |
207 | set `state` to the value that the current state says it should have when that | |
208 | state is approved, as shown in Listing 17-16: | |
209 | ||
210 | <span class="filename">Filename: src/lib.rs</span> | |
211 | ||
212 | ```rust | |
74b04a01 | 213 | {{#rustdoc_include ../listings/ch17-oop/listing-17-16/src/lib.rs:here}} |
13cf67c4 XL |
214 | ``` |
215 | ||
216 | <span class="caption">Listing 17-16: Implementing the `approve` method on | |
217 | `Post` and the `State` trait</span> | |
218 | ||
219 | We add the `approve` method to the `State` trait and add a new struct that | |
220 | implements `State`, the `Published` state. | |
221 | ||
222 | Similar to `request_review`, if we call the `approve` method on a `Draft`, it | |
223 | will have no effect because it will return `self`. When we call `approve` on | |
224 | `PendingReview`, it returns a new, boxed instance of the `Published` struct. | |
225 | The `Published` struct implements the `State` trait, and for both the | |
226 | `request_review` method and the `approve` method, it returns itself, because | |
227 | the post should stay in the `Published` state in those cases. | |
228 | ||
229 | Now we need to update the `content` method on `Post`: if the state is | |
230 | `Published`, we want to return the value in the post’s `content` field; | |
231 | otherwise, we want to return an empty string slice, as shown in Listing 17-17: | |
232 | ||
233 | <span class="filename">Filename: src/lib.rs</span> | |
234 | ||
74b04a01 XL |
235 | ```rust,ignore,does_not_compile |
236 | {{#rustdoc_include ../listings/ch17-oop/listing-17-17/src/lib.rs:here}} | |
13cf67c4 XL |
237 | ``` |
238 | ||
239 | <span class="caption">Listing 17-17: Updating the `content` method on `Post` to | |
240 | delegate to a `content` method on `State`</span> | |
241 | ||
242 | Because the goal is to keep all these rules inside the structs that implement | |
243 | `State`, we call a `content` method on the value in `state` and pass the post | |
244 | instance (that is, `self`) as an argument. Then we return the value that is | |
245 | returned from using the `content` method on the `state` value. | |
246 | ||
247 | We call the `as_ref` method on the `Option` because we want a reference to the | |
248 | value inside the `Option` rather than ownership of the value. Because `state` | |
249 | is an `Option<Box<dyn State>>`, when we call `as_ref`, an `Option<&Box<dyn State>>` is | |
250 | returned. If we didn’t call `as_ref`, we would get an error because we can’t | |
251 | move `state` out of the borrowed `&self` of the function parameter. | |
252 | ||
253 | We then call the `unwrap` method, which we know will never panic, because we | |
254 | know the methods on `Post` ensure that `state` will always contain a `Some` | |
255 | value when those methods are done. This is one of the cases we talked about in | |
9fa01778 XL |
256 | the [“Cases In Which You Have More Information Than the |
257 | Compiler”][more-info-than-rustc]<!-- ignore --> section of Chapter 9 when we | |
258 | know that a `None` value is never possible, even though the compiler isn’t able | |
259 | to understand that. | |
13cf67c4 XL |
260 | |
261 | At this point, when we call `content` on the `&Box<dyn State>`, deref coercion will | |
262 | take effect on the `&` and the `Box` so the `content` method will ultimately be | |
263 | called on the type that implements the `State` trait. That means we need to add | |
264 | `content` to the `State` trait definition, and that is where we’ll put the | |
265 | logic for what content to return depending on which state we have, as shown in | |
266 | Listing 17-18: | |
267 | ||
268 | <span class="filename">Filename: src/lib.rs</span> | |
269 | ||
270 | ```rust | |
74b04a01 | 271 | {{#rustdoc_include ../listings/ch17-oop/listing-17-18/src/lib.rs:here}} |
13cf67c4 XL |
272 | ``` |
273 | ||
274 | <span class="caption">Listing 17-18: Adding the `content` method to the `State` | |
275 | trait</span> | |
276 | ||
277 | We add a default implementation for the `content` method that returns an empty | |
278 | string slice. That means we don’t need to implement `content` on the `Draft` | |
279 | and `PendingReview` structs. The `Published` struct will override the `content` | |
280 | method and return the value in `post.content`. | |
281 | ||
282 | Note that we need lifetime annotations on this method, as we discussed in | |
283 | Chapter 10. We’re taking a reference to a `post` as an argument and returning a | |
284 | reference to part of that `post`, so the lifetime of the returned reference is | |
285 | related to the lifetime of the `post` argument. | |
286 | ||
287 | And we’re done—all of Listing 17-11 now works! We’ve implemented the state | |
288 | pattern with the rules of the blog post workflow. The logic related to the | |
289 | rules lives in the state objects rather than being scattered throughout `Post`. | |
290 | ||
291 | ### Trade-offs of the State Pattern | |
292 | ||
293 | We’ve shown that Rust is capable of implementing the object-oriented state | |
294 | pattern to encapsulate the different kinds of behavior a post should have in | |
295 | each state. The methods on `Post` know nothing about the various behaviors. The | |
296 | way we organized the code, we have to look in only one place to know the | |
297 | different ways a published post can behave: the implementation of the `State` | |
298 | trait on the `Published` struct. | |
299 | ||
300 | If we were to create an alternative implementation that didn’t use the state | |
301 | pattern, we might instead use `match` expressions in the methods on `Post` or | |
302 | even in the `main` code that checks the state of the post and changes behavior | |
303 | in those places. That would mean we would have to look in several places to | |
304 | understand all the implications of a post being in the published state! This | |
305 | would only increase the more states we added: each of those `match` expressions | |
306 | would need another arm. | |
307 | ||
308 | With the state pattern, the `Post` methods and the places we use `Post` don’t | |
309 | need `match` expressions, and to add a new state, we would only need to add a | |
310 | new struct and implement the trait methods on that one struct. | |
311 | ||
312 | The implementation using the state pattern is easy to extend to add more | |
313 | functionality. To see the simplicity of maintaining code that uses the state | |
314 | pattern, try a few of these suggestions: | |
315 | ||
316 | * Add a `reject` method that changes the post’s state from `PendingReview` back | |
317 | to `Draft`. | |
318 | * Require two calls to `approve` before the state can be changed to `Published`. | |
319 | * Allow users to add text content only when a post is in the `Draft` state. | |
320 | Hint: have the state object responsible for what might change about the | |
321 | content but not responsible for modifying the `Post`. | |
322 | ||
323 | One downside of the state pattern is that, because the states implement the | |
324 | transitions between states, some of the states are coupled to each other. If we | |
325 | add another state between `PendingReview` and `Published`, such as `Scheduled`, | |
326 | we would have to change the code in `PendingReview` to transition to | |
327 | `Scheduled` instead. It would be less work if `PendingReview` didn’t need to | |
328 | change with the addition of a new state, but that would mean switching to | |
329 | another design pattern. | |
330 | ||
331 | Another downside is that we’ve duplicated some logic. To eliminate some of the | |
332 | duplication, we might try to make default implementations for the | |
333 | `request_review` and `approve` methods on the `State` trait that return `self`; | |
334 | however, this would violate object safety, because the trait doesn’t know what | |
335 | the concrete `self` will be exactly. We want to be able to use `State` as a | |
336 | trait object, so we need its methods to be object safe. | |
337 | ||
338 | Other duplication includes the similar implementations of the `request_review` | |
339 | and `approve` methods on `Post`. Both methods delegate to the implementation of | |
340 | the same method on the value in the `state` field of `Option` and set the new | |
341 | value of the `state` field to the result. If we had a lot of methods on `Post` | |
342 | that followed this pattern, we might consider defining a macro to eliminate the | |
9fa01778 | 343 | repetition (see the [“Macros”][macros]<!-- ignore --> section in Chapter 19). |
13cf67c4 XL |
344 | |
345 | By implementing the state pattern exactly as it’s defined for object-oriented | |
346 | languages, we’re not taking as full advantage of Rust’s strengths as we could. | |
347 | Let’s look at some changes we can make to the `blog` crate that can make | |
348 | invalid states and transitions into compile time errors. | |
349 | ||
350 | #### Encoding States and Behavior as Types | |
351 | ||
352 | We’ll show you how to rethink the state pattern to get a different set of | |
353 | trade-offs. Rather than encapsulating the states and transitions completely so | |
354 | outside code has no knowledge of them, we’ll encode the states into different | |
355 | types. Consequently, Rust’s type checking system will prevent attempts to use | |
356 | draft posts where only published posts are allowed by issuing a compiler error. | |
357 | ||
358 | Let’s consider the first part of `main` in Listing 17-11: | |
359 | ||
360 | <span class="filename">Filename: src/main.rs</span> | |
361 | ||
362 | ```rust,ignore | |
74b04a01 | 363 | {{#rustdoc_include ../listings/ch17-oop/listing-17-11/src/main.rs:here}} |
13cf67c4 XL |
364 | ``` |
365 | ||
366 | We still enable the creation of new posts in the draft state using `Post::new` | |
367 | and the ability to add text to the post’s content. But instead of having a | |
368 | `content` method on a draft post that returns an empty string, we’ll make it so | |
369 | draft posts don’t have the `content` method at all. That way, if we try to get | |
370 | a draft post’s content, we’ll get a compiler error telling us the method | |
371 | doesn’t exist. As a result, it will be impossible for us to accidentally | |
372 | display draft post content in production, because that code won’t even compile. | |
373 | Listing 17-19 shows the definition of a `Post` struct and a `DraftPost` struct, | |
374 | as well as methods on each: | |
375 | ||
376 | <span class="filename">Filename: src/lib.rs</span> | |
377 | ||
378 | ```rust | |
74b04a01 | 379 | {{#rustdoc_include ../listings/ch17-oop/listing-17-19/src/lib.rs}} |
13cf67c4 XL |
380 | ``` |
381 | ||
382 | <span class="caption">Listing 17-19: A `Post` with a `content` method and a | |
383 | `DraftPost` without a `content` method</span> | |
384 | ||
385 | Both the `Post` and `DraftPost` structs have a private `content` field that | |
386 | stores the blog post text. The structs no longer have the `state` field because | |
387 | we’re moving the encoding of the state to the types of the structs. The `Post` | |
388 | struct will represent a published post, and it has a `content` method that | |
389 | returns the `content`. | |
390 | ||
391 | We still have a `Post::new` function, but instead of returning an instance of | |
392 | `Post`, it returns an instance of `DraftPost`. Because `content` is private | |
393 | and there aren’t any functions that return `Post`, it’s not possible to create | |
394 | an instance of `Post` right now. | |
395 | ||
396 | The `DraftPost` struct has an `add_text` method, so we can add text to | |
397 | `content` as before, but note that `DraftPost` does not have a `content` method | |
398 | defined! So now the program ensures all posts start as draft posts, and draft | |
399 | posts don’t have their content available for display. Any attempt to get around | |
400 | these constraints will result in a compiler error. | |
401 | ||
402 | #### Implementing Transitions as Transformations into Different Types | |
403 | ||
404 | So how do we get a published post? We want to enforce the rule that a draft | |
405 | post has to be reviewed and approved before it can be published. A post in the | |
406 | pending review state should still not display any content. Let’s implement | |
407 | these constraints by adding another struct, `PendingReviewPost`, defining the | |
408 | `request_review` method on `DraftPost` to return a `PendingReviewPost`, and | |
409 | defining an `approve` method on `PendingReviewPost` to return a `Post`, as | |
410 | shown in Listing 17-20: | |
411 | ||
412 | <span class="filename">Filename: src/lib.rs</span> | |
413 | ||
414 | ```rust | |
74b04a01 | 415 | {{#rustdoc_include ../listings/ch17-oop/listing-17-20/src/lib.rs:here}} |
13cf67c4 XL |
416 | ``` |
417 | ||
418 | <span class="caption">Listing 17-20: A `PendingReviewPost` that gets created by | |
419 | calling `request_review` on `DraftPost` and an `approve` method that turns a | |
420 | `PendingReviewPost` into a published `Post`</span> | |
421 | ||
422 | The `request_review` and `approve` methods take ownership of `self`, thus | |
423 | consuming the `DraftPost` and `PendingReviewPost` instances and transforming | |
424 | them into a `PendingReviewPost` and a published `Post`, respectively. This way, | |
425 | we won’t have any lingering `DraftPost` instances after we’ve called | |
426 | `request_review` on them, and so forth. The `PendingReviewPost` struct doesn’t | |
427 | have a `content` method defined on it, so attempting to read its content | |
428 | results in a compiler error, as with `DraftPost`. Because the only way to get a | |
429 | published `Post` instance that does have a `content` method defined is to call | |
430 | the `approve` method on a `PendingReviewPost`, and the only way to get a | |
431 | `PendingReviewPost` is to call the `request_review` method on a `DraftPost`, | |
432 | we’ve now encoded the blog post workflow into the type system. | |
433 | ||
434 | But we also have to make some small changes to `main`. The `request_review` and | |
435 | `approve` methods return new instances rather than modifying the struct they’re | |
436 | called on, so we need to add more `let post =` shadowing assignments to save | |
437 | the returned instances. We also can’t have the assertions about the draft and | |
438 | pending review post’s contents be empty strings, nor do we need them: we can’t | |
439 | compile code that tries to use the content of posts in those states any longer. | |
440 | The updated code in `main` is shown in Listing 17-21: | |
441 | ||
442 | <span class="filename">Filename: src/main.rs</span> | |
443 | ||
444 | ```rust,ignore | |
74b04a01 | 445 | {{#rustdoc_include ../listings/ch17-oop/listing-17-21/src/main.rs}} |
13cf67c4 XL |
446 | ``` |
447 | ||
448 | <span class="caption">Listing 17-21: Modifications to `main` to use the new | |
449 | implementation of the blog post workflow</span> | |
450 | ||
451 | The changes we needed to make to `main` to reassign `post` mean that this | |
452 | implementation doesn’t quite follow the object-oriented state pattern anymore: | |
453 | the transformations between the states are no longer encapsulated entirely | |
454 | within the `Post` implementation. However, our gain is that invalid states are | |
455 | now impossible because of the type system and the type checking that happens at | |
456 | compile time! This ensures that certain bugs, such as display of the content of | |
457 | an unpublished post, will be discovered before they make it to production. | |
458 | ||
459 | Try the tasks suggested for additional requirements that we mentioned at the | |
460 | start of this section on the `blog` crate as it is after Listing 17-20 to see | |
461 | what you think about the design of this version of the code. Note that some of | |
462 | the tasks might be completed already in this design. | |
463 | ||
464 | We’ve seen that even though Rust is capable of implementing object-oriented | |
465 | design patterns, other patterns, such as encoding state into the type system, | |
466 | are also available in Rust. These patterns have different trade-offs. Although | |
467 | you might be very familiar with object-oriented patterns, rethinking the | |
468 | problem to take advantage of Rust’s features can provide benefits, such as | |
469 | preventing some bugs at compile time. Object-oriented patterns won’t always be | |
470 | the best solution in Rust due to certain features, like ownership, that | |
471 | object-oriented languages don’t have. | |
472 | ||
473 | ## Summary | |
474 | ||
475 | No matter whether or not you think Rust is an object-oriented language after | |
476 | reading this chapter, you now know that you can use trait objects to get some | |
477 | object-oriented features in Rust. Dynamic dispatch can give your code some | |
478 | flexibility in exchange for a bit of runtime performance. You can use this | |
479 | flexibility to implement object-oriented patterns that can help your code’s | |
480 | maintainability. Rust also has other features, like ownership, that | |
481 | object-oriented languages don’t have. An object-oriented pattern won’t always | |
482 | be the best way to take advantage of Rust’s strengths, but is an available | |
483 | option. | |
484 | ||
485 | Next, we’ll look at patterns, which are another of Rust’s features that enable | |
486 | lots of flexibility. We’ve looked at them briefly throughout the book but | |
487 | haven’t seen their full capability yet. Let’s go! | |
9fa01778 XL |
488 | |
489 | [more-info-than-rustc]: ch09-03-to-panic-or-not-to-panic.html#cases-in-which-you-have-more-information-than-the-compiler | |
490 | [macros]: ch19-06-macros.html#macros |