Practicing the Chain of Responsibility Pattern
 
Published: 21 Nov 2008
Abstract
The intent of the Chain of Responsibility pattern is to avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request and further, chain the receiving objects and pass the request along the chain until an object handles it. In this article, Xianzhong shows the typical utilization of another popular design pattern, the Chain of Responsibility pattern, by developing a simple application service explorer example with the help of relevant screen shots and source code. He begins with a detailed analysis of the various requirements for developing the sample application followed by an overview of the design. He further provides comprehensive coverage of the template method pattern and the various aspects of the sample application including some improvements which need to be done to the design.
by Xianzhong Zhu
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 33517/ 60

Introduction

Digging further, there are included at least two meanings here. First, there are objects that have a common responsibility, which means these objects can be abstracted into a common interface. Secondly, it is the meaning of the "chain." The so-called "chain" from the angle of data structure, corresponds to the chain typed persisting structure, one of whose outstanding characteristics is that the factual elements are close together in the logical structure, while in the physical position not necessarily the case. Therefore, it is unlike an array that can be accessed at random. In the meantime, once any element in the chain becomes disjointed, the message delivered in the chain will be interrupted. Therefore, it will be a more comfortable way to provide a special class or method to carry out the responsibility of taking care of this chain of responsibility.

As you know, the ultimate purpose to introduce the Chain of Responsibility pattern is to achieve the aim of decoupling. And, from the angle of "encapsulate what varies," it seals the variety of responsibility behavior, in the meantime again, makes use of a chain structure to insure the deliver of news. All in all, the biggest advantage to introduce the Chain of Responsibility pattern lies in it can intelligently locate the suitable object in the responsibility chain according to specified conditions incumbency, and then the found object can carry out its responsibility.

In this article, I will show you the typical utilization of the Chain of Responsibility design pattern by building up a simple service explorer application with gradual modifications.

Constructing an Service Explorer Application: Requirements Analyzing

Suppose that in one of our frame products, because of needing to support a distribute type processing, we have to adopt the .NET Remoting technique. Considering some special requests toward service objects by our frame product, we finally decide to develop a custom service explorer as the host of the Remoting service objects.

Well, since our target is to develop a service explorer, we need to write methods concerning the manipulation of the service objects, such as start or stop a service, etc. On the other hand, we have known that there are 3 kinds of methods to invoking service objects (which are based upon .NET Remoting technique): the Singleton mode, SingleCall mode, and client-side invoking mode, whose implementation logics are different in starting/stopping the service.

Author's Note: In this article we have leveraged .NET Remoting technique to establish the sample application, while for brevity we are not going to dwell upon it, so for more details about .NET Remoting you can refer here.

The service explorer is a Windows application (see Figure 1). It uses a ListView control to manifest all the detailed information of service objects, such as the service name, service description, and the related invoking mode, etc. For the sake of facilitating the administrator, we hope to provide a shortcut menu that will pop up when the user right-clicks each line of the item in the ListView control. The menu items in the shortcut menu should include common operations, such as start or stop services, etc.

Figure 1: The running-time snapshot of the service explorer application

OK, with the requirement analysis above, we can hold a few important points below:

·         Each service is a remote service object, which should include the service name, service description, service type, etc.

·         Different activating mode of the service object results in the dissimilarity in operating the remote object, but with the consistent operation definition.

·         The information of service objects can be obtained through the value of the ListViewltem item in the ListView control.

·         The user sends out a request through a menu item by clicking the shortcut menu, and the application can make corresponding operations according to the specified claim.

Because in this case we have adopted the third mode (i.e. client-side invoking mode) to activate a service object, we have to establish the instantiation of the service object. For this, we need to define the service object as shown in Listing 1 below.

Listing 1: Define the service object

