oleg
oleg

Reputation: 21

C# design guideline - calling appropriate method based on string value

Looking for design guidelines for the following problem. I'm receiving two string values - action and message and have to call appropriate method which processes string message (processM1MessageVer1, processM1MessageVer2, processM2MessageVer1...). The method I have to call depends on the given string action. There are 2 versions (but in future there might be more) of each processing method. The version of method I have to call is determined by global variable version. Every method returns object of different type (ResultObject1, ResultObject2...). The result has to be serialized, converted to base64 and returned back.

Is there more elegant way of writing this (eliminate duplicate code, make possible future changes easier, reduce code...):

    string  usingVersion = "ver1";
    public string processRequest(string action, string message)
        if (usingVersion == "ver1"){
            processRequestVer1(action, message);
        }
        else{
            processRequestVer2(action, message);
        }
    }    

    //version 1
    public string processRequestVer1(string action, string message){
        string result = "";
        switch (action){
            case "m1":
                ResultObject1 ro = processM1MessageVer1(message);
                result = serialize(ro);
                result = convertToB64(result);
            case "m2":
                ResultObject2 ro = processM2MessageVer1(message);
                result = serialize(ro);
                result = convertToB64(result);
            case "m3":
                ResultObject3 ro = processM3MessageVer1(message);
                result = serialize(ro);
                result = convertToB64(result);      
        }
        return result;
    }

    //version 2
    public string processRequestVer2(string action, string message){
        string result = "";
        switch (action){
            case "m1":
                ResultObject1 ro = processM1MessageVer2(message);
                result = serialize(ro);
                result = convertToB64(result);
            case "m2":
                ResultObject2 ro = processM2MessageVer2(message);
                result = serialize(ro);
                result = convertToB64(result);
            case "m3":
                ResultObject3 ro = processM3MessageVer2(message);
                result = serialize(ro);
                result = convertToB64(result);      
        }
        return result;
    }

It would be simplier if messages that have to be processed are of different object types instead of strings so that appropriate method could be called polymorphically. The fact that every process method returns different object type also complicates things even more. But these don't depend on me and I cannot change it.

Upvotes: 0

Views: 152

Answers (2)

MarcinR
MarcinR

Reputation: 1

First - define interface that suit you best, like this

    public interface IProcessMessage
    {
        string ActionVersion { get; }
        string AlgorithmVersion { get; }
        string ProcessMessage(string message);
    }

Then create as many implementation as you need

public class processorM1Ver1 : IProcessMessage
{
    public string ProcessMessage(string message)
    {
        ResultObject1 ro1 = processM1MessageVer1(message);
        var result = serialize(ro1);
        result = convertToB64(result);
        return result;
    }

    public string ActionVersion {get { return "m1"; }}

    public string AlgorithmVersion {get { return "ver1"; }}
}

public class processorM2Ver1 : IProcessMessage
{
    public string ActionVersion {get { return "m2"; }}

    public string AlgorithmVersion {get { return "ver1"; }}

    public string ProcessMessage(string message)
    {
        ResultObject1 ro1 = processM2MessageVer1(message);
        var result = serialize(ro1);
        result = convertToB64(result);
        return result;
    }
}

public class processorM1Ver2 : IProcessMessage
{
    public string ActionVersion {get { return "m1"; }}

    public string AlgorithmVersion {get { return "ver2"; }}

    public string ProcessMessage(string message)
    {
        ResultObject1 ro1 = processM1MessageVer2(message);
        var result = serialize(ro1);
        result = convertToB64(result);
        return result;
    }
}

Now you need something that know which implementation is best in current context

public class MessageProcessorFactory
{
    private MessageProcessorFactory() { }
    private static readonly MessageProcessorFactory _instance = new MessageProcessorFactory();
    public static MessageProcessorFactory Instance { get { return _instance; }}

