In the last post, I talked about Constructor Injection
. If you haven't already, make sure to check it out.
We want our code to be open to extensibility but closed against modification, the desired goal commmonly referred to as open-closed priciple
. How can we achieve that in Rust?
Let's start with the code example that we saw in the last post.
struct Sender<W: Writer> {
writer: W,
}
impl<W: Writer> Sender<W> {
fn send(&self) {
self.writer.write("Hello World!")
}
}
trait Writer {
fn write(&self, word: &str);
}
struct ConsoleWriter;
impl Writer for ConsoleWriter {
fn write(&self, word: &str) {
println!("{}", word)
}
}
fn main() {
let writer = ConsoleWriter;
let sender = Sender { writer };
sender.send();
}
This time, what we want is to add authentication feature to the writer WITHOUT modifying existing interface of Sender
.
Firstly, let's add Identity
trait which has is_authenticated
method.
trait Identity {
fn is_authenticated(&self) -> bool;
}
And its concrete implementation:
struct CIdentity;
impl Identity for CIdentity {
fn is_authenticated(&self) -> bool {
todo!()
}
}
And then you create yet another concrete implementation of SecureWriter
which itself implements Writer
trait.
struct SecureWriter<W: Writer, I: Identity> {
identity: I,
writer: W,
}
impl<W: Writer, I: Identity> Writer for SecureWriter<W, I> {
fn write(&self, word: &str) {
if self.identity.is_authenticated() {
self.writer.write(word)
}
}
}
Now, instead of passing ConsoleWriter
directly to Sender
, you can pass SecureWriter
and use as though nothing has been changed except for adding more feature!
fn main() {
let sender = Sender {
writer: SecureWriter {
writer: ConsoleWriter,
identity: CIdentity,
},
};
sender.send();
}
Actually, this pattern is so common that it is one of the design patterns suggested by Gang of four. Specifically, the pattern is called Decorator Pattern
.