feat: added cleanscript
This commit is contained in:
165
ch18/ch18-01-what-is-oo.html
Normal file
165
ch18/ch18-01-what-is-oo.html
Normal file
@@ -0,0 +1,165 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Characteristics of Object-Oriented Languages</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="characteristics-of-object-oriented-languages"><a class="header" href="#characteristics-of-object-oriented-languages">Characteristics of Object-Oriented Languages</a></h2>
|
||||
<p>There is no consensus in the programming community about what features a
|
||||
language must have to be considered object oriented. Rust is influenced by many
|
||||
programming paradigms, including OOP; for example, we explored the features
|
||||
that came from functional programming in Chapter 13. Arguably, OOP languages
|
||||
share certain common characteristics—namely, objects, encapsulation, and
|
||||
inheritance. Let’s look at what each of those characteristics means and whether
|
||||
Rust supports it.</p>
|
||||
<h3 id="objects-contain-data-and-behavior"><a class="header" href="#objects-contain-data-and-behavior">Objects Contain Data and Behavior</a></h3>
|
||||
<p>The book <em>Design Patterns: Elements of Reusable Object-Oriented Software</em> by
|
||||
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley,
|
||||
1994), colloquially referred to as <em>The Gang of Four</em> book, is a catalog of
|
||||
object-oriented design patterns. It defines OOP in this way:</p>
|
||||
<blockquote>
|
||||
<p>Object-oriented programs are made up of objects. An <strong>object</strong> packages both
|
||||
data and the procedures that operate on that data. The procedures are
|
||||
typically called <strong>methods</strong> or <strong>operations</strong>.</p>
|
||||
</blockquote>
|
||||
<p>Using this definition, Rust is object oriented: Structs and enums have data,
|
||||
and <code>impl</code> blocks provide methods on structs and enums. Even though structs and
|
||||
enums with methods aren’t <em>called</em> objects, they provide the same
|
||||
functionality, according to the Gang of Four’s definition of objects.</p>
|
||||
<h3 id="encapsulation-that-hides-implementation-details"><a class="header" href="#encapsulation-that-hides-implementation-details">Encapsulation That Hides Implementation Details</a></h3>
|
||||
<p>Another aspect commonly associated with OOP is the idea of <em>encapsulation</em>,
|
||||
which means that the implementation details of an object aren’t accessible to
|
||||
code using that object. Therefore, the only way to interact with an object is
|
||||
through its public API; code using the object shouldn’t be able to reach into
|
||||
the object’s internals and change data or behavior directly. This enables the
|
||||
programmer to change and refactor an object’s internals without needing to
|
||||
change the code that uses the object.</p>
|
||||
<p>We discussed how to control encapsulation in Chapter 7: We can use the <code>pub</code>
|
||||
keyword to decide which modules, types, functions, and methods in our code
|
||||
should be public, and by default everything else is private. For example, we
|
||||
can define a struct <code>AveragedCollection</code> that has a field containing a vector
|
||||
of <code>i32</code> values. The struct can also have a field that contains the average of
|
||||
the values in the vector, meaning the average doesn’t have to be computed on
|
||||
demand whenever anyone needs it. In other words, <code>AveragedCollection</code> will
|
||||
cache the calculated average for us. Listing 18-1 has the definition of the
|
||||
<code>AveragedCollection</code> struct.</p>
|
||||
<figure class="listing" id="listing-18-1">
|
||||
<span class="file-name">Filename: src/lib.rs</span>
|
||||
<pre><code class="language-rust noplayground">pub struct AveragedCollection {
|
||||
list: Vec<i32>,
|
||||
average: f64,
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-18-1">Listing 18-1</a>: An <code>AveragedCollection</code> struct that maintains a list of integers and the average of the items in the collection</figcaption>
|
||||
</figure>
|
||||
<p>The struct is marked <code>pub</code> so that other code can use it, but the fields within
|
||||
the struct remain private. This is important in this case because we want to
|
||||
ensure that whenever a value is added or removed from the list, the average is
|
||||
also updated. We do this by implementing <code>add</code>, <code>remove</code>, and <code>average</code> methods
|
||||
on the struct, as shown in Listing 18-2.</p>
|
||||
<figure class="listing" id="listing-18-2">
|
||||
<span class="file-name">Filename: src/lib.rs</span>
|
||||
<pre><code class="language-rust noplayground"><span class="boring">pub struct AveragedCollection {
|
||||
</span><span class="boring"> list: Vec<i32>,
|
||||
</span><span class="boring"> average: f64,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>impl AveragedCollection {
|
||||
pub fn add(&mut self, value: i32) {
|
||||
self.list.push(value);
|
||||
self.update_average();
|
||||
}
|
||||
|
||||
pub fn remove(&mut self) -> Option<i32> {
|
||||
let result = self.list.pop();
|
||||
match result {
|
||||
Some(value) => {
|
||||
self.update_average();
|
||||
Some(value)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn average(&self) -> f64 {
|
||||
self.average
|
||||
}
|
||||
|
||||
fn update_average(&mut self) {
|
||||
let total: i32 = self.list.iter().sum();
|
||||
self.average = total as f64 / self.list.len() as f64;
|
||||
}
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-18-2">Listing 18-2</a>: Implementations of the public methods <code>add</code>, <code>remove</code>, and <code>average</code> on <code>AveragedCollection</code></figcaption>
|
||||
</figure>
|
||||
<p>The public methods <code>add</code>, <code>remove</code>, and <code>average</code> are the only ways to access
|
||||
or modify data in an instance of <code>AveragedCollection</code>. When an item is added to
|
||||
<code>list</code> using the <code>add</code> method or removed using the <code>remove</code> method, the
|
||||
implementations of each call the private <code>update_average</code> method that handles
|
||||
updating the <code>average</code> field as well.</p>
|
||||
<p>We leave the <code>list</code> and <code>average</code> fields private so that there is no way for
|
||||
external code to add or remove items to or from the <code>list</code> field directly;
|
||||
otherwise, the <code>average</code> field might become out of sync when the <code>list</code>
|
||||
changes. The <code>average</code> method returns the value in the <code>average</code> field,
|
||||
allowing external code to read the <code>average</code> but not modify it.</p>
|
||||
<p>Because we’ve encapsulated the implementation details of the struct
|
||||
<code>AveragedCollection</code>, we can easily change aspects, such as the data structure,
|
||||
in the future. For instance, we could use a <code>HashSet<i32></code> instead of a
|
||||
<code>Vec<i32></code> for the <code>list</code> field. As long as the signatures of the <code>add</code>,
|
||||
<code>remove</code>, and <code>average</code> public methods stayed the same, code using
|
||||
<code>AveragedCollection</code> wouldn’t need to change. If we made <code>list</code> public instead,
|
||||
this wouldn’t necessarily be the case: <code>HashSet<i32></code> and <code>Vec<i32></code> have
|
||||
different methods for adding and removing items, so the external code would
|
||||
likely have to change if it were modifying <code>list</code> directly.</p>
|
||||
<p>If encapsulation is a required aspect for a language to be considered object
|
||||
oriented, then Rust meets that requirement. The option to use <code>pub</code> or not for
|
||||
different parts of code enables encapsulation of implementation details.</p>
|
||||
<h3 id="inheritance-as-a-type-system-and-as-code-sharing"><a class="header" href="#inheritance-as-a-type-system-and-as-code-sharing">Inheritance as a Type System and as Code Sharing</a></h3>
|
||||
<p><em>Inheritance</em> is a mechanism whereby an object can inherit elements from
|
||||
another object’s definition, thus gaining the parent object’s data and behavior
|
||||
without you having to define them again.</p>
|
||||
<p>If a language must have inheritance to be object oriented, then Rust is not
|
||||
such a language. There is no way to define a struct that inherits the parent
|
||||
struct’s fields and method implementations without using a macro.</p>
|
||||
<p>However, if you’re used to having inheritance in your programming toolbox, you
|
||||
can use other solutions in Rust, depending on your reason for reaching for
|
||||
inheritance in the first place.</p>
|
||||
<p>You would choose inheritance for two main reasons. One is for reuse of code:
|
||||
You can implement particular behavior for one type, and inheritance enables you
|
||||
to reuse that implementation for a different type. You can do this in a limited
|
||||
way in Rust code using default trait method implementations, which you saw in
|
||||
Listing 10-14 when we added a default implementation of the <code>summarize</code> method
|
||||
on the <code>Summary</code> trait. Any type implementing the <code>Summary</code> trait would have
|
||||
the <code>summarize</code> method available on it without any further code. This is
|
||||
similar to a parent class having an implementation of a method and an
|
||||
inheriting child class also having the implementation of the method. We can
|
||||
also override the default implementation of the <code>summarize</code> method when we
|
||||
implement the <code>Summary</code> trait, which is similar to a child class overriding the
|
||||
implementation of a method inherited from a parent class.</p>
|
||||
<p>The other reason to use inheritance relates to the type system: to enable a
|
||||
child type to be used in the same places as the parent type. This is also
|
||||
called <em>polymorphism</em>, which means that you can substitute multiple objects for
|
||||
each other at runtime if they share certain characteristics.</p>
|
||||
<section class="note" aria-role="note">
|
||||
<h3 id="polymorphism"><a class="header" href="#polymorphism">Polymorphism</a></h3>
|
||||
<p>To many people, polymorphism is synonymous with inheritance. But it’s
|
||||
actually a more general concept that refers to code that can work with data of
|
||||
multiple types. For inheritance, those types are generally subclasses.</p>
|
||||
<p>Rust instead uses generics to abstract over different possible types and
|
||||
trait bounds to impose constraints on what those types must provide. This is
|
||||
sometimes called <em>bounded parametric polymorphism</em>.</p>
|
||||
</section>
|
||||
<p>Rust has chosen a different set of trade-offs by not offering inheritance.
|
||||
Inheritance is often at risk of sharing more code than necessary. Subclasses
|
||||
shouldn’t always share all characteristics of their parent class but will do so
|
||||
with inheritance. This can make a program’s design less flexible. It also
|
||||
introduces the possibility of calling methods on subclasses that don’t make
|
||||
sense or that cause errors because the methods don’t apply to the subclass. In
|
||||
addition, some languages will only allow <em>single inheritance</em> (meaning a
|
||||
subclass can only inherit from one class), further restricting the flexibility
|
||||
of a program’s design.</p>
|
||||
<p>For these reasons, Rust takes the different approach of using trait objects
|
||||
instead of inheritance to achieve polymorphism at runtime. Let’s look at how
|
||||
trait objects work.</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user