使“只读”对象从 API 包装器返回

本文关键字:API 包装 返回 对象 只读 | 更新日期: 2023-09-27 17:56:26

我正在为工作中 REST API(这是一个具有静态方法的静态类)编写一个包装器,它应该返回一个类或结构,其中包含从 API 请求返回的所有解析的 Json。我正在使用这样的System.Web.Script.Serialization解析 Json:

JavaScriptSerializer jss = new JavaScriptSerializer();
QueryResult re = jss.Deserialize<QueryResult>(json);

然后,我想在 QueryResult 上设置两个附加参数:使用的原始请求 URL 和 API 返回的确切 Json。一个矛盾是,我希望整个对象仅在从 API 包装器返回后被读取。我的第一个想法是只让变量通过构造函数设置,但以我的方式解析 Json 永远不会让我使用构造函数。我想过有两个非常相似的对象,即一个在用于解析的包装器之外看不到的私有类,然后是一个使用构造函数设置只读参数一次的公共类,但这是非常多余的,我宁愿以任何其他方式这样做。

他们有什么设计模式或技巧让我这样做吗?我希望他们尝试分配给其中一个属性是一个语法错误,而不仅仅是一个被忽略的赋值。

使“只读”对象从 API 包装器返回

更新

我正在寻找的是一种方式 能够编辑对象然后锁定 它下来,以便读取每个字段 仅在特定代码行之后。

这听起来像是构建器模式的合适用例。你有一个可变的对象,你可以用它来设置你想要构建的任何内容的状态;然后,此类型负责构造实际可用的不可变("锁定")类型的实例。

例如:

public class QueryResultBuilder
{
    private string _requestUrl;
    // plus other fields
    public QueryResultBuilder SetRequestUrl(string requestUrl)
    {
        _requestUrl = requestUrl;
        return this;
    }
    // plus other methods
    public QueryResult Build()
    {
        // This could be an internal constructor,
        // only used by this builder type.
        return new QueryResult(_requestUrl /* plus other parameters *);
    }
}

然后,调用代码可能如下所示:

JavaScriptSerializer jss = new JavaScriptSerializer();
QueryResult re = jss.Deserialize<QueryResultBuilder>(json)
                    .SetRequestUrl("url")
                    .Build();

老实说,我不知道这是否适合您的方案,但是"可以'更新'的只读对象"问题的一个常见解决方案是拥有一个不可变的类型,该类型返回经过修改的副本(例如,很像DateTime.Add)。

所以你可以有一个像这样的操作...

class QueryResult
{
    // ...lots of other stuff...
    public QueryResult SetRequestUrl(string requestUrl)
    {
        QueryResult clone = this.Clone();
        // This property could have a private setter.
        clone.RequestUrl = requestUrl;
        return clone;
    }
}

然后调用代码需要执行以下操作:

JavaScriptSerializer jss = new JavaScriptSerializer();
QueryResult re = jss.Deserialize<QueryResult>(json).SetRequestUrl("url");
不幸的是,

您已经发现了为什么序列化通常被认为与正确的 OO 设计正交:为了方便起见,它破坏了封装。

我认为仅具有get{}属性的包装器对象是您最好的选择。 是的,这是多余的,但它有效,而且非常简单。

此外,您可能需要考虑为什么需要此功能。服务的使用者如何处理数据不应该引起你太多关注。您有什么特别的原因吗?

我没有看到让某人在仍然允许序列化程序工作的同时设置属性的编译时错误的好方法,但如果设置的属性已经具有值,您可能会引发异常:

class QueryResult
{
  public string Foo
  {
    get
    {
      return _foo; //private backing ivar
    }
    set
    {
      if (_foo == null)
        _foo = value;
      else
        throw new InvalidOperationException("Cannot set a property that already has a value.");
    }
  }
}

只需确保所有类型都可为 null 或具有永远不会设置的默认值(即在可能的实际属性值集之外)。

您可以添加一个机制来在序列化对象后锁定对象...

class QueryResult
{
    private bool _locked;
    // Called after deserialization to lock the object...
    internal void LockDown()
    {
        this._locked = true;
    }
    public String Foo
    {
        get { return this._foo; }
        set 
        {
            if (this._locked)
                throw new InvalidOperationException();
            this._foo = value;
        }
    }
}

你能QueryResult实现一个接口IQueryResult吗?

JavaScriptSerializer jss = new JavaScriptSerializer();
IQueryResult re = jss.Deserialize<QueryResult>(json);

使接口仅指定属性 get,而不指定集合。

public interface IQueryResult
{
    int Foo { get; }
}
internal class QueryResult : IQueryResult
{
    public Foo { get; set; }
}

当然,它不能绝对保证只读。 但它确实避免了重复的类。 如果可以的话,QueryResult做一个internal类,这样他们就不能像我上面所做的那样投靠它。 这样就可以操纵它,但他们不能。