namespace DonOfDesign.PracticeCORPattern.ServiceManagerLib
{
    public enum ActiveMode
    {
        Singleton = 0, SingleCall, Activation
    }
    public enum ServiceState
    {
        Start = 0, Stop
    }
    public class ServiceObject
    {
        private object m_serviceInstance;
        private string m_serviceName;
        private string m_serviceDesc;
        private ActiveMode m_activeMode;
        private ServiceState m_currentState;
        private Type m_objType;
        public object ServiceInstance
        {
            get { return m_serviceInstance; }
            set { m_serviceInstance = value; }
        }
        public string ServiceName
        {
            get { return m_serviceName; }
            set { m_serviceName = value; }
        }
        public string ServiceDesc
        {
            get { return m_serviceDesc; }
            set { m_serviceDesc = value; }
        }
        public ActiveMode ActiveMode
        {
            get { return m_activeMode; }
            set { m_activeMode = value; }
        }
        public ServiceState CurrentState
        {
            get { return m_currentState; }
            set { m_currentState = value; }
        }
        public Type ObjectType
        {
            get { return m_objType; }
            set { m_objType = value; }
        }
    }
}

In the definition of the service object for class ServiceObject, the Servicelnstance attribute is the instance of the service object to create. The ServiceName attribute is the name of the service object, it is the one and only; substantially, its value is just the class name of the service object. The ServiceDesc attribute relates to the description of the function of the service object. The ActiveMode attribute is an enum type which specifies the activating way of the service object. The CurrentState attribute represents the state of the existing service object, whose state needs to be updated when it is started/stopped. The last attribute ObjectType is very important, which is a "System.Type" type and the real type value of a service object, which will be used when starting the service.

Next, let us look at the ServiceManager class with which to manage the service objects.

The First Design: Simple yet Available

Actually, according to the requirements introduced a moment ago, we can naturally conceive a very simple while practical solution. We can define a ServiceManager class which will be responsible for maintaining the common manipulations of the service objects (start or stop). Listing 2 indicates the definition for class ServiceManager.

Listing 2: The definition for class ServiceManager

public class ServiceManager
{
    private List<ServiceObject> m_serviceList = new List<ServiceObject>();
    static ServiceManager()
    {
        Initialize();
    }
    public List<ServiceObject> ServiceList
    {
        get { return m_serviceList; }
    }
    private static void Initialize()
    {
        //omit the concrete implementation...
      //initializing the service objects and 
       //add them into variable m_serviceList
    }
    public static void StartService(ServiceObject obj)
    {
        if (obj.CurrentState.Equals(ServiceState.Stop))
        {
            Switch (obj.ActiveMode)
            {
                  case ActiveMode.Singleton:
                        RemotingConfiguration.RegisterWellKnownServiceType(
                            obj.ObjectType,obj.ServiceName, 
                            WellKnownObjectMode.Singleton);
                        obj.CurrentState=ServiceState.Start;
                        break;
                  case ActiveMode.SingleCall:
                        RemotingConfiguration.RegisterWellKnownServiceType(
                            obj.ObjectType,obj.ServiceName, 
                            WellKnownObjectMode.SingleCall);
                        obj.CurrentState=ServiceState.Start;
                        break;
                  case ActiveMode.Activation:
                        RemotingServices.Marshal(
                            (MarshalByRefObject)obj.ServiceInstance, 
                            obj.ServiceName);
                        obj.CurrentState=ServiceState.Start;
                        break;
            }
        }
    }
 
    public static void StopService(ServiceObject obj)
    {
        if (obj.CurrentState.Equals(ServiceState.Start))
        {
            Switch (obj.ActiveMode)
            {
                  case ActiveMode.Singleton:
                        //no implementation
                        break;
                  case ActiveMode.SingleCall:
                        //no implementation
                        break;
                  case ActiveMode.Activation:
                        RemotingServices.Disconnect
 (MarshalByRefObject)obj.ServiceInstance);
                        obj.CurrentState=ServiceState.Stop;
                        break;
            }
        }
    }
}

Herein, the Initialize method is used to initialize the service object. Concretely, it takes the responsibility of reading out information about the service object from inside the configure file, establishing the service object, and then adding it into the m_seviceList object.

In addition to what is listed above, SeviceManager has to provide methods to start/stop all the service objects. Because the stop related methods are similar to start, we are still to abridge the stop service related code while only give the start related ones below.

Listing 3

