If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.
17.1.0 What Are Object-Oriented Programming Features?
Object-oriented programming (OOP) is a programming model. The concept of objects was introduced in the programming language Simula. These objects influenced Alan Kay’s programming architecture, in which objects send messages to one another. To describe this architecture, he coined the term object-oriented programming in 1967.
Core Concepts
-
Object
- The basic unit of a program, containing properties (state) and behavior (operations).
-
Class
- A template for objects that defines properties and behavior.
-
Encapsulation
- Binds data and operations together, hides internal details, and interacts with the outside world through an interface.
-
Inheritance
- A subclass inherits the properties and behavior of a parent class, improving code reuse.
-
Polymorphism
- The same interface exhibits different behavior, including method overloading and overriding.
-
Abstraction
- Focuses only on the necessary parts and ignores complex implementations, providing a high-level design through classes or interfaces.
Benefits of Object-Oriented Programming
- Modularity and maintainability: Code is easier to maintain and extend.
- Code reuse: Inheritance and abstraction reduce duplicate code.
- Easy extension: New features can be added easily.
- Real-world modeling: Closer to real-world concepts.
- Data safety: Encapsulation protects data and improves security.
17.1.1 Rust’s Object-Oriented Programming Characteristics
There is still no community consensus on which features a language must have to be considered object-oriented. Rust is influenced by many programming paradigms, including OOP. OOP usually includes features such as objects, encapsulation, and inheritance.
There are many definitions of object-oriented programming, and many of them conflict with one another. Some definitions classify Rust as an object-oriented language, while others do not.
In Chapter 13, we talked about Rust’s functional programming features, but Rust is neither a traditional object-oriented language nor a pure functional language. It is a multiparadigm language that combines some features of functional programming and object-oriented programming.
Objects Contain Data and Behavior
The book Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, known informally as the “Gang of Four,” is a classic work on design patterns. It defines OOP this way:
Object-oriented programs are made up of objects. An **object* packages both data and the procedures that operate on that data. The procedures are typically called methods or operations.*
Based on this definition, Rust is object-oriented: structs and enums contain data, and impl blocks provide methods for them. But in Rust, structs and enums with methods are not called objects.
Encapsulation
Encapsulation means that code outside the object cannot directly access the object’s internal implementation details; the only way to interact with the object is through its public API.
Rust uses the pub keyword to decide which modules, types, functions, or methods in code are public. By default, they are private.
Take a look at this example:
pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
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;
}
}
This struct is marked pub so that other code can use it, but its fields are still private. That is because we want to ensure that the average is updated whenever a value is added to or removed from the list. Directly changing the fields would not guarantee that, so users should not be allowed to modify the fields directly. We achieve this by implementing the add, remove, and average methods on the struct.
Inheritance
Inheritance means that one object can reuse the data and behavior of another object without redefining the related code. Rust does not support this feature.
The usual reasons for using inheritance are code reuse and polymorphism.
For code reuse, Rust provides default trait methods to share code. If a method in a trait has a default implementation, then any type that implements that trait automatically gets that method. This is very similar to object-oriented languages, where methods implemented in a parent class can be inherited by subclasses. When implementing a trait, you can also override the trait’s default implementation, which is similar to a subclass overriding an inherited method from its parent class.
Polymorphism means expecting a type to work where a parent type is required. In other words, if several objects share some common traits, those objects can be substituted for one another at runtime. Rust achieves this with generics and trait bounds: generics allow logic to be more independent of the concrete data type, and trait bounds specify which concrete capabilities the types using that logic must provide. This technique is also called bounded parametric polymorphism.
Nowadays, many languages no longer use inheritance as a built-in programming design. That is because it often risks sharing too much code. A subclass should not always share all the traits of its parent class, but inheritance makes that possible. This reduces flexibility in program design. It also introduces the possibility of calling methods on a subclass that are meaningless or even incorrect, because those methods are not appropriate for the subclass. In addition, some languages allow only single inheritance, meaning a subclass can inherit from only one class, which further limits design flexibility.
Top comments (0)