Gaining Flexibility by Building Bridges: Analysis and Implementations in Four Different Languages with the Bridge Design Pattern
[tr] Türkçe Oku 2023-06-22
Once upon a time, there were two kingdoms. Despite being very close to each other, a wide and deep river was located between them. This river made it difficult for the kingdoms to interact with each other. To solve this situation, both kingdoms decided to build a bridge.
This bridge became an interface that facilitated communication between the two kingdoms. However, the bridge had different structures and features on each side. One side was made of stone, while the other was made of wood. This allowed each side of the bridge to be modified independently of the other. In other words, changing the structure of one side did not affect the other side. This increased the flexibility and extensibility of the bridge.
This story represents the fundamental idea of the Bridge design pattern. The Bridge pattern allows different parts of an application to be modified independently of each other. This makes the application more flexible and extensible.
C# Example:
interface IDrawAPI
{
void DrawCircle(int radius, int x, int y);
}
public class RedCircle : IDrawAPI
{
public void DrawCircle(int radius, int x, int y)
{
Console.WriteLine("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", " + y + "]");
}
}
public class GreenCircle : IDrawAPI
{
public void DrawCircle(int radius, int x, int y)
{
Console.WriteLine("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", " + y + "]");
}
}
abstract class Shape
{
protected IDrawAPI drawAPI;
protected Shape(IDrawAPI drawAPI)
{
this.drawAPI = drawAPI;
}
public abstract void Draw();
}
public class Circle : Shape
{
private int x, y, radius;
public Circle(int x, int y, int radius, IDrawAPI drawAPI) : base(drawAPI)
{
this.x = x;
this.y = y;
this.radius = radius;
}
public override void Draw()
{
drawAPI.DrawCircle(radius,x,y);
}
}
Java Example:
interface DrawAPI {
public void drawCircle(int radius, int x, int y);
}
public class RedCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", " + y + "]");
}
}
public class GreenCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", " + y + "]");
}
}
abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw();
}
public class Circle extends Shape {
private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
Go Example:
package main
import "fmt"
type DrawAPI interface {
drawCircle(radius int, x int, y int)
}
type RedCircle struct {}
func (r *RedCircle) drawCircle(radius int, x int, y int) {
fmt.Printf("Drawing Circle[ color: red, radius: %d, x: %d, y: %d]\n", radius, x, y)
}
type GreenCircle struct {}
func (g *GreenCircle) drawCircle(radius int, x int, y int) {
fmt.Printf("Drawing Circle[ color: green, radius: %d, x: %d, y: %d]\n", radius, x, y)
}
type Shape struct {
drawAPI DrawAPI
}
type Circle struct {
x int
y int
radius int
drawAPI DrawAPI
}
func (c *Circle) draw() {
c.drawAPI.drawCircle(c.radius, c.x, c.y)
}
Rust Example:
trait DrawAPI {
fn draw_circle(&self, radius: i32, x: i32, y: i32);
}
struct RedCircle;
impl DrawAPI for RedCircle {
fn draw_circle(&self, radius: i32, x: i32, y: i32) {
println!("Drawing Circle[ color: red, radius: {}, x: {}, y: {}]", radius, x, y);
}
}
struct GreenCircle;
impl DrawAPI for GreenCircle {
fn draw_circle(&self, radius: i32, x: i32, y: i32) {
println!("Drawing Circle[ color: green, radius: {}, x: {}, y: {}]", radius, x, y);
}
}
struct Circle {
x: i32,
y: i32,
radius: i32,
draw_api: Box<dyn DrawAPI>,
}
impl Circle {
fn new(x: i32, y: i32, radius: i32, draw_api: Box<dyn DrawAPI>) -> Circle {
Circle { x, y, radius, draw_api }
}
fn draw(&self) {
self.draw_api.draw_circle(self.radius, self.x, self.y);
}
}
These examples demonstrate how the Bridge design pattern can be implemented. In each one, there is an interface (or trait in Rust) called DrawAPI
and two classes (RedCircle
and GreenCircle
) that implement this interface. The Shape
and Circle
classes perform drawing operations using this interface. This allows the way in which drawing operations are performed (using RedCircle
or GreenCircle
) to be independent of the Shape
and Circle
classes.
There are many reasons to use the Bridge design pattern. Here are the most important ones:
-
Independence: The Bridge design pattern allows different parts of an application to be modified independently of each other. This means that changing one part does not affect the others.
-
Flexibility: The Bridge design pattern makes the application more flexible. This means that the application can easily adapt to different requirements and situations.
-
Extensibility: The Bridge design pattern makes the application extensible. This means that new features or functions can be easily added.
-
Code Reuse: The Bridge design pattern facilitates code reuse. This means that the same code can be used over and over again in different places, making the code shorter and more understandable.
-
Separation and Organization: The Bridge design pattern allows for better organization and separation of code. This makes the code easier to read and maintain.
-
Dynamic Binding: The Bridge design pattern uses dynamic binding. This means that the application can choose different implementations at runtime.