public static void StartAllServices()
{
    foreach (ServiceObject obj in m_serviceList)
    {
        StartService(obj);
    }
}
public static void StartAllServices(ActiveMode activeMode)
{
    foreach (ServiceObject obj in m_serviceList)
    {
        if (obj.ActiveMode.Equals(activeMode))
        {
            StartService(obj);
        }
    }
}

Because the shortcut menu PopupMenu is associated with the ListView control, we can obtain the service object information through the currently-selected item on the ListView control when right-clicking the ListView control. For example, the Name of the service can be obtained through the following code.

Listing 4

String serviceName=lvService.SelectedItems[0].
 SubItems[0].Text;

In the ServiceManager definition, method StartService and StopService receivesthe object of the ServiceObject type. To invoke these methods only knowing of the service name is not enough, and we have to also provide the ServiceObject object. Therefore, we still need to define another method-- LookUpServiceObject, which seeks the related ServiceObject object in the m_serviceList object according to the given argument ServiceName.

Listing 5

protected static ServiceObject LookUpServiceObject(string serviceName)
{
    foreach (ServiceObject obj in m_serviceList)
    {
        if (obj.ServiceName.Equals(serviceName))
        {
            return obj;
        }
    }
    return null;
 
}

The menu item "Start--Start By Service Name" in the shortcut menu related Click event handler can be implemented below:

Listing 6

private void startByServiceNameToolStripMenuItem_Click(object sender, EventArgs e)
{
    string serviceName = lvService.SelectedItems[0].SubItems[0].Text;
    ServiceObject obj=ServiceManager.LookUpServiceObject(serviceName);
    if(obj!=null)
    {
        ServiceManager.StartService(obj);
    }
}

As for the click handler for the menu item "Start All", it is much simpler, as follows.

private void munStartAll_Click(object sender, EventArgs e)
{
    ServiceManager.StartAllServices();
}

For now, we have nearly accomplished the design of the service explorer application. Surveying the whole design, it is simple yet practical, which can meet the requirements brought out at the beginning. In the above programming, we have used OOP as much as possible. Although the design of class ServiceManager is not very elegant, while from the angle of availability, such a design is enough.

By digging deeper, we find out that the above design can still be improved, which can be done by introducing the Template Method pattern.

Introducing the Template Method Pattern to Improve the Service Explorer

In fact, in the ServiceManager definition, things are not as simple as imaged with only two methods, StartService and StartAllServices. With further examination, you can conclude ServiceManager should also include other methods, such as RegisterService, UnregisterService, etc. However, simply making use of the refactoring way "Extract Method," we cannot make more improvements with the ServiceManager class even exhausting all related tricks. At this time, we need to take up the "abstract" sword to build up a new type of structure as shown in Figure 2.

Figure 2

For the reader more clearly to hold tight upon the essence of problem, I abridged the two methods RegisterService and UnregisterService in the definition of class ServiceManager. As seen from Figure 2, class ServiceManager at this time should be an abstract one and its every method should all be defined as related to the instance object.

Now, let us delve into the main methods in class ServiceManager. First, the responsibility of method Initialize is to read the configuration document information, initialize the service object and add it into the m_serviceList object. This method will be invoked by the constructor function and used when the instances of sub-classes are created. On the other hand, there are maybe differences in special initializations when the instances of sub-classes initialize service objects (and the service objects to be initialized may also be different). In this case, we can conclude that it is unnecessary for class ServiceManager to initialize the service objects any more. Therefore, the Initialize method is defined as a protective abstract method.

The task of method LookUpServiceObject is still as before. It seeks in the m_serviceList the corresponding service object and then returns it. Because of the abstract reason, the m_serviceList object has now been defined the related instantiation field. At this time, it is not fit to define method LookUpServiceObject as a static method again. If we thoroughly inquiry into that method, we will discover the realization of that method actually undertakes the task of facilitating developers to start services, with not much relevance to the need of the client side. So, according to the principle of "Information Hiding," we do not have the necessity to expose such a method that has nothing to do with the user of the class. For this, we should set it as the type of protect for the purpose of encapsulation. Accordingly, we need to modify the StartService method, setting the parameter as the name of the service object, and then calling the LookUpServiceObject method inside the method to find out the corresponding service object and work with it.

