C# Coding Solutions—Abstract-Class Bridge-Pattern Variation

Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio


Jump to: navigation, search
CSharp-Online.NET:Articles
C# Articles

C# Coding Solutions

© 2006 Christian Gross

Abstract-Class Bridge-Pattern Variation

The classic Bridge pattern implementation defines a class that implements an interface. Some extra code is associated with the formal definition of the Bridge pattern. For this section I’ll focus on one variation of the Bridge pattern and why you would use it.

One variation of the Bridge pattern has no interface definition and only a defined abstract class. Decoupling the interface from the implementation is a good idea, but often the implementation of the interfaces becomes very tedious. A classic example is the XML DOM—when using interfaces extensively, implementing them can be tedious because you must implement the details for all classes.

To illustrate the tedious work I am going to implement the Composite pattern that is used to build a hierarchy of objects. Each object in the hierarchy has to implement the following interface:

interface INode {
    void Add(INode node);
    void PrintContents();
}

The interface INode has two methods: Add and Print. The method Add is structural and Print is an application method. Structural methods are used in a utilitarian context. The method Add adds another Node to the instance associated with the node. Based on the implementation of Add we have no idea what the associated application logic is.

The PrintContents node, on the other hand, is related to the application logic. The implementation of the method could be to write the contents to the database or the screen, or even to translate the contents of the node. PrintContents will have some structural aspects, but the method’s primary function is to do something that the application requires. I differentiated between the two so that you understand that interfaces have methods that serve different purposes.

Continuing with the example, a class called Collection is defined and represents the implementation of a class that contains other nodes:

class Collection : INode {
    protected string _name;
    protected List< INode> _nodes = new List< INode>();
    public void Add(INode node) {
        _nodes.Add(node);
    }
    public Collection(string name) {
        _name = name;
 
    }
    public void PrintContents() {
        Console.WriteLine("Is Collection");
        foreach (INode node in _nodes) {
            node.PrintContents();
        }
    }
}

The implementation details are not particularly important, but they illustrate a thought process of implementing an interface’s tedious details. Imagine if you had 15 different implementations of the same interface. If each one had to be uniquely implemented, that would take quite a bit of your time. So for coding optimization you decide to reuse code and use inheritance. You define an interface, a default abstract base class, and then the specific implementations. However, you do have to ask yourself, if the interface is only relevant to a local assembly or application, why put yourself through the pain of defining an interface and abstract base class combination for each and every interface?

You use the abstract class variation on the Bridge pattern when you want the advantages of interface-based development, but do not want the overhead of having to write all of the plumbing related to interface-based development. You should use this variation of the Bridge pattern only in the context of a single assembly or a single application.

This Bridge pattern variation lets you decouple, but because the interface and implementation are used in the context of one assembly, you can relax the strict separation rules. The reason why you would use interfaces is that a change to the implementation has no ramifications to the interface. If the interface is an abstract base class, then changes to the abstract base class could have potentially huge ramifications. But because we are limiting this solution to a single assembly or application, it is easy to trace where the problems lie.

The following code shows INode and Collection rewritten to use an abstract class:

public abstract class Node {
    protected string _name;
    protected List< Node> _nodes = new List< Node>();
    public Node(string name) {
        _name = name;
    }
    public virtual void Add(Node node) {
        _nodes.Add(node);
    }
    public abstract void PrintContents();
}
class Collection : Node {
    public Collection(string name) : base( name) {
 
    }
    public override void PrintContents() {
        Console.WriteLine("Is Collection");
        foreach (Node node in _nodes) {
            node.PrintContents();
        }
    }
}

When using abstract classes you are still using interface-driven development techniques except that the abstract class serves as interface and the default implementation for structural methods. In the example, the abstract class Node has two data members: _name and _nodes. Both data members have protected data access, allowing derived classes to access them directly. Normally, the internal state of classes is managed delicately, but considering the context it is acceptable to use protected scope.

Node has two methods: Add and PrintContents. Both methods are considered virtual in the abstract logic sense, but Add is marked as explicitly virtual and PrintContents is marked as abstract. By marking the methods as virtual Node is expecting the derived classes to provide their own functionality. In the case of Add the derived classes can define their own functionality, whereas with PrintContents a derived class must define its own functionality.

The class Collection subclasses the interface Node and represents the implementation of the interface. Collection uses the default functionality provided by Node for the method Add, and implements PrintContents.

When using an abstract class instead of an explicit interface definition, remember the following points:

  • When using abstract classes in the context of the Bridge pattern, the abstract class is architectural; the derived class is the implementation.
  • Using abstract classes as a combination interface and default class is simpler and quicker than implementing interfaces with their associated plumbing.
  • You should use abstract classes as a Bridge-pattern variation only in the context of a single assembly or single application so that when dramatic changes are made the ramifications are easy to spot.
  • Abstract classes implement the default functionality that all implementations share.
  • Abstract classes can evolve as the application evolves, which is contrary to how interfaces are defined. Interfaces are defined ahead of time, and they define a contract that is implemented.


Previous_Page_.gif Next_Page_.gif


Personal tools