This article uses a workflow module in the project to demonstrate the combined implementation of the responsibility chain mode, policy mode, and command mode!
Process Overview
A recent project involves a process requirement. Service Process for the Construction Machinery Industry: service task process and spare parts delivery process.
At the beginning of the project, the requirements were not very clear. It is an evolutionary model. A simple version is provided first, and new requirements are further explored based on the user's usage. That is to say, each step in these two processes is not fixed for the time being, but should be configurable and can be increased or decreased.
The two tentative processes are as follows:
The above is the general process of the two processes. Of course, other processes may be required in the actual process.
However, after careful analysis, you will see. No matter how many intermediate steps there are, they always correspond to their statuses in the process:
/// <Summary> <br/> // Service Process status enumeration <br/> /// </Summary> <br/> Public Enum maintanstateenum <br/> {<br/> non_assign, // created, to be allocated <br/> non_accept, // allocated, to be received <br/> maintaining, // received, in service <br/> non_confirm, // complete the service, to be confirmed <br/> non_userconfirm, // confirmed, to be confirmed by the customer <br/> non_feedback, // confirmed by the customer, wait for a return visit <br/> feedbacked, // The return visit is complete, and the process is complete <br/> Goback // return the allocation. This is an action. To facilitate coding, no corresponding service status <br/>}
You will see that the following actions are followed by non. It is important to clarify the status and action here, otherwise it will be difficult to clarify. Sometimes an action corresponds to the frontend and backend States and does not have repeated states, such as created (created successfully) and non_assign (to be allocated.
These statuses are actually the main line of the entire process, similar to the main road of a city. We only need to grasp the clues of such a day to think about it, and we will be able to simplify it.
Each step can be configured, and each step is not coupled to achieve call-side consistency-the responsibility chain mode
The responsibility chain model is born for this!
Here, I use the responsibility chain model to encapsulate the changes brought about by the uncertainty of such steps.
First, we need to first understand what is the responsibility chain model:
Chain of responsibility: Enables multiple objects to process requests to avoid coupling between request senders and receivers. Connect these objects into a chain and pass the request along the chain until an object processes it.
Applicable scenarios:
1. There are multiple objects that can process a request, and the object that processes the request is automatically determined at the runtime;
2. If the recipient is not explicitly specified, submit a request to one of multiple objects;
3. The object set for processing a request should be dynamically specified.
We can see that no matter which scenario is above, there is a many-to-one relationship. In this process, a service flow is clearly matched (in other words, it is a record of database service tasks ). In most cases, we are modifying related fields in a service and setting different service statuses. Many of them correspond to different steps here.
Let's take a look at the implementation:
/// <Summary> <br/> // service processor-abstract base class <br/> /// </Summary> <br/> public abstract class servicehandler <BR/>{< br/> protected servicehandler nexthandler; </P> <p> Public Virtual void handle (maintenanceform, object otherparams) <br/>{< br/> If (nexthandler! = NULL) <br/>{< br/> nexthandler. Handle (maintenanceform, otherparams); <br/>}</P> <p>}
This is an abstract processor. The processor for each step in the process will overwrite the processing method of this processor:
Create:
/// <Summary> <br/> // create a service ticket process -- handle the operator/service receiver role [or other roles with creation permissions] <br/> // /</Summary> <br/> public class createhanlder: servicehandler <br/>{< br/> Public override void handle (maintenanceform, object otherparams) <br/>{< br/> // If created (Not allocated) <br/> If (maintenanceform. currentstate = maintanstateenum. non_assign) <br/>{< br/> // create the service ticket </P> <p> return; <br/>}< br/> else // allocated, it is passed to the next process (assuming the allocation process) <br/>{< br/> base. handle (maintenanceform, otherparams ); <br/>}</P> <p> // set the next processing procedure <br/> Public servicehandler nexthandler <br/>{< br/> set <br/>{< br/> base. nexthandler = value; <br/>}< br/>}<textarea readonly name="code" class="csharp">}</textarea>
Allocation:
/// <Summary> <br/> // service process allocation process-assign personnel roles such as the service director (or other persons with assigned permissions). <br/>/ /// </Summary> <br/> public class assignhandler: servicehandler <br/>{< br/> Public override void handle (model. maintenanceform, object otherparams) <br/>{< br/> // if the current status is allocated (not received) <br/> If (maintenanceform. currentstate = maintanstateenum. non_accept) <br/>{< br/> // allocate </P> <p> // record the log <br/> log (maintenanceform ); <br/> return; <br/>}< br/> else <br/> {<br/> // enter the next process <br/> base. handle (maintenanceform, otherparams ); <br/>}</P> <p> Public servicehandler nexthandler <br/>{< br/> set <br/> {<br /> base. nexthandler = value; <br/>}< br/>}
I will not post other processes one by one Code . Next, let's analyze the special features of these processors. First, we can see that it is State-oriented (in the handle method's judgment ). Each step has a nexthandler attribute that is used to configure the next processor, which can concatenate them into a process. You can see that each handle method has a "return;" statement at the end. Yes. Here I use an incomplete responsibility chain model. That is to say, a process does not end at a time, because it may not be consistent in time, or a person may not finish all the processes, for example, a person is responsible for creating a task list or assigning a task.
Let's take a look at how to connect them to a process:
// Initialize the service process chain <br/> static servicehandler initservicechain () <br/>{< br/> // The responsibility chain mode is applied here, in this way, there is only one entry for processing the repair service ticket <br/> // The current service status can be redirected to a specific processing process [chained process] </P> <p> createhanlder createhandler = new createhanlder (); <br/> assignhandler = new assignhandler (); <br/> finishhandler = new finishhandler (); <br/> feedbackhandler = new feedbackhandler (); </P> <p> // explicitly specify the process chain <br/>/* <br/> * Note: The process can be increased or decreased, <br/> */<br/> createhandler. nexthandler = assignhandler; <br/> assignhandler. nexthandler = finishhandler; <br/> finishhandler. nexthandler = feedbackhandler; <br/> feedbackhandler. nexthandler = NULL; </P> <p> // return process inspiration, similar to the head pointer of a linked list structure <br/> return createhandler; <br/>}
The second code block in the preceding Section implements assembly and integration of all processors (for example, not complete)
Let's take a look at the client call:
Static void main (string [] ARGs) <br/>{< br/> servicehandler = initservicechain (); <br/> // Application Scenario: </P> <p> // scenario 1 [describe how to create a service order] <br/> // click Add to bring up a window, create a repair service form <br/> maintenanceform newmaintenanceform = new maintenanceform (); <br/> newmaintenanceform. creator = "YH"; <br/> newmaintenanceform. createdtime = datetime. now; <br/> newmaintenanceform. currentstate = maintanstateenum. non_assign; </P> <p> servicehandler. handle (newmaintenanceform, null ); // create </P> <p> // scenario 2 [demo allocation process] <br/> // select a repair ticket from the list of unassigned repair tickets, click Allocate <br/> maintenanceform = new maintenanceform (); <br/> maintenanceform. lastmodifiedtime = datetime. now; <br/> maintenanceform. currentstate = maintanstateenum. non_accept; </P> <p> servicehandler. handle (maintenanceform, null); // create (skip)-> assign </P> <p> // scenario 3: demonstrate that the assigned repair personnel are returned, re-allocation process] <br/> // select a repair ticket from the returned repair ticket list and click Allocate/re-allocate <br/> maintenanceform. lastmodifiedtime = datetime. now; <br/> maintenanceform. currentstate = maintanstateenum. goback; </P> <p> servicehandler. handle (maintenanceform, null); // create (skip) -> allocate </P> <p> // scenario 4 [demo acceptance process] <br/> // select a record in the allocated to be completed form, click the accept task button <br/> maintenanceform. lastmodifiedtime = datetime. now; <br/> maintenanceform. currentstate = maintanstateenum. maintaining; </P> <p> servicehandler. handle (maintenanceform, null); // create (skip)-> allocate (skip) -> Accept </P> <p> // scenario 5 [demo return visit process] <br/> // select a record from the completed return visit form, click the "Return Visit" button <br/> maintenanceform. lastmodifiedtime = datetime. now; <br/> maintenanceform. currentstate = maintanstateenum. feedbacked; </P> <p> servicehandler. handle (maintenanceform, null); // create (skip)-> allocate (skip)-> Accept (skip )...... -> return visit </P> <p> console. read (); <br/>}
No matter which process is followed, there is only one method to call:
Servicehandler. Handle (maintenanceform, null );
The first parameter is the object of the task, and the second parameter is the attached parameter (if not, you do not need to pass it ). In any practical experience transmitted in the process chain, the current state of the entity guides the handler to which it is handed for processing. In this way, no matter how steps change in your process, the call method at the call end is unique. In addition, your increase and decrease steps have no impact on other processes. The only thing you need to change is to assemble them. For example, if you need to follow a review process after creating a service ticket, adding it is a very simple action.
I will not give an example of the shipping process here, which is similar.
Implementation of different logging methods in each step-policy Mode
There may be various requirements for log records, such as sending emails to remote engineers or some leaders, and storing them in databases or flat file backups .....
The details of the Rule mode will not be described in detail. Let's look at the implementation:
<textarea readonly name="code" class="csharp">/// <Summary> <br/> // abstract log record policy class <br/> /// </Summary> <br/> public abstract class logstrategy <br/> {<br/> public abstract void log (Object OBJ ); <br/>}</textarea>
<textarea readonly name="code" class="csharp">/// <Summary> <br/> // database policy -- service logging implementation class <br/> /// </Summary> <br/> public class servicedatabaselogstrategy: logstrategy <br/>{< br/> Public override void log (Object OBJ) <br/>{< br/> If (OBJ = NULL) <br/>{< br/> throw new nullreferenceexception ("OBJ"); <br/>}</P> <p> maintenanceform = OBJ as maintenanceform; <br/> // insert a database <br/> console. writeline ("database logging"); <br/> console. writeline (); <br/>}< br/>}</textarea>
<textarea readonly name="code" class="csharp">/// <Summary> <br/> // email policy -- service logging implementation class <br/> // some roles that do not pay attention to details <br/> /// (for example, a leader, they only pay attention to the results, but they need to grasp the macro level. Then, after completing the service steps, logs can be sent to the leadership email via email) <br/> // </Summary> <br/> public class serviceemaildatabaselogstrategy: logstrategy <br/>{< br/> Public override void log (Object OBJ) <br/>{< br/> If (OBJ = NULL) <br/>{< br/> throw new nullreferenceexception ("OBJ "); <br/>}</P> <p> maintenanceform = OBJ as maintenanceform; <br/> // send to the specified mailbox for Operation <br/> console. writeline ("email logging"); <br/> console. writeline (); <br/>}< br/>}</textarea>
<textarea readonly name="code" class="csharp">/// <Summary> <br/> // flat file policy-Service Log implementation class <br/> // supports text or XML documents, easy to view and interact <br/> /// </Summary> <br/> public class servicefiledatabaselogstrategy: logstrategy <br/>{< br/> Public override void log (Object OBJ) <br/>{< br/> If (OBJ = NULL) <br/>{< br/> throw new nullreferenceexception ("OBJ"); <br/>}< br/> console. writeline ("file logging"); <br/> console. writeline (); <br/>}< br/>}</textarea>
The above is the implementation of several tentative logging policies. Let's take a look at how to combine them into various processors. First of all, the abstract base class of the processor just now has a virtual method for logging:
/// <Summary> <br/> // service processor-abstract base class <br/> /// </Summary> <br/> public abstract class servicehandler <BR/>{< br/> protected servicehandler nexthandler; </P> <p> Public Virtual void handle (maintenanceformmaintenanceform, object otherparams) <br/>{< br/> If (nexthandler! = NULL) <br/>{< br/> nexthandler. handle (maintenanceform, otherparams); <br/>}</P> <p> Public Virtual void log (maintenanceformmaintenanceform) <br/>{< br/> // record data to the database </P> <p >}< br/>}
The abstract class above implements that, by default, records into the database
In fact, the processing of log records also exists in the processor of each step (just to avoid interfering with the responsible chain, but omitted)
Public classcreatehanlder: servicehandler <br/>{< br/> Public override voidhandle (maintenanceform, object otherparams) <br/>{< br/> // if not allocated <br/> If (maintenanceform. currentstate = maintanstateenum. non_assign) <br/>{</P> <p> // log <br/> log (maintenanceform); <br/> return; <br/>}< br/> else // allocated, it is passed to the next process (assuming the allocation process) <br/>{< br/> base. handle (maintenanceform, otherparams ); <br/>}</P> <p> // set the next processing procedure <br/> Public servicehandler nexthandler <br/>{< br/> set <br/>{< br/> base. nexthandler = value; <br/>}</P> <p> Public logstrategy {Get; set ;} </P> <p> Public override void log (maintenanceformmaintenanceform) <br/>{< br/> // if no logging policy is explicitly specified, call the basic record method [Save to dB] <br/> If (logstrategy = NULL) <br/>{< br/> base. log (maintenanceform); <br/>}< br/> else <br/>{< br/> logstrategy. log (maintenanceform); <br/>}< br/>
Here there is a logstrategy (log policy base class) type attribute to provide external configuration interfaces. Each processor class overwrites the LOG method of the processor base class. The logic is: if there is a logging policy, it is recorded by the logging policy, otherwise, use the record method of the base class (DB mode ).
Before the handle method is returned, the overwritten LOG method is called.
Let's take a look at how the logging policy is assembled outside:
Static servicehandlerinitservicechain () <br/>{< br/> // The responsibility chain mode is applied here, in this way, there is only one entry for processing the repair service ticket <br/> // The current service status can be redirected to a specific processing process [chained process] </P> <p> createhanlder createhandler = newcreatehanlder (); <br/> assignhandler = newassignhandler (); <br/> finishhandler = newfinishhandler (); <br/> feedbackhandler = new feedbackhandler (); </P> <p> // explicitly specify the process chain <br/>/* <br/> * Note: The process can be increased or decreased, <br/> */<br/> createhandler. nexthandler = assignhandler; <br/> assignhandler. nexthandler = finishhandler; <br/> finishhandler. nexthandler = feedbackhandler; <br/> feedbackhandler. nexthandler = NULL; </P> <p> // explicitly specify a logging policy <br/>/* <br/> * supports multiple logging policies (including dB, email, and file) <br/> * if not configured, the default record method is dB <br/> * This shows configurability-some top-level users only pay attention to the results <br/> * (that is, they may only pay attention to 'service completion 'process, then the logging policy of the process can be set to email) <br/> * the configuration can also be written to the configuration file, multiple logging methods can also be supported for a process <br/> */</P> <p> // example of a logging policy: <br/> createhandler. logstrategy = newservicedatabaselogstrategy (); // dB <br/> assignhandler. logstrategy = newservicefiledatabaselogstrategy (); // file <br/> finishhandler. logstrategy = newserviceemaildatabaselogstrategy (); // email <br/> feedbackhandler. logstrategy = newserviceemaildatabaselogstrategy (); // email </P> <p> // return process inspiration, similar to the header pointer of a linked list structure <br/> return createhandler; <br/>}
We can see that the logging policy is assembled at the same time during the assembly process. In fact, each process corresponds to only one policy. Of course, you can configure several logging policies for a process (changed to list <logstrategy>, then it is called in the log method of the processor ).
Processing database processing logic call end consistency-- Command mode
I don't know if everyone is used to using a three-tier architecture to deal with general projects. In our project, The bll layer is a zombie. This is a fact (not to debate it, whether it is yours or not. Our project is actually, right or wrong, everyone knows ). Here, I have changed the inconsistency of various complicated xxxbll. XXX () Methods used by database operation callers in the past. The command mode is used to add, modify, delete, and query databases. As for the reason-the business is implemented by various processors and there is no need to adopt the BLL form. At the same time, the calling end of my database operation method is always encapsulated internally.
See the implementation:
/// <summary> <br/> // command interface -- supported: undo/Redo operation <br/> /// </Summary> <br/> Public interface icommand <br/> {<br/> // object undo (); </P> <p> Object execute (); </P> <p> // object Redo (); <br/>}
command implementation:
/// <summary> <br/> // create a service task LIST Command-command mode <br />/// </Summary> <br/> Public classcreatemaintenanceformcommand: icommand <br/>{< br/> private role; <br/> publiccreatemaintenanceformcommand (maintenanceform _ maintenanceform) <br/>{< br/> maintenanceform = _ maintenanceform; <br/>}</P> <p> Public object execute () <br/>{< br/> // string STR = "insertinto .... "; <br/> // dB. executenonquery (....); <br/> return NULL; <br/>}< br/>
/// <Summary> <br/> /// maintenance task allocation command implementation <br/> /// </Summary> <br/> Public classassignmaintenanceformcommand: icommand <br/> {<br/> private role; <br/> publicassignmaintenanceformcommand (maintenanceform _ maintenanceform) <br/>{ <br/> maintenanceform = _ maintenanceform; <br/>}</P> <p> Public object execute () <br/> {<br/> // throw new notimplementedexception (); <br/> return NULL; <br/>}< br/>}
You can also see how to call a single processor when creating a service:
Public classcreatehanlder: servicehandler <br/>{< br/> Public override voidhandle (maintenanceform, object otherparams) <br/>{< br/> // if not allocated <br/> If (maintenanceform. currentstate = maintanstateenum. non_assign) <br/>{</P> <p> // create a service order command and execute <br/> icommand createcommand = newcreatemaintenanceformcommand (maintenanceform ); <br/> createcommand. execute (); </P> <p> // log <br/> log (maintenanceform); <br/> return; <br/>}< br/> else // allocated, it is passed to the next process (assuming the allocation process) <br/>{< br/> base. handle (maintenanceform, otherparams); <br/>}
All parameters are passed in the constructor of each command, so it is flexible. The additional parameters are from the otherparams of the handle method, so there is no limit on parameter passing.
Of course, the Undo and redo commands are not implemented here.
Summary
The above are the three design modes used by the Service Process-responsibility chain mode, policy mode, and command mode. I am not very interested in the design. These are some of my ideas. I think it is of some guiding significance to design simple workflows or processes that are of a strong nature!