Now, the two methods of starting service are both modified as public. Method StartService only starts a service object, while the StartAllServices method starts all the matched service objects.

Next, let us take a closer look at the StartAllService method. In this method, the m_serviceList object is looped through to locate each matched service object, and then invoke its StartService method. Note that although the m_serviceList object is acquired through method lnitialize, we still need to judge the activating mode of the service object when the StartAllService method is executed to avoid exceptions when starting the service object. However, there exists a problem that we have to judge the activating mode of the service object according to the type of the sub class-- we cannot obtain that information in the parent class. As for method StartService, it is tightly relevant to the type of the sub class-- the logic of starting services differs with different kinds of activating modes of the service objects.

To resolve such a problem, we can fall back upon the Template Method pattern to define a special abstract method GetActiveMode to acquire activating modes of the service objects. The reason that we define method GetActiveMode as an abstract method is the implementation of that method is accomplished inside the sub classes, through which we can acquire the proper activating mode of the service objects.

Similarly, the StartService method in the abstract class should also be defined as an abstract one. As for the StartAllServices method, except that the acquirement of the proper activating mode of the service objects needs to be a sub class implementation, the behavior of looping through the m_serviceList object has nothing to do with sub classes. Therefore, it can be implemented in the parent class. Moreover, the other two methods GetActiveMode and StartService will be called inside method StartAllServices. The related code is shown in Listing 7.

Listing 7

public abstract class ServiceManager
{
    protected List<ServiceObject> m_serviceList = new List<ServiceObject>();
    public ServiceManager()
    {
        Initialize();
    }
    public List<ServiceObject> ServiceList
    {
        get { return m_serviceList; }
    }
 
    protected ServiceObject LookUpServiceObject(string serviceName)
    {
        foreach (ServiceObject obj in m_serviceList)
        {
            if (obj.ServiceName.Equals(serviceName))
            {
                return obj;
            }
        }
        return null;
    }
    public void StartAllServices()
    {
        foreach (ServiceObject obj in m_serviceList)
        {
            if (obj.ActiveMode.Equals(GetActiveMode()))
            {
                StartService(obj.ServiceName);
            }
        }
    }
    protected abstract void Initialize();
    protected abstract ActiveMode GetActiveMode();
    public abstract void StartService(string serviceName);
}

Notice the access level of method GetActiveMode and StartService. The StartService method is to be invoked by exterior classes, so we set it as public. However, method GetActiveMode can only be called by ServiceManager and its sub classes, so we set it as a protected method.

Although the StamAllServices method provides a concrete implementation, it uses two abstract methods inside, which stay to be implemented in sub classes. This is just like when we define a set of templates in the parent class, and then provide a concrete implementation in sub classes according to the related template. This is exactly what the Template Method pattern means.

For example, the definition of class SingletonServiceManager is as follows.

Listing 8

public class SingletonServiceManager : ServiceManager
{
    public SingletonServiceManager() : base() { }
 
    protected override void Initialize()
    {
        //initialize all the service objects that
        //use Singleton mode, and add them into
        // the m_serviceList object…
    }
 
    protected override ActiveMode GetActiveMode()
    {
        return ActiveMode.Singleton;
    }
    
