]>
Commit | Line | Data |
---|---|---|
cc61c64b XL |
1 | ## What Does Object-Oriented Mean? |
2 | ||
3b2f2976 | 3 | There isn’t consensus in the programming community about the features a |
cc61c64b XL |
4 | language needs to have in order to be called object-oriented. Rust is |
5 | influenced by many different programming paradigms; we explored the features it | |
6 | has that come from functional programming in Chapter 13. Some of the | |
7 | characteristics that object-oriented programming languages tend to share are | |
3b2f2976 | 8 | objects, encapsulation, and inheritance. Let’s take a look at what each of |
cc61c64b XL |
9 | those mean and whether Rust supports them. |
10 | ||
11 | ### Objects Contain Data and Behavior | |
12 | ||
3b2f2976 XL |
13 | The book “Design Patterns: Elements of Reusable Object-Oriented Software,” |
14 | colloquially referred to as “The Gang of Four book,” is a catalog of | |
cc61c64b XL |
15 | object-oriented design patterns. It defines object-oriented programming in this |
16 | way: | |
17 | ||
18 | > Object-oriented programs are made up of objects. An *object* packages both | |
19 | > data and the procedures that operate on that data. The procedures are | |
20 | > typically called *methods* or *operations*. | |
21 | ||
22 | Under this definition, then, Rust is object-oriented: structs and enums have | |
23 | data and `impl` blocks provide methods on structs and enums. Even though | |
3b2f2976 XL |
24 | structs and enums with methods aren’t *called* objects, they provide the same |
25 | functionality that objects do, using the Gang of Four’s definition of objects. | |
cc61c64b XL |
26 | |
27 | ### Encapsulation that Hides Implementation Details | |
28 | ||
29 | Another aspect commonly associated with object-oriented programming is the idea | |
3b2f2976 | 30 | of *encapsulation*: the implementation details of an object aren’t accessible |
cc61c64b XL |
31 | to code using that object. The only way to interact with an object is through |
32 | the public API the object offers; code using the object should not be able to | |
3b2f2976 XL |
33 | reach into the object’s internals and change data or behavior directly. |
34 | Encapsulation enables changing and refactoring an object’s internals without | |
cc61c64b XL |
35 | needing to change the code that uses the object. |
36 | ||
37 | As we discussed in Chapter 7, we can use the `pub` keyword to decide what | |
38 | modules, types, functions, and methods in our code should be public, and by | |
39 | default, everything is private. For example, we can define a struct | |
40 | `AveragedCollection` that has a field containing a vector of `i32` values. The | |
41 | struct can also have a field that knows the average of the values in the vector | |
42 | so that whenever anyone wants to know the average of the values that the struct | |
3b2f2976 | 43 | has in its vector, we don’t have to compute it on-demand. `AveragedCollection` |
cc61c64b XL |
44 | will cache the calculated average for us. Listing 17-1 has the definition of |
45 | the `AveragedCollection` struct: | |
46 | ||
47 | <span class="filename">Filename: src/lib.rs</span> | |
48 | ||
49 | ```rust | |
50 | pub struct AveragedCollection { | |
51 | list: Vec<i32>, | |
52 | average: f64, | |
53 | } | |
54 | ``` | |
55 | ||
56 | <span class="caption">Listing 17-1: An `AveragedCollection` struct that | |
57 | maintains a list of integers and the average of the items in the | |
58 | collection.</span> | |
59 | ||
60 | Note that the struct itself is marked `pub` so that other code may use this | |
61 | struct, but the fields within the struct remain private. This is important in | |
62 | this case because we want to ensure that whenever a value is added or removed | |
63 | from the list, we also update the average. We do this by implementing `add`, | |
64 | `remove`, and `average` methods on the struct as shown in Listing 17-2: | |
65 | ||
66 | <span class="filename">Filename: src/lib.rs</span> | |
67 | ||
68 | ```rust | |
69 | # pub struct AveragedCollection { | |
70 | # list: Vec<i32>, | |
71 | # average: f64, | |
72 | # } | |
73 | impl AveragedCollection { | |
74 | pub fn add(&mut self, value: i32) { | |
75 | self.list.push(value); | |
76 | self.update_average(); | |
77 | } | |
78 | ||
79 | pub fn remove(&mut self) -> Option<i32> { | |
80 | let result = self.list.pop(); | |
81 | match result { | |
82 | Some(value) => { | |
83 | self.update_average(); | |
84 | Some(value) | |
85 | }, | |
86 | None => None, | |
87 | } | |
88 | } | |
89 | ||
90 | pub fn average(&self) -> f64 { | |
91 | self.average | |
92 | } | |
93 | ||
94 | fn update_average(&mut self) { | |
95 | let total: i32 = self.list.iter().sum(); | |
96 | self.average = total as f64 / self.list.len() as f64; | |
97 | } | |
98 | } | |
99 | ``` | |
100 | ||
101 | <span class="caption">Listing 17-2: Implementations of the public methods | |
102 | `add`, `remove`, and `average` on `AveragedCollection`</span> | |
103 | ||
104 | The public methods `add`, `remove`, and `average` are the only way to modify an | |
105 | instance of a `AveragedCollection`. When an item is added to `list` using the | |
106 | `add` method or removed using the `remove` method, the implementations of those | |
107 | methods call the private `update_average` method that takes care of updating | |
108 | the `average` field as well. Because the `list` and `average` fields are | |
3b2f2976 | 109 | private, there’s no way for external code to add or remove items to the `list` |
cc61c64b XL |
110 | field directly, which could cause the `average` field to get out of sync. The |
111 | `average` method returns the value in the `average` field, which allows | |
112 | external code to read the `average` but not modify it. | |
113 | ||
3b2f2976 | 114 | Because we’ve encapsulated the implementation details of `AveragedCollection`, |
cc61c64b XL |
115 | we can easily change aspects like the data structure in the future. For |
116 | instance, we could use a `HashSet` instead of a `Vec` for the `list` field. As | |
117 | long as the signatures of the `add`, `remove`, and `average` public methods | |
3b2f2976 XL |
118 | stay the same, code using `AveragedCollection` wouldn’t need to change. This |
119 | wouldn’t necessarily be the case if we exposed `list` to external code: | |
cc61c64b XL |
120 | `HashSet` and `Vec` have different methods for adding and removing items, so |
121 | the external code would likely have to change if it was modifying `list` | |
122 | directly. | |
123 | ||
124 | If encapsulation is a required aspect for a language to be considered | |
125 | object-oriented, then Rust meets that requirement. Using `pub` or not for | |
126 | different parts of code enables encapsulation of implementation details. | |
127 | ||
128 | ### Inheritance as a Type System and as Code Sharing | |
129 | ||
130 | *Inheritance* is a mechanism that some programming languages provide whereby an | |
3b2f2976 XL |
131 | object can be defined to inherit from another object’s definition, thus gaining |
132 | the parent object’s data and behavior without having to define those again. | |
133 | Inheritance is a characteristic that is part of some people’s definitions of | |
cc61c64b XL |
134 | what an OOP language is. |
135 | ||
136 | If a language must have inheritance to be an object-oriented language, then | |
137 | Rust is not object-oriented. There is not a way to define a struct that | |
3b2f2976 XL |
138 | inherits from another struct in order to gain the parent struct’s fields and |
139 | method implementations. However, if you’re used to having inheritance in your | |
cc61c64b XL |
140 | programming toolbox, there are other solutions in Rust depending on the reason |
141 | you want to use inheritance. | |
142 | ||
143 | There are two main reasons to reach for inheritance. The first is to be able to | |
144 | re-use code: once a particular behavior is implemented for one type, | |
145 | inheritance can enable re-using that implementation for a different type. Rust | |
146 | code can be shared using default trait method implementations instead, which we | |
3b2f2976 | 147 | saw in Listing 10-15 when we added a default implementation of the `summary` |
cc61c64b XL |
148 | method on the `Summarizable` trait. Any type implementing the `Summarizable` |
149 | trait would have the `summary` method available on it without any further code. | |
150 | This is similar to a parent class having an implementation of a method, and a | |
151 | child class inheriting from the parent class also having the implementation of | |
152 | the method due to the inheritance. We can also choose to override the default | |
153 | implementation of the `summary` method when we implement the `Summarizable` | |
154 | trait, which is similar to a child class overriding the implementation of a | |
155 | method inherited from a parent class. | |
156 | ||
157 | The second reason to use inheritance is with the type system: to express that a | |
158 | child type can be used in the same places that the parent type can be used. | |
159 | This is also called *polymorphism*, which means that multiple objects can be | |
160 | substituted for each other at runtime if they have the same shape. | |
161 | ||
162 | <!-- PROD: START BOX --> | |
163 | ||
3b2f2976 XL |
164 | > While many people use “polymorphism” to describe inheritance, it’s actually |
165 | > a specific kind of polymorphism, called “sub-type polymorphism.” There are | |
cc61c64b | 166 | > other forms as well; a generic parameter with a trait bound in Rust is |
3b2f2976 XL |
167 | > also polymorphism, more specifically “parametric polymorphism.” The exact |
168 | > details between the different kinds of polymorphism aren’t crucial here, | |
169 | > so don’t worry too much about the details: just know that Rust has multiple | |
cc61c64b XL |
170 | > polymorphism-related features, unlike many OOP languages. |
171 | ||
172 | <!-- PROD: END BOX --> | |
173 | ||
174 | To support this sort of pattern, Rust has *trait objects* so that we can | |
175 | specify that we would like values of any type, as long as the values implement | |
176 | a particular trait. | |
177 | ||
178 | Inheritance has recently fallen out of favor as a programming design solution | |
179 | in many programming languages. Using inheritance to re-use some code can | |
3b2f2976 | 180 | require more code to be shared than you actually need. Subclasses shouldn’t |
cc61c64b | 181 | always share all characteristics of their parent class, but inheritance means |
3b2f2976 XL |
182 | the subclass gets all of its parent’s data and behavior. This can make a |
183 | program’s design less flexible, and creates the possibility of calling methods | |
184 | on subclasses that don’t make sense or cause errors since the methods don’t | |
cc61c64b XL |
185 | apply to the subclass but must be inherited from the parent class. In addition, |
186 | some languages only allow a subclass to inherit from one class, further | |
3b2f2976 | 187 | restricting the flexibility of a program’s design. |
cc61c64b XL |
188 | |
189 | For these reasons, Rust chose to take a different approach with trait objects | |
3b2f2976 | 190 | instead of inheritance. Let’s take a look at how trait objects enable |
cc61c64b | 191 | polymorphism in Rust. |