Reputation: 3153
I quite understand the basics of the Chain of Responsibility pattern. However, I would like to ask if it is possible to set the next receiver in the sequence dynamically.
The basic idea is that depending on the sequence of the approver, it will instantiate the next object.
Below is the example:
Abstract Base Class
public abstract class ApproverCategorizer
{
protected ApproverCategorizer NextApprover { get; private set; }
public ApproverCategorizer RegisterNextApprover(ApproverCategorizer nextApprover)
{
NextApprover = nextApprover;
return nextApprover;
}
public abstract void ApproveAmount(TestEntity entity);
protected bool IsLast(Queue<string> approverQueue)
{
return string.IsNullOrEmpty(approverQueue.Peek());
}
}
Officer approver class
public class OfficerAApprover : ApproverCategorizer
{
public override void ApproveAmount(TestEntity entity)
{
entity.ApproverQueue.Dequeue();
if (entity.Amount <= 300)
{
entity.Status = "Approved";
return;
}
if (!IsLast(entity.ApproverQueue) && string.IsNullOrWhiteSpace(entity.Status))
{
NextApprover.ApproveAmount(entity);
return;
}
else
{
entity.Status = "Rejected";
}
}
}
Officer B Class
public class OfficerBApprover : ApproverCategorizer
{
public override void ApproveAmount(TestEntity entity)
{
entity.ApproverQueue.Dequeue();
if (entity.Amount <= 300)
{
entity.Status = "Approved";
return;
}
if (!IsLast(entity.ApproverQueue) && string.IsNullOrWhiteSpace(entity.Status))
{
NextApprover.ApproveAmount(entity);
return;
}
else
{
entity.Status = "Rejected";
}
}
}
Approver Chain Class
public class ApproverChain
{
public static TestEntity Entity { get; set; }
public static ApproverCategorizer Approver { get; set; }
public ApproverChain()
{
List<string> approverList = Entity.ApproverList.Split(',').ToList();
Queue<string> approverQueue = new Queue<string>();
Approver = new StartApprover();
// Note: The code below is working, but not the desired logic.
//Approver.RegisterNextApprover(new OfficerAApprover()).RegisterNextApprover(new OfficerBApprover());
// Note: This code below is not working, but this is the desired logic.
foreach (string approver in approverList)
{
switch (approver)
{
case "OfficerA":
Approver.RegisterNextApprover(new OfficerAApprover());
break;
case "OfficerB":
Approver.RegisterNextApprover(new OfficerBApprover());
break;
}
approverQueue.Enqueue(approver);
}
Entity.ApproverQueue = approverQueue;
}
public string StartProcess()
{
Approver.ApproveAmount(Entity);
return Entity.Status;
}
}
Business Class
public string ProcessApproval()
{
TestEntity entity = new TestEntity
{
Amount = 500,
ApproverList = "OfficerB,OfficerA"
};
ApproverChain.Entity = entity;
ApproverChain chain = new ApproverChain();
var result = chain.StartProcess();
return result;
}
This means that the OfficerB
class will process first. If it fails, it will go to OfficerA
class.
Is there a way to tweak it to the desired logic as mentioned? If so, how is it done?
Upvotes: 1
Views: 1001
Reputation: 73442
If I understand correctly, you need to configure your approvers via runtime value (string in this case).
With very little change in your code it is possible. Here is the required modification.
public ApproverChain()
{
List<string> approverList = Entity.ApproverList.Split(',').ToList();
Queue<string> approverQueue = new Queue<string>();
Approver = new StartApprover();
ApproverCategorizer currentApprover = Approver;
foreach (string approver in approverList)
{
switch (approver)
{
case "OfficerA":
currentApprover = currentApprover.RegisterNextApprover(new OfficerAApprover());
break;
case "OfficerB":
currentApprover = currentApprover.RegisterNextApprover(new OfficerBApprover());
break;
}
approverQueue.Enqueue(approver);
}
Entity.ApproverQueue = approverQueue;
}
Above code will work just fine. But for me the ApproverQueue
looks fishy.
It seems like you're having ApproverQueue
property just to find out whether the current approver is the last approver in the chain. You can find it by simply checking NextApprover
to null and completely get rid of that approverQueue
.
Then your code becomes
public class ApproverChain
{
public static TestEntity Entity { get; set; }
public static ApproverCategorizer Approver { get; set; }
public ApproverChain()
{
Approver = new StartApprover();
List<string> approverList = Entity.ApproverList.Split(',').ToList();
ApproverCategorizer currentApprover = Approver;
foreach (string approver in approverList)
{
switch (approver)
{
case "OfficerA":
currentApprover = currentApprover.RegisterNextApprover(new OfficerAApprover());
break;
case "OfficerB":
currentApprover = currentApprover.RegisterNextApprover(new OfficerBApprover());
break;
}
}
}
public string StartProcess()
{
Approver.ApproveAmount(Entity);
return Entity.Status;
}
}
public abstract class ApproverCategorizer
{
protected ApproverCategorizer NextApprover { get; private set; }
public ApproverCategorizer RegisterNextApprover(ApproverCategorizer nextApprover)
{
NextApprover = nextApprover;
return nextApprover;
}
public abstract void ApproveAmount(TestEntity entity);
protected bool IsLast()
{
return NextApprover == null;
}
}
public class OfficerAApprover : ApproverCategorizer
{
public override void ApproveAmount(TestEntity entity)
{
if (entity.Amount <= 300)
{
entity.Status = "Approved";
return;
}
if (!IsLast() && string.IsNullOrWhiteSpace(entity.Status))
{
NextApprover.ApproveAmount(entity);
return;
}
else
{
entity.Status = "Rejected";
}
}
}
And your TestEntity
class will not have ApproverQueue
property.
Upvotes: 3