    protected override void StartService(string serviceName)
    {
        ServiceObject obj= LookUpServiceObject(
                  serviceName);
        if (obj!=null)
        {
           If(obj.CurrentState.Equals(ServiceState.
                                    Stop))
           {
              obj.CurrentState = ServiceState.Start;
           }
        }
}

Now, you see, by making use of an abstract way, we only did very small adjustment to the whole structure, but this small one step set a good keynote.

Is There Anything Wrong?

However, when I am hugging myself for the above modifications, a practical problem hits me in the head. As depicted above, when the user right clicks the items in the ListView control, we have to decide to which instance of the sub classes to establish according to the related info of the currently-clicked service object in the ListView control. To gain a more intuitive understanding with this, look at the following.

Listing 9

private void startByServiceNameToolStripMenuItem_Click(object sender, EventArgs e)
{
    string serviceName = lvService.SelectedItems[0].SubItems[0].Text;
    string activeType= lvService.SelectedItems[0].SubItems[2].Text;
 
    Switch (activeType)
    {
      Case activeType.Singleton.ToString():
            ServiceManager=new SingletonServiceManager();
            break;
      Case activeType.SingleCall.ToString():
            ServiceManager=new SingleCallServiceManager();
            break;
      Case activeType.Activation.ToString():
            ServiceManager=new ActivationServiceManager();
            break;
    }
    ServiceManager.StartService(serviceName);
}

You can easily discover that we have made the greatest efforts to eliminate the use of the switch sentence for now. However, the switch sentence, like a "phantom," again appears before us. The new design hands over the branch sentence related trouble to the caller. That is to say, the existing structure still bears some fatal flaw in terms of availability.

There is also a problem with the implementation of method StartAllServices. If we invoke the method StartAllServices of SingletonServiceManager, it only means to start the Singleton mode related service objects. Therefore, if we want to start all service objects, we have to create all the sub objects of ServiceManager, and then call their related StartAllServices methods. In this way, the code of the caller will become rather complicated and cause the low performance as well.

Listing 10

private void munStartAll_Click(object sender, EventArgs e)
{
    ServiceManager m1,m2,m3;
    m1=new SingletonServiceManager();
    m2=new SingleCallServiceManager();
    m3=new ActivationServiceManager();
    m1.StartAllServices();
    m2.StartAllServices();
    m3.StartAllServices();
}

Obviously, the Template Method pattern efficiently improved the reusability of code, while at the same time increased the invoking difficulty of the caller. Such a result is not what we would like to see.

Falling Back on the Chain of Responsibility Pattern to Better the Design

Let us again recall the intent of the Chain of Responsibility pattern: "Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it." As you see, the advantage of the Chain of Responsibility pattern lies in that it is able, according to the specified condition, to locate the suitable object intelligently and performs its responsibility. The present design lacks some method that can judge given conditions intelligently and then instantiate the proper ServiceManager object.

According to the above analysis, to introduce the Chain of Responsibility pattern, we need to set up a linked table, like structure for class ServiceManager to store the ServiceManager typed objects. For this, we have to again modify class ServiceManager; Figure 3 shows the modified class diagram and its related derivations.

Figure 3: The modified ServiceManager class and its derivatives

Careful readers may catch sight of the difference between Figure 2 and Figure 3. Here, it is added a field m_manager of ServiceManager type. It is just through this field that we can achieve the Chain of Responsibility. Now, when we start the service, we need, according to the given conditions, to judge whether to execute the StartService method of current class. If not, then continue to judge the value of m_manager, and if m_manager is not empty, then invoke its related method.

For the sake of facilitating code reusing, I have also introduced a new method, IntemalStartService, with which to replace the concrete task of the old StartService method to be exclusively responsible for starting service objects. Because the implementation of the IntemalStartService method is decided by concrete sub classes, we define it as a protective abstract method. As for method StartService, due to the responsibility chain, it needs to acquire the activating mode of service objects. And since the activating mode is decided by judging the sub class type, it is defined as a public abstract method.

The key is the StartAllServices method. Now, this method undertakes two kinds of different responsibilities. One is to start all service objects; the other is to activate the related service object according to the specified activating mode. As a result, we need to define two methods both named StartAllServices, but with different signatures when defining the abstract ServiceManager class. In addition, we need to make small adjustment to the design of the responsibility chain pattern. In implementing method StartAllServices, whether the current object carries out this method or not, we have to carry out the StartAllServices method of the next object in the responsibility chain. Only in this way can we then start all the service objects. Because the StartAllServices method is relevant to the activating mode of service objects we still define it as a public abstract method.

In addition, in the abstract ServiceManager we have also defined a protected typed field m_hasNextManager to judge whether there exists the next object in the responsibility chain. Actually, the purpose of this field is to judge whether the m_manager object is empty. If empty, then the m_manager field is empty. When we add the ServiceManager object through method AddSeiviceManage we set the m_manager object to be empty.

Let us now take a look at the new definition of the abstract class ServiceManager, as shown in Listing 11.

Listing 11

public abstract class ServiceManager
{
    protected bool m_hasNextManager;
    protected ServiceManager m_manager;
    protected List<ServiceObject> m_serviceList = new List<ServiceObject>();
    public ServiceManager()
    {
        Initialize();
        m_hasNextManager = false;
    }
    public List<ServiceObject> ServiceList
    {
        get { return m_serviceList; }
    }
 
