不知道类型如何返回通用对象?

6

我还是一个编程新手,被分配任务创建一个WebHook消费者,它接收原始JSON字符串,将其解析为对象,并传递给处理程序进行处理。JSON的格式如下:

{
   "id":"1",
   "created_at":"2017-09-19T20:41:23.093Z",
   "type":"person.created",
   "object":{
      "id":"person1",
      "created_at":"2017-09-19T20:41:23.076Z",
      "updated_at":"2017-09-19T20:41:23.076Z",
      "firstname":"First",
      ...
   }
}

内部对象可以是任何对象,因此我认为这是使用泛型的绝佳机会,因此我按以下方式构建了我的类:

public class WebHookModel<T> where T : class, new()
{
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }

    [JsonProperty(PropertyName = "created_at")]
    public DateTime CreatedAt { get; set; }

    [JsonProperty(PropertyName = "type")]
    public string Type { get; set; }

    [JsonProperty(PropertyName = "object")]
    public T Object { get; set; }

    [JsonIgnore]
    public string WebHookAction
    {
        get
        {
            return string.IsNullOrEmpty(Type) ? string.Empty : Type.Split('.').Last();
        }
    }
}

然后创建了以下界面:

public interface IWebHookModelFactory<T> where T : class, new()
{
   WebHookModel<T> GetWebHookModel(string type, string jsonPayload);
}

我不明白的是,如果在编译时不知道类型,我该如何实现工厂类呢?

通过对模型进行一些尝试,我将其更改为抽象类,并添加了一个抽象的T对象,以便由派生类定义。

public abstract class WebHookModel<T> where T : class, new()
{
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }

    [JsonProperty(PropertyName = "created_at")]
    public DateTime CreatedAt { get; set; }

    [JsonProperty(PropertyName = "type")]
    public string Type { get; set; }

    [JsonProperty(PropertyName = "object")]
    public abstract T Object { get; set; }

    [JsonIgnore]
    public string WebHookAction
    {
        get
        {
            return string.IsNullOrEmpty(Type) ? string.Empty : Type.Split('.').Last();
        }
    }
}

public PersonWebHookModel : WebHookModel<Person>
{
    public override Person Object { get; set; }
}

但我仍然遇到了同样的问题,即试图在运行时实现一个我不知道类型的接口。根据我在网上找到的资料,这是协变的一个例子,但我没有找到任何解决此问题的文章。最好是跳过泛型并创建一个巨大的case语句吗?

public interface IWebHookFactory<TModel, TJsonObject> 
    where TJsonObject : class, new()
    where TModel : WebHookModel<TJsonObject>
{
    TModel GetWebHookModel(string type, string jsonPayload);
}

我有点偏向于使用抽象类的方法,因为它可以让我根据传递给我的服务的模型来定义各个处理程序。

public interface IWebHookService<TModel, TJsonObject>
    where TJsonObject : class, new()
    where TModel : WebHookModel<TJsonObject>
{
    void CompleteAction(TModel webHookModel);
}

public abstract class BaseWebhookService<TModel, TJsonObject> : IWebHookService<TModel, TJsonObject>
        where TJsonObject : class, new()
        where TModel : WebHookModel<TJsonObject>
{
    public void CompleteAction(TModel webHookModel)
    {
        var self = this.GetType();
        var bitWise = System.Reflection.BindingFlags.IgnoreCase
                        | System.Reflection.BindingFlags.Instance
                        | System.Reflection.BindingFlags.NonPublic;

        var methodToCall = self.GetMethod(jsonObject.WebHookAction, bitWise);
        methodToCall.Invoke(this, new[] { jsonObject });
    }

    protected abstract void Created(TModel webHookObject);

    protected abstract void Updated(TModel webHookObject);

    protected abstract void Destroyed(TModel webHookObject);
}

public class PersonWebHookService : BaseWebHookService<PersonWebHookModel, Person>
{
    protected override void Created(PersonWebHookModel webHookModel)
    {
        throw new NotImplementedException();
    }

    protected override void Updated(PersonWebHookModel webHookModel)
    {
        throw new NotImplementedException();
    }

    protected override void Destroyed(PersonWebHookModel webHookModel)
    {
        throw new NotImplementedException();
    }
}

1
如果你只有 T 的运行时类型信息(Type、名称或其他信息),那么不要使用泛型,它不是正确的工具;你最终会得到一个毫无意义的 WebHookModel<object>;只需使用接口的非泛型版本即可。 - InBetween
这些对象具体是用来做什么的?它们将如何在下游处理?是否有一个处理程序来处理所有这些对象,还是每种类型都有一个处理程序? - Jason Boyd
对于每个传入的WebHook对象,我已经找到了10个,它们需要被不同地处理。BaseWebHookService和PersonWebHookService是如何处理每个对象的示例。根据API文档中的约定,对象.动作(动作可以是Created、Updated或Destroyed)。BaseWebHookService知道要使用哪个派生服务,这取决于传递的WebHookModel。 - bschreck
1个回答

2

解决方案的关键点: 1. 在某个地方需要进行虚拟调用。 2. 你需要将JSON有效载荷中的类型标记映射到实际的C#类中。
例如,“person.created” --> “Person”。
如果你控制序列化格式,JSON.Net可以注入自己的类型标记并为你完成此操作。假设你不能走这条路…… 因此,你需要像Dictionary这样的东西来包含映射。

假设你的定义如下:

abstract class WebhookPayload // Note this base class is not generic! 
{
    // Common base properties here 

    public abstract void DoWork();
}

abstract class PersonPayload : WebhookPayload
{
    public override void DoWork()
    {
        // your derived impl here 
    }
}

然后您可以像这样反序列化:

    static Dictionary<string, Type> _map = new Dictionary<string, Type>
    {
        { "person.created", typeof(PersonPayload)}
    }; // Add more entries here 

    public static WebhookPayload Deserialize(string json)
    {
        // 1. only parse once!
        var jobj = JObject.Parse(json); 

        // 2. get the c# type 
        var strType = jobj["type"].ToString(); 

        Type type;
        if (!_map.TryGetValue(strType, out type))
        {
            // Error! Unrecognized type
        }

        // 3. Now deserialize 
        var obj = (WebhookPayload) jobj.ToObject(type);
        return obj;
    }

那么就没有办法创建一个像Dictionary<string,Func<string,WebHookModel<TJson>> factoryMapper这样的映射字典,因为工厂必须有一个具体的实现?我想我不明白为什么不能让工厂类实现我的IWebHookFactory<TModel,TJsonObject>,因为该方法将返回一个具体的类(即PersonWebHookModel)。也许我需要删除泛型并使用WebHookModel作为基类。 - bschreck
没错。您不能有Dictionary<string,Func <string,WebHookModel <TJson>> factoryMapper,因为其中有一个未解析的通用 TJson。所以这里的关键是 a)拥有一个具有虚拟函数的非泛型基类;b)在派生类上具有任何泛型内容;它可以实现虚拟函数并利用其实现中的泛型属性。 - Mike S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接