jb62 jb62 - 13 days ago 8
C# Question

Use a Design Pattern to add functionality to a class in C#

My challenge: Add the ability to serialize and deserialize from/to XML to a class that only knows how to do this with JSON.

I'm new to C#, although I'm conversant with the "basic" application of Design Patterns.

A colleague suggested the following:

My recommendation is that you inject a serializer (write an IRDCSerizliazer interface that is a decorator/adapter that wraps whichever serializer it is constructed with).

They're very big on the "open/closed" principle here, which is probably why the suggestion was to use an interface and a
pattern rather than adding any code to the class.

I just built a sample "Decorator" pattern in C# to make sure I understood how to do the "standard" decorator pattern
and it feels way to complex for this situation... (although I'm happy to be corrected)

I think I need something that abstracts "Serializer" and allows "Serializer.serialize() and Serializer.deserialize()
to be called from my worker class that currently only knows about JSON. I also think I want to be able to have either the JSON code or the XML code in "Serializer" based on how it is constructed. I.E. I'm assuming from my colleague's tip that he's built patterns like this...

I can feel this "on the tip of my tongue" as they say, but am having trouble making the leap. A "pure" Decorator pattern
seems too complex. Help would be appreciated.

Please don't flame about how I should ask for more help at the office - obviously I don't want to do that or I wouldn't be asking for help here.

My worker class is below - you'll see that currently it just calls the JSON serialization and deserialization directly... and obviously that would have to change. Don't worry too much about what it does - it's tricky reflection stuff - the key thing is making it possible to serialize and deserialize with XML as well as JSON in this class using this concept of "injecting an interface".

namespace XXXXXXXXXXXX
{
public interface IRecordSearchClientAdapter
{
string CallCorrectMethod(ClientServiceTypes component, string method, string request, Type[] paramTypes);
}

public class RecordSearchClientAdapter : IRecordSearchClientAdapter
{
private IDictionary<ClientServiceTypes, object> componentMappings;

public static IRecordSearchClientAdapter Create(IDictionary<ClientServiceTypes, object> clients)
{
return new RecordSearchClientAdapter(clients);
}

private RecordSearchClientAdapter(IDictionary<ClientServiceTypes, object> clients)
{
componentMappings = clients;
}
public string CallCorrectMethod(ClientServiceTypes component, string method, string request, Type[] paramTypes)
{
dynamic parsedrequest = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(request);
//use another method to decide if it's JSON or XML, don't worry about injecting a Decorator:Interface?
//dynamic parsedrequest = DeserializeObject(request);
return CallCorrectMethod(component, method, parsedrequest, paramTypes);
}

//In this method, check for XML or JSON - then forward to the appropriate method.
private string DeserializeObject(string request)
{
//stub - fixme!
return null;
}

private string CallCorrectMethod(ClientServiceTypes component, string method, JObject request, Type[] paramTypes)
{
object client = componentMappings[component];

MethodInfo mi = FindExactMethod(client, method, request, paramTypes);

string response = null;

if (paramTypes.Length == 1) // just one "request" object
{
var obj = DeserializeGenericObject(request, paramTypes[0]);
var methodResponse = mi.Invoke(client, new object[] { obj });
response = Newtonsoft.Json.JsonConvert.SerializeObject(methodResponse);
}
else // multiple parameters, need to grab them from the request
{
JEnumerable<JToken> tokens = request.Children();
IEnumerator<JToken> tokenEnumerator = tokens.GetEnumerator();
List<object> args = new List<object>();
foreach (Type t in paramTypes)
{
if (!tokenEnumerator.MoveNext())
{
throw new ArgumentException("Number of arguments in request doesn't match number of arguments in method");
}

args.Add(DeserializeGenericObject(tokenEnumerator.Current.First, t));
}
var methodResponse = mi.Invoke(client, args.ToArray());
response = Newtonsoft.Json.JsonConvert.SerializeObject(methodResponse);
}

return response;
}

private MethodInfo FindExactMethod(object client, string method, JObject request, Type[] paramTypes)
{
// get the method that matches the name and type params
MethodInfo mi = client.GetType().GetMethod(method, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance, null, paramTypes, null);
return mi;
}

private object DeserializeGenericObject(JToken token, Type objectType)
{
Type myClassType = this.GetType();
MethodInfo methodToCall = myClassType.GetMethod("DeserializeGenericTemplate", BindingFlags.IgnoreCase | BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo genericMethodToCall = methodToCall.MakeGenericMethod(new Type[] { objectType });
object newObject = genericMethodToCall.Invoke(this, new[] { token });

return newObject;
}

private T DeserializeGenericTemplate<T>(JToken token)
{
T obj = token.ToObject<T>();
return obj;
}
}


}

Answer

Not sure why you need this in ONE class. Your design seems like an overkill. I would have an interface to define common behavior (serialize and deserialize) and two implementation classes, one for JSON and another for XML, something like this

public interface ISerialize<T>
{
    T Deserialize<T>(string input);
    string Serialize(T type)
}

public class JsonSerializer<T> : ISerialize<T>
{
    T Deserialize<T>(string input) {...}
    string Serialize(T type) {...}
}

public class XmlSerializer<T> : ISerialize<T>
{
    T Deserialize<T>(string input) {...}
    string Serialize(T type) {...}
}

And here is how I would use it

public class Foo<T>
{
    private ISerializer<T> _serialiser;

    public Foo<T>(ISerializer<T> serialiser)
    {
        _serialiser = serialiser;
    }

    void DoFoo()
    {
        string result = serialiser.Serialize(instanceOfA_1);
        var instanceOfA_2 = serialiser.Deserialize<ClassA>(result);
    }
}

If you don't know which serializer is required - use an interface, for example, by injecting it into a constructor.

Comments