Design Patterns. The Bridge. Another Look
Introduction
This article starts a new series 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 intention is to help a programmer understand what a particular design pattern is, and how it can be used in application architecture.
I do not address this article to an advanced 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, check who my audience is and let other people decide if it has some use for them.
I skipped the formal introduction to the design patterns. You can find it here.
Bridge Pattern
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".This pattern belongs to Structural Patterns.
There are many programmers who perfectly understand the scientific programming language and can just visualize the Bridge pattern on the spot , but unfortunately not all of them.
My approach is to explain the pattern with the help of a life example; in other words I would like to build the Bridge together with you.
We will start from a simple class "Customer".
public class Customer
{
public int ID { get; set; }
public string FullName { get; set; }
public Customer() { }
public Customer(int id)
{
Dictionary<string, object> tmp = GetCustomerInfo(id);
this.ID = (int)tmp["ID"];
this.FullName = tmp["FullName"].ToString();
}
private Dictionary<string, object> GetCustomerInfo(int id)
{
// in real life the customer related data
// will be taken from some data storage:
// MSSQL, or Oracle, or MySql, or XML file, etc...
// at the moment I just create a dictionary object and
// add the items in sync with the customer properties:
Dictionary<string, object> tmp = new Dictionary<string, object>();
tmp.Add("ID", id);
tmp.Add("FullName", "Customer" + id.ToString());
return tmp;
}
}
If you want to utilize object oriented design in your architecture, when you create the above class, your first thought should be "can I have an abstraction of this class?"
In other words: can I have a base class, an abstract class, or an interface from which my class can be inherited?
Usually programmers consider the set of properties of the class and which of them can be moved into the base class.
Say, in our example the Customer class has a quite unique set of properties.
We know that there is no need to create the base class for these properties.
Then let’s consider the behavior of the Customer class.
The behavior is represented by the function GetCustomerInfo(int id).
And we know that that behavior can be different based on the data storage for the customer data.
OK, let us move the behavior from the Customer class to its abstract.
public abstract class ACustomer
{
public Dictionary<string, object> GetCustomerInfo(int id)
{
// in real life the customer related data
// will be taken from some data storage:
// MSSQL, or Oracle, or MySql, or XML file, etc...
// at the moment I just create a dictionary object and
// add the items in sync with the customer properties:
Dictionary<string, object> tmp = new Dictionary<string, object>();
tmp.Add("ID", id);
tmp.Add("FullName", "Customer" + id.ToString());
return tmp;
}
}
public class Customer : ACustomer
{
public int ID { get; set; }
public string FullName { get; set; }
public Customer() { }
public Customer(int id)
{
Dictionary<string, object> tmp = GetCustomerInfo(id);
this.ID = (int)tmp["ID"];
this.FullName = tmp["FullName"].ToString();
}
}
We created the abstract class ACustomer with a single function GetCustomer(int id).The Customer class inherits from ACustomer.
UML diagram for this scenario follows below:
The next step is to decouple the abstraction (ACustomer) from its implementation.
In other words I want some interface for GetCustomerInfo.
public interface IGetInfo
{
Dictionary<string, object> GetInfo(int id);
}
public abstract class ACustomer
{
private IGetInfo getInfo;
public Dictionary<string, object> GetCustomerInfo(int id)
{
return getInfo.GetInfo(id);
}
}
public class Customer : ACustomer
{
public int ID { get; set; }
public string FullName { get; set; }
public Customer() { }
public Customer(int id)
{
Dictionary<string, object> tmp = GetCustomerInfo(id);
this.ID = (int)tmp["ID"];
this.FullName = tmp["FullName"].ToString();
}
}
I added the interface IGetInfo, which will be responsible for getting the info for the Customer (and not only the Customer, that is why I named if IGetInfo).As you noticed, the ACustomer abstraction has a private member of IGetInfo type, which cannot be used, because the interface does not have concrete implementation.
There are two steps left:
- I need to create a concrete class to implement the interface.
- I need to initialize this concrete class in ACustomer.
The concrete class SQLGetInfo:
public class SQLGetInfo : IGetInfo
{
#region IGetInfo Members
public Dictionary<string, object> GetInfo(int id)
{
// in real life the customer related data
// will be taken from the MSSLQ database
// at the moment I just create a dictionary object and
// add the items in sync with the customer properties:
Dictionary<string, object> tmp = new Dictionary<string, object>();
tmp.Add("ID", id);
tmp.Add("FullName", "Customer" + id.ToString());
return tmp;
}
#endregion
}
The new modification of the ACustomer:
public abstract class ACustomer
{
private IGetInfo getInfo = new SQLGetInfo();
public Dictionary<string, object> GetCustomerInfo(int id)
{
return getInfo.GetInfo(id);
}
}
This is the UML diagram for this code:
Please compare this with the classical Bridge UML Diagram:

