In modern software design, using composition (via the Bridge pattern) instead of inheritance is often considered a better approach, particularly when working with flexible, scalable systems. The Bridge pattern is a structural design pattern that decouples an abstraction from its implementation, allowing the two to vary independently. This pattern promotes composition by separating the interface from the implementation, which solves many problems associated with inheritance.
Here is the code of Bridge pattern implemented in Rust...
trait Color {
fn new(&self) {
}
fn get_name(&self) -> String;
}
struct Red;
struct Black;
impl Color for Red {
fn new(&self) {
println!("The color is Black");
}
fn get_name(&self) -> String {
"Red".to_string()
}
}
impl Color for Black {
fn new(&self) {
println!("The color is Black");
}
fn get_name(&self) -> String {
"Black".to_string()
}
}
trait Gear {
fn new(&self){
}
fn get_name(&self) -> String;
}
struct AutoGear;
struct ManualGear;
impl Gear for AutoGear {
fn new(&self){
println!("The gear is AutoGear");
}
fn get_name(&self) -> String {
"AutoGear".to_string()
}
}
impl Gear for ManualGear {
fn new(&self){
println!("The gear is Manual Gear");
}
fn get_name(&self) -> String {
"ManualGear".to_string()
}
}
trait Vehicle {
fn set_color(&mut self, color : Box<dyn Color>) {}
fn set_gear (&mut self, gear : Box<dyn Gear>) {}
fn display_attributes(&self) { }
}
#[derive()]
struct SmallCar{
color: Box<dyn Color>,
gear: Box<dyn Gear>,
}
#[derive()]
struct Truck{
color: Box<dyn Color>,
gear: Box<dyn Gear>,
}
impl Vehicle for SmallCar {
fn set_color(&mut self, color : Box<dyn Color>) {
self.color = color;
}
fn set_gear(&mut self, gear : Box<dyn Gear>) {
self.gear = gear;
}
fn display_attributes(&self) {
println!("The vehicle color is {:?}", self.color.get_name());
println!("The vehicle gear is {:?}", self.gear.get_name());
}
}
impl Vehicle for Truck {
fn set_color(&mut self, color : Box<dyn Color>) {
self.color = color;
}
fn set_gear(&mut self, gear : Box<dyn Gear>) {
self.gear = gear;
}
fn display_attributes(&self) {
println!("The vehicle color is {:?}", self.color.get_name());
println!("The vehicle gear is {:?}", self.gear.get_name());
}
}
fn main() {
let color = Box::new(Red);
let gear = Box::new(AutoGear);
let truck = Truck {
color: color, gear:gear,
};
truck.display_attributes();
}
Let me tell you in detail what is there in this example.
Here we have a top-level root class called Vehicle having two attributes - color and gear.
The color may be Red and White whereas the Gear may be Manual and Auto.
Now if we put all of these in a monolithic class hierarchy, as shown in the left diagram - there will be many classes.
So, to do it in a better fashion, we maintain two different inheritance hierarchies namely one for Color and another for Gear. And the Vehicle hierarchy is maintained separately.
So from the vehicle hierarchy, we pick up any color and any type of Gear we want from the Color and Gear inheritance hierarchies respectively as shown in the right-side diagram.
The result is that there will be few classes to maintain.
This is exactly what is explained in the following section.
Look at the UML diagram on the left - that is the class hierarchy without using Bridge Pattern. Look at the number of classes - it's many classes.
Now look at the class diagram at the right- look at how fewer classes can manage the same level of different classes if we use the Bridge Pattern
Using composition with the Bridge pattern is often a superior design choice compared to inheritance. It:
- Avoids the pitfalls of deep class hierarchies.
- Promotes code reuse and flexibility.
- Supports better maintainability and scalability.
By favoring composition over inheritance, we can create more modular, flexible, and maintainable systems, which align with modern software design principles such as SOLID.
Here's my implementation of the Bridge pattern in other languages - C++ and Python...
No comments:
Post a Comment