Dr_klo
Dr_klo

Reputation: 469

Best way to use shared components in Chain of Responsibility pattern

I have a problem with Chain of Responsibility pattern. All handlers implements this class:

/// <summary>
/// Chain of Responsibility pattern
/// </summary>
abstract public class ChainHandler
{
    protected LogWrapper Log;
    protected ChainHandler successor;

    /// <summary>
    /// SetSuccessor
    /// </summary>
    private void SetSuccessor(ChainHandler successor)
    {
       this.successor = successor;
    }

    protected oAPI NotImplemented
    {
        get { return new oAPI(HTTPCodes.NotImplemented); }
    }

    /// <summary>
    /// Set Successor to the end of chain
    /// </summary>
    /// <param name="successor">Handler</param>
    public void Add(ChainHandler successor)
    {
        if (this.successor == null) SetSuccessor(successor);
        else
        {
            this.successor.Add(successor);
        }
    }
    protected oAPI ReferToSuccessor (iAPI request)
    {
        if (successor != null) return successor.HandleRequest(request);
        return NotImplemented;
    }
    /// <summary>
    /// output API builder
    /// </summary>
    /// <param name="code">HTTP code</param>
    /// <returns></returns>
    protected oAPI RB(HTTPCodes code, string messsage = null, string data = null, bool hasError = false)
    {
        return new oAPI(Shared.User, code, message) { Data = data, HasError = hasError };
    }
    /// <summary>
    /// Serializer (JSON)
    /// </summary>
    public Func<object, string> Serializer { get; set; }
    /// <summary>
    /// Handle request
    /// </summary>
    /// <param name="request">request</param>
    /// <returns>result</returns>
    public abstract oAPI HandleRequest(iAPI request);
}

Then I implement DocHandler

public class DocHandler:ChainHandler
    {
     public DocChain()
     {
      Log = new LogWrapper(this);
     }
     public override oAPI HandleRequest(iAPI request)
     {
      switch (request.Comand)
      {
       case iAPI.Target.GetModel:
        return GetModel(request.DocID);
      }
      return ReferToSuccessor(request);
      }
 private Doc GetDoc(int id)
    {
     Log.Debug("get document by id: " + id);
     using (var unit = UnitOfWork.Create())
     {
      var repository = unit.GetRepository<Doc>();
      Doc doc = repository.Get(id);
      if (doc == null)
      {
       Log.Error("Document not found");
       throw new DocumentNotFoundException();
      }
      return doc;
     }
    }

 public oAPI GetModel(int DocId)
      {
       var Model = GetDoc();
       return RB(HTTPCodes.OK, data: Serializer(
       Model));
       }
}

And CloudHandler

 public class CloudHandler:ChainHandler
    {
        private IDAVService _service;
        private string RemoteRepository;
        public CloudChain(IDAVService service)
        {
            Log=new LogWrapper(this);
            _service=service;
        }

        public override oAPI HandleRequest(iAPI request)
        {
            switch (request.Comand)
                    {
                    case iAPI.Target.UploadModel:
                    return Upload(request.DocID,request.VersionID);
                    case iAPI.Target.DownloadModel:
                    return Download(request.VersionID, request.DocID);
                    }
            return ReferToSuccessor(request);
        }
        public oAPI Upload(int DocID,int VersionID)
        {
            // Need to get model from DocHandler
            var model = ???
            service.Upload(model);
            return RB(HTTPCodes.OK);
        }
        public oAPI Download(int DocID,int VersionID)
        {
            // Need to get model from DocHandler
            var model = ???
            service.Download(model);
            return RB(HTTPCodes.OK);
        }
    }

And my problem in finding the best way to share methods and properties between handlers. Now I use static class SharedComponents where each handler delegate own method.

public static class SharedComponents
    {
        public static Func<int, Doc> GetDoc;
    }

In DocHandler I delegate method SharedComponents.GetDoc = this.GetDoc; and then use it in CloudHandler var Model = SharedComponents.GetDoc(docid). This is spagetti to write delegates to hundred of shared methods.

But how I tested this? I will must initialize all handlers (because A uses method of B and B may use methods of C etc.) to test one method in one handler. Horror!

I try to set shared methods as static to use like var Model = DocHandler .GetDoc(docid). But this solution breaking Dependency inversion principle. And if some shared method use context (like session in UnitOfWork) to I need in test initialise all handlers again!

Upvotes: 1

Views: 1887

Answers (1)

Markus
Markus

Reputation: 22456

One of the main purposes of applying the Chain of Responsiblity pattern is to have some steps that are independent of each other. The goal is to be able to change the order of steps or add new steps without having to change the existing ones.

In your approach, even if you use delegates on a shared class, you introduce a strong dependency from the CloudHandler to DocHandler as it will only be able to run if the delegate is set.

In order to solve this, I'd propose to extract the shared methods from the Handlers and move them to one or more helper classes with virtual methods or interfaces. Instead of creating the instance of the class in the Handlers, you inject the required instances into the Handlers during construction. You mention hundreds of shared methods. It is important to group the methods in a good way so that you do not have too many helper classes/interfaces and that you do not have to inject too many into a specific Handler class.

In your example, you'd create the following helper class for Docs:

internal class DocHelper
{
    internal virtual Doc GetDoc(int docId) 
    { 
        // ...
    }

    // Other methods as required
}

DocHandler and CloudHandler get an instance in their constructor and use this instance to get the document, e.g.:

public class DocHandler : ChainHandler
{
    private readonly DocHelper docHelper; 

    public DocHandler(DocHelper docHelper)
    {
        Log = new LogWrapper(this); 
        this.docHelper = docHelper;
    }
    // ...
    public oAPI GetModel(int DocId)
    {
        var Model = docHelper.GetDoc();
        return RB(HTTPCodes.OK, data: Serializer(
        Model));
    }
    // ...
}

This approach enables you to supply mock helper classes when testing the handlers.

Upvotes: 2

Related Questions