Nikita Katchik
Posted on August 22, 2022
Intro
It is handy to be able to separate the interface from the implementation; however, it often is not enough. In this piece, I'd like to talk about probably the simplest example of an interface-class paradigm limitation.
What if we had to design a Size
class in Java or other classic OOP language? There are many variations, especially in construction (factories) and setters (if any), but one thing would have remained unchanged in every single implementation: the two numbers.
// Java
class Size {
private int mWidth;
private int mHeight;
public Size(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException();
}
mWidth = width;
mHeight = height;
}
public int width() {
return mWidth;
}
public int height() {
return mHeight;
}
public int area() {
return width() * height();
}
}
Every engineer had to write something like this a couple of times, but has this never bothered you from the data perspective? It strikes me odd every time a pair of numbers is suddenly a Size
. A pair of numbers can be anything, but since the data is not detachable from implementation, we create classes like Size
, Point
, and ComplexNumber
– all containing precisely two numbers: from a data perspective, they are not different types.
Of course, we do this because, in Java, we have to think from the interface's perspective; implementation simply contains all the necessary member variables to fulfil the interface contract. The price we pay here is having to create a Size
even if we have a pair of perfectly fine numbers on the stack.
In the case of Size
, it does not seem like a big sacrifice; however, once the structure of the types, the transformations performed on them, and the wiring become more complex, not being able to use perfectly fine data can become rather limiting, especially if it's be something big.
The solution
What if there was an idiomatic way to treat data in different ways on different occasions? This is precisely what a trait is: a predefined way to interpret certain data type. Let us have a look at how similar thing could be done with traits in Rust.
First, we don't even have to declare a data type here: we can simply use a tuple instead. Here is how we'd declare a variable suitable to be treated as a size.
// Rust
let s = (2, 3);
Then we declare a trait. Notice that we define area()
function right away. That's because the area calculation does not depend on the details of width()
and height()
functions implementation details.
// Rust
trait Size {
fn width(&self) -> u32;
fn height(&self) -> u32;
fn area(&self) -> u32 {
return self.width() * self.height();
}
}
All that is left to do is to define to interpret a tuple of two integers (u32, u32)
as a Size
.
// Rust
impl Size for (u32, u32) {
fn width(&self) -> u32 {
return self.0;
}
fn height(&self) -> u32 {
return self.1;
}
}
It is just as simple to implement the Size
trait for a slice of two u32
's.
// Rust
impl Size for [u32; 2] {
fn width(&self) -> u32 {
return self[0];
}
fn height(&self) -> u32 {
return self[1];
}
}
We have just implemented Size
trait for two basic data types which preexisted in the language. Now let us try invoking these implementations.
// Rust
fn foo() {
let tuple = (2, 3);
// Automatic trait resolution
println!("Area: {}", tuple.area());
// Explicitly choose trait implementation
println!("Area: {}", Size::area(&tuple));
let array = [4, 5];
// Automatic trait resolution
println!("Area: {}", array.area());
// Explicitly choose trait implementation
println!("Area: {}", Size::area(&array));
}
Thoughts
Data doesn't have to be glued together with the implementation even when the interface is declared separately, and creating useful code without defining redundant data types is possible. With traits, we can interpret the same piece of data differently on different occasions, which changes everything in the pursuit of a good design. Traits are sometimes referred to as a concept of object-oriented programming, but in my opinion it is misleading: the complete traits can only be found in languages leaning towards data-oriented design, like Rust.
Posted on August 22, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 13, 2024
November 12, 2024