    protected virtual void Initialize()
    {
        //omit the detailed implementation...
    }
    
    protected ServiceObject LookUpServiceObject(string serviceName)
    {
        //just as before, omitted…
 
    }
    public void AddServiceManager(ServiceManager manager)
    {
        m_manager = manager;
        m_hasNextManager = true;
    }
    protected abstract ActiveMode GetActiveMode();
    protected abstract void InternalStartService(ServiceObject obj);
    //…(omitted)
}
 

Accordingly, we should modify the sub classes. Here, take class SingletonSetviceManager as an example.

Listing 12

public class SingletonServiceManager : ServiceManager
{
    public SingletonServiceManager() : base() { }
    protected override ActiveMode GetActiveMode()
    {
        return ActiveMode.Singleton;
    }
    
    protected override void InternalStartService(ServiceObject obj)
    {
        if (obj.CurrentState == ServiceState.Stop)
        {
            RemotingConfiguration.RegisterWellKnownServiceType(obj.ObjectType, 
              obj.ServiceName, WellKnownObjectMode.Singleton);
            obj.CurrentState = ServiceState.Start;
        }
    }
 
    protected override void StartService(string serviceName)
    {
        ServiceObject obj= LookUpServiceObject(
                  serviceName);
        if (obj!=null)
        {
           if(obj.CurrentState.Equals(ActiveMode.
                                    Singleton))
           {
           }
           else
                   {
                          if(m_hasNextManager)
                   {
                  m_manager.StartService(serviceName);
                            }
                    }
        }
    }
}
    public override void StartAllServices()
    {
      for(ServiceObject obj in m_serviceList)
      {
                  if(obj.ActiveMode.Equals(ActiveMode.Singleton))
            {
                  InternalStartService(obj);
            }
      }
      if(m_hasNextManager)
      {
            m_manager.StartAllServices();
      }
    }
    public override void StartAllServices(ActiveMode activeMode)
    {
      if(activeMode.Equals(ActiveMode.Singleton))
      {
            for(ServiceObject obj in m_serviceList)
            {
                  if(obj.ActiveMode.Equals(activeMode))
            {
                  InternalStartService(obj);
            }
            }
      else
             if(m_hasNextManager)
            {
                  m_manager.StartAllServices(activeMode);
            }
    }
}

Because the implementations of methods StartService and StartAllServices are both put into sub classes, at this time, in the parent class the two protect abstract methods GetActiveMode and IntemalStartService lose existent meaning. Our design, for now, obviously has some problems. By carefully examining all the sub classes of the parent class ServiceManager, it is not difficult to find that whether in the StartService method or the startAllSerVices method it still rests upon the value of ActiveMode to select the mode to run. Therefore, we still can pick up the Template Method pattern to abstract the similar implementation logic into the parent class, which can reduce the repeated code that is before defined in sub classes. Hence, the final design of class ServiceManager should look like that shown below.

Listing 13

public abstract class ServiceManager
{
    protected bool m_hasNextManager;
    protected ServiceManager m_manager;
    protected List<ServiceObject> m_serviceList = new List<ServiceObject>();
    public ServiceManager()
    {
        Initialize();
        m_hasNextManager = false;
    }
    public List<ServiceObject> ServiceList
    {
        get { return m_serviceList; }
    }
 
    public static ServiceManager GetServiceManager()
    {
        SingletonServiceManager stManager = new SingletonServiceManager();
        SingleCallServiceManager scManager = new SingleCallServiceManager();
        ActivationServiceManager acManager = new ActivationServiceManager();
        acManager.AddServiceManager(stManager);
        stManager.AddServiceManager(scManager);
        return acManager;
    }
 
    protected virtual void Initialize()
    {
        //omit the concrete implementation...
    }
    