    private IEnumerable<IProcessMessage> _processorCollection;
    IEnumerable<IProcessMessage> ProcessorCollection
    {
        get
        {
            if (_processorCollection == null)
            {
                //use reflection to find all imlementation of IProcessMessage
                //or initialize it manualy
                _processorCollection = new List<IProcessMessage>()
                {
                    new processorM1Ver1(),
                    new processorM2Ver1(),
                    new processorM1Ver2()
                };
            }
            return _processorCollection;
        }
    } 

    internal IProcessMessage GetProcessor(string action)
    {
        var algorithVersion = ReadAlgorithVersion();
        var processor = ProcessorCollection.FirstOrDefault(x => x.AlgorithmVersion == algorithVersion && x.ActionVersion == action);
        return processor;
    }

    private string ReadAlgorithVersion()
    {
        //read from config file
        //or from database
        //or where this info it is kept
        return "ver1";
    }
}

It can be use in such way

public class Client
{
    public string ProcessRequest(string action, string message)
    {
        IProcessMessage processor = MessageProcessorFactory.Instance.GetProcessor(action);
        return processor.ProcessMessage(message);
    }
}

Upvotes: 0

Phuong Nguyen
Phuong Nguyen

Reputation: 3070

My approach (make it more object oriented, and you should justify whether it's appropriate to create class structure depending on how complex your processing logic is. If your processing logic is only little then maybe this is over-engineering):

For serialize and convert to base 64, I assume you have some logic to do those tasks in a generic way. If not, move those to sub class also

 public interface IRequestProcessorFactory
{
    IRequestProcessor GetProcessor(string action);
}

public class FactoryVersion1 : IRequestProcessorFactory
{
    public IRequestProcessor GetProcessor(string action)
    {
        switch(action)
        {
            case "m1":
                return new M1Ver1RequestProcessor();
            case "m2":
                return new M2Ver1RequestProcessor();
            case "m3":
                return new M3Ver1RequestProcessor();
            default:
                throw new NotSupportedException();
        }
    }
}

public class FactoryVersion2 : IRequestProcessorFactory
{
    public IRequestProcessor GetProcessor(string action)
    {
        switch(action)
        {
            case "m1":
                return new M1Ver2RequestProcessor();
            case "m2":
                return new M2Ver2RequestProcessor();
            case "m3":
                return new M3Ver2RequestProcessor();
            default:
                throw new NotSupportedException();
        }
    }
}

public interface IRequestProcessor
{
    string ProcessRequest(string message);
}

public class RequestProcessorBase<T>
{
    public string ProcessRequest(string message)
    {
        T result = Process(message);
        string serializedResult = Serialize(result);
        return ConvertToB64(serializedResult);
    }

    protected abstract T Process(string message);

    private string Serialize(T result)
    {
        //Serialize
    }

    private string ConvertToB64(string serializedResult)
    {
        //Convert
    }
}

public class M1Ver1RequestProcessor : RequestProcessorBase<ResultObject1>
{
    protected ResultObject1 Process(string message)
    {
        //processing
    }
}

public class M2Ver1RequestProcessor : RequestProcessorBase<ResultObject2>
{
    protected ResultObject2 Process(string message)
    {
        //processing
    }
}

public class M3Ver1RequestProcessor : RequestProcessorBase<ResultObject3>
{
    protected ResultObject3 Process(string message)
    {
        //processing
    }
}

public class M1Ver2RequestProcessor : RequestProcessorBase<ResultObject1>
{
    protected ResultObject1 Process(string message)
    {
        //processing
    }
}

public class M2Ver2RequestProcessor : RequestProcessorBase<ResultObject2>
{
    protected ResultObject2 Process(string message)
    {
        //processing
    }
}

public class M3Ver2RequestProcessor : RequestProcessorBase<ResultObject3>
{
    protected ResultObject3 Process(string message)
    {
        //processing
    }
}

Usage:

string action = "...";
string message = "...";
IRequestProcessorFactory factory = new FactoryVersion1();
IRequestProcessor processor = factory.GetProcessor(action);
string result = processor.ProcessRequest(message);

The switch is still there in factory class, but it only returns processor and doesn't do actual work so it's fine for me

Upvotes: 1

Related Questions