As you can see we have just built the Bridge!
Ok, the question is WHY?
Why did we take all these steps and create four different classes/interfaces? Why not just leave the initial Customer class which works perfectly?
The answer is "scalability". This is not the only answer, but from my stand point it is the major one.
For now your application you use the MSSQL database as the data storage.
Your business needs require you to add a new class "Product":
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public Product() { }
public Product(int id)
{
Dictionary<string, object> tmp = GetProduct(id);
this.ID = (int)tmp["ID"];
this.Name = tmp["Name"].ToString();
this.Type = tmp["Type"].ToString();
}
public Dictionary<string, object> GetProduct(int id)
{
// in real life the customer related data
// will be taken from some data storage:
// MSSQL, or Oracle, or MySql, or XML file, etc...
// at the moment I just create a dictionary object and
// add the items in sync with the customer properties:
Dictionary<string, object> tmp = new Dictionary<string, object>();
tmp.Add("ID", id);
tmp.Add("Name", "Product" + id.ToString());
tmp.Add("Type", "Product Type" + id.ToString());
return tmp;
}
}
This code reminds you of the very initial code for the Customer.
But we already have the Bridge pattern created for the Customer class.
Let us make this Bridge able to handle the Product class as well.
Now I want to change the previous model to accommodate the newly added Product class and predict the possible addition in the future (to make it scalable):
public interface IGetInfo
{
Dictionary<string, object> GetInfo(int id, string typeName);
}
public abstract class ABisinessObject
{
private IGetInfo getInfo = new SQLGetInfo();
public Dictionary<string, object> GetObjectInfo(int id, string typeName)
{
return getInfo.GetInfo(id, typeName);
}
}
public class Customer : ABisinessObject
{
public int ID { get; set; }
public string FullName { get; set; }
public Customer() { }
public Customer(int id)
{
Dictionary<string, object> tmp = GetObjectInfo(id, this.GetType().Name);
this.ID = (int)tmp["ID"];
this.FullName = tmp["FullName"].ToString();
}
}
public class SQLGetInfo : IGetInfo
{
#region IGetInfo Members
public Dictionary<string, object> GetInfo(int id, string typeName)
{
// in real life the customer related data
// will be taken from the MSSLQ database
// at the moment I just create a dictionary object and
// add the items in sync with the customer properties:
Dictionary<string, object> tmp = new Dictionary<string, object>();
switch (typeName)
{
case "Customer":
tmp.Add("ID", id);
tmp.Add("FullName", "MSSQL Customer" + id.ToString());
break;
case "Product":
tmp.Add("ID", id);
tmp.Add("Name", "MSSQL Product" + id.ToString());
tmp.Add("Type", "MSSQL Product Type" + id.ToString());
break;
}
return tmp;
}
#endregion
}
public class Product : ABisinessObject
{
public int ID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public Product() { }
public Product(int id)
{
Dictionary<string, object> tmp = GetObjectInfo(id,this.GetType().Name);
this.ID = (int)tmp["ID"];
this.Name = tmp["Name"].ToString();
this.Type = tmp["Type"].ToString();
}
}
I made some changes for classes in terms of names and function signatures. Let me explain these changes:
- Instead of ACustomer the abstraction became ABusinessObject
- The newly added class Product inherits the ABusinessObject
- IGetInfo interface changed the signature of the GetInfo function, the string type name was added
- The concrete implementation of the IGetInfo interface was changed. The GetInfo function acts accordingly the type name.
From now on you can add any object into the model, just make it inherit from ABusinessObject and change the concrete implementation of IGetInfo interface.
Here is the UML for now:

Now we have to decide how we will handle the situation when we need to move our data storage from the MSSQL server to a different one.
- We must create a concrete GetInfo class per data storage.
- We must write some code in the ABusinessObject to pick the proper GetInfo concrete type
public class OracleGetInfo : IGetInfo
{
#region IGetInfo Members
public Dictionary<string, object> GetInfo(int id, string typeName)
{
// in real life the customer related data
// will be taken from the Oracle database
// at the moment I just create a dictionary object and
// add the items in sync with the customer properties:
Dictionary<string, object> tmp = new Dictionary<string, object>();
switch (typeName)
{
case "Customer":
tmp.Add("ID", id);
tmp.Add("FullName", "Oracle Customer" + id.ToString());
break;
case "Product":
tmp.Add("ID", id);
tmp.Add("Name", "Oracle Product" + id.ToString());
tmp.Add("Type", "Oracle Product Type" + id.ToString());
break;
}
return tmp;
}
#endregion
}
Let us change the ABusinessObject GetObjectInfo() function:
public Dictionary<string, object> GetObjectInfo(int id, string typeName)
{
string dataProvider;
// In real application we get the dataProvider string from Configuration:
dataProvider = "sql";
switch (dataProvider)
{
case "sql":
getInfo = new SQLGetInfo();
break;
case "oracle":
getInfo = new OracleGetInfo();
break;
case "mysql":
// mysql:
break;
case "xmlfile":
break;
default:
//default:
break;
}
return getInfo.GetInfo(id, typeName);
}
Now the final UML diagram:

Conclusion
I hope this example helps you better understand not only the Bridge Pattern but also the way you think when creating the architecture for your class library.
Please give me some feedback on this article.
I am planning to write about other design patterns, and I would like to know how helpful my method of explanation is.
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架