The bridge mode is a very useful and complex mode. Familiarity with this mode is helpful for understanding the object-oriented design principles, including the "open-closed" principle (OCP) and the combination/aggregation Reuse Principle (CARP. Understanding these two principles helps to form correct design ideas and cultivate a good design style.
Note: In the book "Java and mode", the Bridge mode is not a frequently used mode. I don't agree with it. I think the Bridge mode contains many key ideas of the design mode, so I have adopted Alan Shalloway and James R. trott's point of view: The Bridge pattern is quite a bit more complex than the other patterns you just learned; it is also much more useful.
Intention of the Bridge Model
[GOF95] when proposing the bridge model, it is pointed out that the purpose of the bridge model is to "decouple abstract action and Implementation so that the two can change independently ". This sentence has three keywords: abstraction, implementation, and decoupling.
Abstraction
The conceptual relationships that exist in multiple entities are abstract. As a process, abstraction is to ignore some information and treat different entities as the same entities [LISKOV94 ].
Implementation
The specific implementation given in abstraction is implementation.
Decoupling
Coupling is a strong association between two entities. Removing their strong associations is the relief of coupling, or decoupling. Here, decoupling means to free up coupling between abstraction and reality, or change strong associations between them into weak associations.
Change the inheritance relationship between two roles to an aggregation relationship, that is, change the strong association between them to a weak Association. Therefore, the so-called decoupling in the bridge mode refers to the use of a combination/aggregation relationship between the abstraction and implementation of a software system, rather than an inheritance relationship, so that the two can change relatively independently. This is the purpose of the bridge model.
Ii. Structure of the Bridge Model
Bridge Mode [GOF95] is the object structure mode, also known as Handle and Body mode or Interface mode.
The figure below shows a schematic system structure that implements the bridge mode.
We can see that this system has two levels, namely:
Abstract level structure composed of abstract roles and corrected abstract roles.
The implementation level structure consists of an implementation role and two specific implementation roles.
The following roles are involved in the Bridge Mode:
Abstract action role: defines the abstract object and saves a reference to the implemented object.
Modify abstract roles: Expand abstract roles, and modify the abstract definitions of the parent class.
Implementor role: this role provides an interface for implementing the role, but does not provide a specific implementation. It must be noted that this interface is not necessarily the same as the interface definition of abstract roles. In fact, these two interfaces can be very different. An implemented role should only provide underlying operations, while an abstract role should only provide operations at a higher level based on underlying operations.
Concrete Implementor role: this role provides the specific implementation of the role interface.
Iii. Schematic source code of the Bridge Mode
// Bridge pattern -- Structural example using System;// "Abstraction"class Abstraction{ // Fields protected Implementor implementor; // Properties public Implementor Implementor { set{ implementor = value; } } // Methods virtual public void Operation() { implementor.Operation(); }}// "Implementor"abstract class Implementor{ // Methods abstract public void Operation();}// "RefinedAbstraction"class RefinedAbstraction : Abstraction{ // Methods override public void Operation() { implementor.Operation(); }}// "ConcreteImplementorA"class ConcreteImplementorA : Implementor{ // Methods override public void Operation() { Console.WriteLine("ConcreteImplementorA Operation"); }}// "ConcreteImplementorB"class ConcreteImplementorB : Implementor{ // Methods override public void Operation() { Console.WriteLine("ConcreteImplementorB Operation"); }}/**//// <summary>/// Client test/// </summary>public class Client{ public static void Main( string[] args ) { Abstraction abstraction = new RefinedAbstraction(); // Set implementation and call abstraction.Implementor = new ConcreteImplementorA(); abstraction.Operation(); // Change implemention and call abstraction.Implementor = new ConcreteImplementorB(); abstraction.Operation(); }}
Iv. Modem Problems
I feel that the example of Bridge pattern in Agile Software Development-principles, models and practices is good. (The change encapsulation section in chapter 33 of Java and patterns is also well written. We recommend that you read it. It elaborates on Design to interfaces in Design Patterns Explained. 2) Favor composition over inheritance. 3) Find what varies and encapsulate it .).
There are a large number of Modem customer programs using the Modem interface. The Modem interface is implemented by several Derived classes HayesModem, USRoboticsModem, and EarniesModem. It complies well with OCP, LSP, and DIP. When a new type of modem is added, the client program of the modem will not be affected.
It is assumed that this situation lasted for several years, and many Modem client programs are using the Modem interface. A non-dial modem is now available, called a dedicated modem. They are located at both ends of a dedicated connection. There are several new applications that use these dedicated modem, which do not require dialing. We call these users DedUser. However, the customer wants all current modem client programs to use these dedicated modems. They do not want to change many modem customer applications, so they can have these modem customer programs dial fake phone numbers.
If possible, we will change the system design as shown in.
We separate the dialing and communication functions into two different interfaces. The original modem implements these two interfaces, and the modem client uses these two interfaces. DedUser only uses the Modem interface, while DedicateModem only implements the Modem interface. But doing so will require us to change all modem customer programs-this is not allowed by the customer.
One possible solution is to let DedicatedModem derive from Modem and implement the dial and hangup methods as null, as shown below:
A few months later, there were already a large number of dedusers, and the customer made a new change. To enable international phone numbers, credit card phone numbers, PIN phone numbers, and so on, you must use char [10] storage numbers in the existing dial to change to phone numbers of any length.
Obviously, all modem client programs must be changed. The customer agreed to change the modem client program because they had no choice. The bad thing is, we have to tell DedUser writers that they have to change their code! You can imagine how happy they are to hear this. They didn't need to call dial.
This is the harmful and chaotic dependency that many projects will have. A kludge in a part of the system creates a harmful dependency, which eventually leads to problems in completely unrelated parts of the system.
If you use the ADAPTER mode to solve the initial problem, you can avoid this serious problem.
Note that the pooled object still exists. The adapter still needs to simulate the connection status. However, all dependencies are initiated from the adapter. The collection body is isolated from the system and hidden from almost unknown adapters.
BRIDGE Mode
There is another way to look at this problem. Now there is another way to split the Modem hierarchy. For example:
This is not an ideal structure. Each time a new hardware is added, two new classes must be created-one for the dedicated scenario and the other for the dial-up scenario. Each time a new connection type is added, three new classes must be created, corresponding to three different hardware. If these two degrees of freedom are unstable at all, then a large number of derived classes will appear soon.
The BRIDGE mode is usually useful when the type hierarchy has multiple degrees of freedom. We can separate these hierarchies and combine them through bridges, rather than merging them.
We divide the modem hierarchy into two layers. One represents the connection method, and the other represents the hardware.
This structure is complex but interesting. Its creation does not affect the user of the modem and completely isolates the connection policy and hardware implementation. Each derived class of ModemConnectController represents a new connection policy. In the implementation of this policy, sendlmp, cancelmp, diallmp, and hanglmp can be used. The addition of new imp methods does not affect users. You can use ISP to add new interfaces to the connection control class. In this way, you can create a migration path. The Modem client program can follow this path to obtain an API that is higher than the dial and hangup levels.
5. Another example of applying the Bridge Mode
This example demonstrates how to decouple a Business Object (BusinessObject) from a data object (DataObject) through the Bridge mode. The implementation of data objects can be dynamically changed without changing the client code.
// Bridge pattern -- Real World exampleusing System;using System.Collections;// "Abstraction"class BusinessObject{ // Fields private DataObject dataObject; protected string group; // Constructors public BusinessObject( string group ) { this.group = group; } // Properties public DataObject DataObject { set{ dataObject = value; } get{ return dataObject; } } // Methods virtual public void Next() { dataObject.NextRecord(); } virtual public void Prior() { dataObject.PriorRecord(); } virtual public void New( string name ) { dataObject.NewRecord( name ); } virtual public void Delete( string name ) { dataObject.DeleteRecord( name ); } virtual public void Show() { dataObject.ShowRecord(); } virtual public void ShowAll() { Console.WriteLine( "Customer Group: {0}", group ); dataObject.ShowAllRecords(); }}// "RefinedAbstraction"class CustomersBusinessObject : BusinessObject{ // Constructors public CustomersBusinessObject( string group ) : base( group ){} // Methods override public void ShowAll() { // Add separator lines Console.WriteLine(); Console.WriteLine( "------------------------" ); base.ShowAll(); Console.WriteLine( "------------------------" ); }}// "Implementor"abstract class DataObject{ // Methods abstract public void NextRecord(); abstract public void PriorRecord(); abstract public void NewRecord( string name ); abstract public void DeleteRecord( string name ); abstract public void ShowRecord(); abstract public void ShowAllRecords();}// "ConcreteImplementor"class CustomersDataObject : DataObject{ // Fields private ArrayList customers = new ArrayList(); private int current = 0; // Constructors public CustomersDataObject() { // Loaded from a database customers.Add( "Jim Jones" ); customers.Add( "Samual Jackson" ); customers.Add( "Allen Good" ); customers.Add( "Ann Stills" ); customers.Add( "Lisa Giolani" ); } // Methods public override void NextRecord() { if( current <= customers.Count - 1 ) current++; } public override void PriorRecord() { if( current > 0 ) current--; } public override void NewRecord( string name ) { customers.Add( name ); } public override void DeleteRecord( string name ) { customers.Remove( name ); } public override void ShowRecord() { Console.WriteLine( customers[ current ] ); } public override void ShowAllRecords() { foreach( string name in customers ) Console.WriteLine( " " + name ); }}/**//// <summary>/// Client test/// </summary>public class BusinessApp{ public static void Main( string[] args ) { // Create RefinedAbstraction CustomersBusinessObject customers = new CustomersBusinessObject(" Chicago "); // Set ConcreteImplementor customers.DataObject = new CustomersDataObject(); // Exercise the bridge customers.Show(); customers.Next(); customers.Show(); customers.Next(); customers.Show(); customers.New( "Henry Velasquez" ); customers.ShowAll(); }}
6. Under what circumstances should the bridge model be used
According to the above analysis, the bridge mode should be used in the following situations:
If a system requires more flexibility between the abstract role and the specific role of the component, it will avoid establishing static connections between the two layers.
The design requires that any change to the role should not affect the client, or the change to the role should be completely transparent to the client.
A component has more than one abstract role and actual role, and the system needs dynamic coupling between them.
Although there is no problem in using inheritance in the system, the abstract roles and specific roles need to be changed independently, and the design requirements need to be managed independently.