    protected ServiceObject LookUpServiceObject(string serviceName)
    {
        foreach (ServiceObject obj in m_serviceList)
        {
            if (obj.ServiceName.Equals(serviceName))
            {
                return obj;
            }
        }
        return null;
 
    }
    public void AddServiceManager(ServiceManager manager)
    {
        m_manager = manager;
        m_hasNextManager = true;
    }
    public void StartService(string serviceName)
    {
        ServiceObject obj = LookUpServiceObject(serviceName);
        if (obj != null)
        {
 
            InternalStartService(obj);
        }
        else
        {
            if (m_hasNextManager)
            {
                m_manager.StartService(serviceName);
            }
        }           
    }
    public void StartAllServices()
    {
        foreach (ServiceObject obj in m_serviceList)
        {
            if (obj.ActiveMode.Equals(GetActiveMode()))
            {
                InternalStartService(obj);
            }
        }
        if (m_hasNextManager)
        {
            m_manager.StartAllServices();
        }
    }
    public void StartAllServices(ActiveMode activeMode)
    {
        if (activeMode.Equals(GetActiveMode()))
        {
            foreach (ServiceObject obj in m_serviceList)
            {
                if (obj.ActiveMode.Equals(activeMode))
                {
                    InternalStartService(obj);
                }
            }
        }
        else
        {
            if (m_hasNextManager)
            {
                m_manager.StartAllServices(activeMode);
            }
        }
    }
 
    public void StopService(string serviceName)
    {
        ServiceObject obj = LookUpServiceObject(serviceName);
        if (obj != null)
        {
 
            InternalStopService(obj);
        }
        else
        {
            if (m_hasNextManager)
            {
                m_manager.StopService(serviceName);
            }
        }
    }
    public void StopAllServices()
    {
        foreach (ServiceObject obj in m_serviceList)
        {
            if (obj.ActiveMode.Equals(GetActiveMode()))
            {
                InternalStopService(obj);
            }
        }
        if (m_hasNextManager)
        {
            m_manager.StopAllServices();
        }
    }
    public void StopAllServices(ActiveMode activeMode)
    {
        if (activeMode.Equals(GetActiveMode()))
        {
            foreach (ServiceObject obj in m_serviceList)
            {
                if (obj.ActiveMode.Equals(activeMode))
                {
                    InternalStopService(obj);
                }
            }
        }
        else
        {
            if (m_hasNextManager)
            {
                m_manager.StopAllServices(activeMode);
            }
        }
    }
    protected abstract ActiveMode GetActiveMode();
    protected abstract void InternalStartService(ServiceObject obj);
    protected abstract void InternalStopService(ServiceObject obj);
}

So, the definitions of the sub classes of class ServiceManager become much simpler. The crucial part in the class SingletonServiceManager is listed below. For more details and the other two sub classes SingleCallServiceManager and ActivationServiceManager, please refer to the download source code at the end of this article.

Listing 14

public class SingletonServiceManager : ServiceManager
{
    public SingletonServiceManager() : base() { }
 
    protected override ActiveMode GetActiveMode()
    {
        return ActiveMode.Singleton;
    }
    
    protected override void InternalStartService(ServiceObject obj)
    {
        if (obj.CurrentState == ServiceState.Stop)
        {
            RemotingConfiguration.RegisterWellKnownServiceType(obj.ObjectType, 
              obj.ServiceName, WellKnownObjectMode.Singleton);
            obj.CurrentState = ServiceState.Start;
        }
    }
      //…(omitted)
}

Due to reasonably making use of the Template Method pattern (a large part of the implementation logics are shifted into the abstract parent class), the definitions and implementations seem much concise. And again, because we have introduced the Chain of Responsibility pattern, the suitable service object can be found out intelligently, which greatly simplifies the caller. You can refer to the following code to find out this simplicity.

Listing 15

public partial class MainForm : Form
{
    private ServiceManager acManager;
    public MainForm()
    {
        InitializeServiceManager ();
    }
    private void InitializeServiceManager ()
      {
       SingletonServiceManager stManager = new SingletonServiceManager();
       SingleCallServiceManager scManager = new SingleCallServiceManager();
       acManager = new ActivationServiceManager();
       stManager.AddServiceManager(scManager);
       acManager.AddServiceManager(stManager);
      }
    private void munStartAll_Click(object sender, EventArgs e)
    {
        acManager.StartAllServices();
        InitListView();
    }
 
