Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Design Pattern the Bridge. Another Look

0.00/5 (No votes)
10 Feb 2011 1  
How to use the design pattern Bridge in your code

Introduction

This article starts a new serious of articles about the design patterns.
I realize that this topic was covered by myriads of different authors, but I also want to put my modest share into it.
My intension is to help a programmer to understand what a particular design pattern is and how it can be used in application architecture.
I do not address this article to an advance programmer/analyst but rather to a beginner-intermediate programmer who wants master his/her analytical and architectural skills.

If you find this explanation to be too simple, please, read who my audience is and let other people decide if it has some use for them.

I skip the formal introduction to the design patterns you can find it here.

Bridge Pattern

This pattern belongs to Structural Patterns.

The bridge pattern is a design pattern used in software engineering which is meant to "decouple an abstraction from its implementation so that the two can vary independently"

For now, let us do not think hard on the above definition and let us consider the following class:
public class Customer
    {
        public int ID { get; set; }
        public string FullName { get; set; }

        public Customer() { }
        public Customer(int id)
        {

            Customer tmp = GetCustomer(id);
            this.ID = tmp.ID;
            this.FullName = tmp.FullName;
        }

        private Customer GetCustomer(int id)
        {
            //get customer out of the database:
            string sql = "select * from customer where id=" + id.ToString();
            using (SqlConnection con = new SqlConnection())
            {
                Customer tmp = new Customer();
                //con.Open();
                //create command:
                //get dataset:
                //populate the customer:
                tmp.ID = id;
                tmp.FullName = "Customer" + id.ToString();
                return tmp;
            }

        }
    }

It is a very simple Customer class with ID and FullName properties, having two constructors.
In his second constructor it uses GetCustomer() function, which takes the data from a MSSQL database using the SQL connection and assigns the Customer object properties(this is, of course, not working code created just for demonstrating purposes).

From the object oriented design and analysis view point, such code is less than perfect.

If by chance, you change something in you database which relates to the customer (rename columns, add columns, use view rather than table, etc…), you need to re-write sql statement in the procedure and recompile the project.

Or if you want to move your database from MSSQL to Oracle, or MySQL, you have to re-write the procedure and recompile the project.

Or if you need to add another class, say Product, you have to create its own GetProduct() method to be used in the similar constructor.

Bottom line, this approach is not scalable.

Here comes the Bridge pattern really handy.
Let us take a look at the classical UML diagram for the Bridge pattern:
BridgePattern.png
At the left part of the diagram we can see an Abstraction and its Refinement, At the right part of the diagram we can see the Implementer and its Implementation.

In our example the Customer class is the Refinement.

Obviously we have to do 3 mandatory things:
  1. Define some abstraction for the Customer class.
  2. Decouple the newly found abstraction from the implementation of the GetCustomer() function
  3. Create the Concrete Implementation


Using the above diagram and pattern as an example, having in mind our goals let us create a diagram that would serve our purpose:
Bridge1.png
As you can see the Customer object becomes and independent refined object and in no way depends on the database. It inherits from the BuisinessObjectAbstract abstract class.
This class has GetObject() function which will be used in the Customer constructor (see later).
The GetObject() function is implemented via dataObject private variable of the DataObjectImplementor class.

The DataObjectImplementor class is an abstract class, which is created to implement some behavior. It can be an interface rather than a class.
And the SQLImplementor class is the concrete implementation of the DataObjectImplementor.

Let us provide some code to explain the above diagrams.

class Customer : BusinessObjectAbstract
    {
        public int ID { get; set; }
        public string FullName { get; set; }

        public Customer() { }
        public Customer(int id)
        {
            Customer tmp = (Customer)GetObject(id, this.GetType());

            this.ID = tmp.ID;
            this.FullName = tmp.FullName;
        }


public abstract class BusinessObjectAbstract
    {
        private bool isSQL = true;

        private DataObjectImplementor dataObject;

        public object GetObject(int id, Type type)
        {

            if (isSQL)
                dataObject = new SQLController();
            else
                dataObject = new OracleController();

            return dataObject.Get(id, type);
        }
    }





public abstract class DataObjectImplementor
    {
        public abstract object Get(int id, Type type);
    }


public class SQLController : DataObjectImplementor
    {

        public override object Get(int id, Type type)
        {

            if (type.Name==  "Customer")
            {
                //use specific to T-SQL code:
                Customer tmp = new Customer();

                tmp.ID = id;
                tmp.FullName = "SQL Customer" + id.ToString();

                return tmp;
            }

            return null;
        }
    }

    public class OracleController : DataObjectImplementor
    {

        public override object Get(int id, Type type)
        {

            if (type.Name == "Customer")
            {
                //use specific to PL/SQL code:
                Customer tmp = new Customer();

                tmp.ID = id;
                tmp.FullName = "Oracle Customer" + id.ToString();

                return tmp;
            }

            return null;
        }
    }

}


Imagine that in some time you have to add to the model a new class Product1.

Following the existing pattern all you need is to create something like this:
    class Product : BusinessObjectAbstract
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public Product() { }
        public Product(int id)
        {
            Product tmp = (Product)GetObject(id,this.GetType());

            this.ID = tmp.ID;
            this.Name = tmp.Name;
        }
    }

After this you have to upgrade your Concrete Implementers

public class SQLController : DataObjectImplementor
    {

        public override object Get(int id, Type type)
        {

            if (type.Name==  "Customer")
            {
                //use specific to T-SQL code:
                Customer tmp = new Customer();

                tmp.ID = id;
                tmp.FullName = "SQL Customer" + id.ToString();

                return tmp;
            }

            if (type.Name == "Product1")
            {

                //use specific to T-SQL code:
                Product tmp = new Product();

                tmp.ID = id;
                tmp.Name = "SQL Product" + id.ToString();

                return tmp;
            }

            return null;
        }
    }

    public class OracleController : DataObjectImplementor
    {

        public override object Get(int id, Type type)
        {

            if (type.Name == "Customer")
            {
                //use specific to PL/SQL code:
                Customer tmp = new Customer();

                tmp.ID = id;
                tmp.FullName = "Oracle Customer" + id.ToString();

                return tmp;
            }

            if (type.Name == "Product1")
            {

                //use specific to PL/SQL code:
                Product tmp = new Product();

                tmp.ID = id;
                tmp.Name = "Oracle Product" + id.ToString();

                return tmp;
            }

            return null;
        }
    }


Let us see how we use our classes:
  1. Abstraction class is responsible for the selection of a concrete implementer
  2. Concrete implementer is responsible for the execution of the procedure.

In future if we want to add and/or change some behavior we work only with those classes.

If we put those classes in a special assembly in our project, we can change and recompile them without touching the main class library.

Conclusion

As you can see from this example, the Bridge Pattern is a very useful and powerful design pattern which helps you to create objects oriented scalable application.
Extremely useful it is when you define your business/data layers.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here