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