]>
Commit | Line | Data |
---|---|---|
83c7162d | 1 | ## Characteristics of Object-Oriented Languages |
cc61c64b | 2 | |
0531ce1d | 3 | There is no consensus in the programming community about what features a |
83c7162d XL |
4 | language must have to be considered object oriented. Rust is influenced by many |
5 | programming paradigms, including OOP; for example, we explored the features | |
6 | that came from functional programming in Chapter 13. Arguably, OOP languages | |
7 | share certain common characteristics, namely objects, encapsulation, and | |
8 | inheritance. Let’s look at what each of those characteristics means and whether | |
9 | Rust supports it. | |
cc61c64b XL |
10 | |
11 | ### Objects Contain Data and Behavior | |
12 | ||
83c7162d XL |
13 | The book *Design Patterns: Elements of Reusable Object-Oriented Software* by |
14 | Enoch Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley | |
15 | Professional, 1994) colloquially referred to as *The Gang of Four* book, is a | |
16 | catalog of object-oriented design patterns. It defines OOP this way: | |
cc61c64b XL |
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 | ||
0531ce1d XL |
22 | Using this definition, Rust is object oriented: structs and enums have data, |
23 | and `impl` blocks provide methods on structs and enums. Even though structs and | |
24 | enums with methods aren’t *called* objects, they provide the same | |
25 | functionality, according to the Gang of Four’s definition of objects. | |
cc61c64b XL |
26 | |
27 | ### Encapsulation that Hides Implementation Details | |
28 | ||
0531ce1d XL |
29 | Another aspect commonly associated with OOP is the idea of *encapsulation*, |
30 | which means that the implementation details of an object aren’t accessible to | |
31 | code using that object. Therefore, the only way to interact with an object is | |
32 | through its public API; code using the object shouldn’t be able to reach into | |
33 | the object’s internals and change data or behavior directly. This enables the | |
34 | programmer to change and refactor an object’s internals without needing to | |
35 | change the code that uses the object. | |
36 | ||
37 | We discussed how to control encapsulation in Chapter 7: we can use the `pub` | |
38 | keyword to decide which modules, types, functions, and methods in our code | |
39 | should be public, and by default everything else is private. For example, we | |
40 | can define a struct `AveragedCollection` that has a field containing a vector | |
41 | of `i32` values. The struct can also have a field that contains the average of | |
42 | the values in the vector, meaning the average doesn’t have to be computed | |
83c7162d | 43 | on demand whenever anyone needs it. In other words, `AveragedCollection` will |
0531ce1d | 44 | cache the calculated average for us. Listing 17-1 has the definition of the |
abe05a73 | 45 | `AveragedCollection` struct: |
cc61c64b XL |
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 | |
0531ce1d | 58 | collection</span> |
cc61c64b | 59 | |
0531ce1d XL |
60 | The struct is marked `pub` so that other code can use it, but the fields within |
61 | the struct remain private. This is important in this case because we want to | |
62 | ensure that whenever a value is added or removed from the list, the average is | |
63 | also updated. We do this by implementing `add`, `remove`, and `average` methods | |
64 | on the struct, as shown in Listing 17-2: | |
cc61c64b XL |
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 | ||
0531ce1d XL |
104 | The public methods `add`, `remove`, and `average` are the only ways to modify |
105 | an instance of `AveragedCollection`. When an item is added to `list` using the | |
abe05a73 | 106 | `add` method or removed using the `remove` method, the implementations of each |
0531ce1d XL |
107 | call the private `update_average` method that handles updating the `average` |
108 | field as well. | |
abe05a73 | 109 | |
0531ce1d XL |
110 | We leave the `list` and `average` fields private so there is no way for |
111 | external code to add or remove items to the `list` field directly; otherwise, | |
abe05a73 XL |
112 | the `average` field might become out of sync when the `list` changes. The |
113 | `average` method returns the value in the `average` field, allowing external | |
114 | code to read the `average` but not modify it. | |
cc61c64b | 115 | |
83c7162d XL |
116 | Because we’ve encapsulated the implementation details of the struct |
117 | `AveragedCollection`, we can easily change aspects, such as the data structure, | |
94b46f34 XL |
118 | in the future. For instance, we could use a `HashSet<i32>` instead of a |
119 | `Vec<i32>` for the `list` field. As long as the signatures of the `add`, | |
120 | `remove`, and `average` public methods stay the same, code using | |
121 | `AveragedCollection` wouldn’t need to change. If we made `list` public instead, | |
122 | this wouldn’t necessarily be the case: `HashSet<i32>` and `Vec<i32>` have | |
123 | different methods for adding and removing items, so the external code would | |
124 | likely have to change if it were modifying `list` directly. | |
cc61c64b | 125 | |
0531ce1d XL |
126 | If encapsulation is a required aspect for a language to be considered object |
127 | oriented, then Rust meets that requirement. The option to use `pub` or not for | |
128 | different parts of code enables encapsulation of implementation details. | |
cc61c64b XL |
129 | |
130 | ### Inheritance as a Type System and as Code Sharing | |
131 | ||
abe05a73 XL |
132 | *Inheritance* is a mechanism whereby an object can inherit from another |
133 | object’s definition, thus gaining the parent object’s data and behavior without | |
134 | you having to define them again. | |
cc61c64b XL |
135 | |
136 | If a language must have inheritance to be an object-oriented language, then | |
83c7162d | 137 | Rust is not one. There is no way to define a struct that inherits the parent |
abe05a73 | 138 | struct’s fields and method implementations. However, if you’re used to having |
83c7162d | 139 | inheritance in your programming toolbox, you can use other solutions in Rust, |
abe05a73 XL |
140 | depending on your reason for reaching for inheritance in the first place. |
141 | ||
0531ce1d XL |
142 | You choose inheritance for two main reasons. One is for reuse of code: you can |
143 | implement particular behavior for one type, and inheritance enables you to | |
144 | reuse that implementation for a different type. You can share Rust code using | |
145 | default trait method implementations instead, which you saw in Listing 10-14 | |
146 | when we added a default implementation of the `summarize` method on the | |
147 | `Summary` trait. Any type implementing the `Summary` trait would have the | |
148 | `summarize` method available on it without any further code. This is similar to | |
149 | a parent class having an implementation of a method and an inheriting child | |
150 | class also having the implementation of the method. We can also override the | |
151 | default implementation of the `summarize` method when we implement the | |
152 | `Summary` trait, which is similar to a child class overriding the | |
153 | implementation of a method inherited from a parent class. | |
154 | ||
155 | The other reason to use inheritance relates to the type system: to enable a | |
abe05a73 | 156 | child type to be used in the same places as the parent type. This is also |
0531ce1d | 157 | called *polymorphism*, which means that you can substitute multiple objects for |
abe05a73 XL |
158 | each other at runtime if they share certain characteristics. |
159 | ||
0531ce1d | 160 | > ### Polymorphism |
abe05a73 XL |
161 | > |
162 | > To many people, polymorphism is synonymous with inheritance. But it’s | |
163 | > actually a more general concept that refers to code that can work with data | |
164 | > of multiple types. For inheritance, those types are generally subclasses. | |
0531ce1d XL |
165 | > |
166 | > Rust instead uses generics to abstract over different possible types and | |
abe05a73 XL |
167 | > trait bounds to impose constraints on what those types must provide. This is |
168 | > sometimes called *bounded parametric polymorphism*. | |
cc61c64b | 169 | |
cc61c64b | 170 | Inheritance has recently fallen out of favor as a programming design solution |
abe05a73 | 171 | in many programming languages because it’s often at risk of sharing more code |
83c7162d | 172 | than necessary. Subclasses shouldn’t always share all characteristics of their |
0531ce1d | 173 | parent class but will do so with inheritance. This can make a program’s design |
83c7162d XL |
174 | less flexible. It also introduces the possibility of calling methods on |
175 | subclasses that don’t make sense or that cause errors because the methods don’t | |
176 | apply to the subclass. In addition, some languages will only allow a subclass | |
177 | to inherit from one class, further restricting the flexibility of a program’s | |
178 | design. | |
0531ce1d XL |
179 | |
180 | For these reasons, Rust takes a different approach, using trait objects instead | |
181 | of inheritance. Let’s look at how trait objects enable polymorphism in Rust. |