    private void startByServiceNameToolStripMenuItem_Click(object sender, EventArgs e)
    {
        if (lvService.SelectedItems.Count > 0)
        {
            string serviceName = lvService.SelectedItems[0].SubItems[0].Text;
            acManager.StartService(serviceName);
            InitListView();
        }
    }
}

As you have seen, we have built up a responsibility chain with the help of the InitializeServiceManager method, as shown in Figure 4. When you invoke the StartService method of the acManager object, no matter which kind of activating mode of the currently-selected remote object is, we can find out the appropriate matched ServiceManager object to execute.

Figure 4 - The Responsibility Chain Diagram

Although we have, in the above code, used the InitializeServiceManager method to succeed in establishing a responsibility chain; however, as I have pointed out previously, we would better provide a specialized class or method to be responsible for such kink of job. For simplicity, we can directly define a static method named GetServiceManager in class ServiceManager to bear this task.

Listing 16

namespace DonOfDesign.PracticeCORPattern.ServiceManagerLib
{
    public abstract class ServiceManager
    {
        public static ServiceManager GetServiceManager()
        {
            SingletonServiceManager stManager = new SingletonServiceManager();
            SingleCallServiceManager scManager = new SingleCallServiceManager();
            ActivationServiceManager acManager = new ActivationServiceManager();
            acManager.AddServiceManager(stManager);
            stManager.AddServiceManager(scManager);
            return acManager;
        }
   //…(omitted)

A very small improvement can vastly simplifies the calling code. In this way, we can remove the InitializeServiceManager method from class MainFom, and modify the code of the constructor function.

Listing 17

public MainForm()
{
    acManager =ServiceManager.GetServiceManager();
}

Now, let us take a quick look at the running order of method StartService of the acManager object from the point view of the caller. Suppose that the currently-selected service object will be called in the SingleCall way, and then it is executed in the following order:

(1) Invoke the StartService method of the ActivationServiceManager object.

(2) Since the GetActiveMode method returns a non-Activation value, execute the else branch.

(3) Judge whether hasNextManager is true or not. Because the acManager object invoked the AddServiceManager method, hasNextManager should be true, and the value of the private field m_manager is stManager.

(4) Invoke the StartService method of the SingletonServiceManager object.

(5) Repeat step (2), and if the active mode still does not match, then execute the else branch.

(6) Because the stManager object invoked the AddServiceManager method, the value of hasNextManager was true and the value of m_manager is scManager. So, the StartService method of the SingleCallServiceManager object is invoked.

(7) At this time, the active mode matches the given condition; then the InternalStartService method of the SingleCallServiceManager object is called to start related service.

As for the StartAllServices method of the acManager object, it has hypostatic differentiation with the StartService method. Superficially seen, it only carried out the StartAllServices method of an object; however, it can access all the elements in the responsibility chain. As a result, it actually calls all the StartAllServices methods of acManager, stManager and scManager, which starts all service objects.

Downloads

Summary

In this article, we have succeeded in building up a remote service manager application. By introducing the Chain of Responsibility design pattern and reasonably applying the Template Method pattern, we have not only removed the switch branch sentence in the original design, but guaranteed code reusing as much as possible. Although, in this simple service manager application, we have not provided more extension points to enhance the system for the future and the introduction of the both patterns above have not shown apparent privileges, the whole design seems elegant and filled with the beauty of design.

On the other hand, purely for the sake of showing off the beauty of design and increasing the complexity of design seems "concentrating on too many details with excessive design." But, put in the larger application environment, the design of the latter doubtless has more general use.

Therefore, to weigh whether a design is an "excessive design" or not needs to be evaluated and analyzed deliberatively and from several angles. In the practical scenarios, with the help of the design patterns we are probable to establish better designs. However, whether it is indeed necessary to introduce the related design patterns depends upon the facts in the end.



User Comments

No comments posted yet.

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-24 12:23:51 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search