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.