设为“只读”从 API 包装器返回的对象?

发布于 2024-11-07 13:33:07 字数 572 浏览 2 评论 0原文

我正在工作中为 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 永远不会让我使用构造函数。我想过有两个非常相似的对象,即一个在用于解析的包装器之外看不到的私有类,然后是一个使用构造函数一次设置只读参数的公共类,但这非常多余的,我宁愿以任何其他方式来做。

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

I'm writing a wrapper for a REST API (which is a static class with static methods) at work and it should return a class or a struct holding all the parsed Json returned from the API request. I'm parsing the Json using System.Web.Script.Serialization like so:

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

I then want to set two additional parameters on the QueryResult: the original Request Url used and the exact Json returned by the API. The one contradiction is that I want the entire object to be read only once it's returned from the API wrapper. My first thought is to only let the variables be set through a constructor, but parsing Json the way that I am never lets me use a constructor. I thought about having two objects that are very similar, i.e. a private class that can't be seen outside of the wrapper that is used for parsing and then a public class who uses a constructor to set the read only parameters once, but that's very redundant and I'd rather do it just about any other way.

Are their any design patterns or tips to let me do this? I want it to be a syntactical error for them to try and assign to one of the properties, not just an ignored assignment.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

ˉ厌 2024-11-14 13:33:07

更新

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

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

例如:

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");

Update:

What I'm looking for is a way to be
able to edit an object and then lock
it down so that every field is read
only after a particular line of code.

This sounds like an appropriate use case for the builder pattern. You have a mutable object that you can use just to set up the state of whatever you want to build; then this type is responsible for constructing an instance of an immutable ("locked down") type which is actually usable.

For example:

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 *);
    }
}

Then your calling code might look like this:

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

I honestly have no idea if this is an appropriate answer for your scenario, but one common solution to the "read-only object that can be 'updated'" problem is to have an immutable type that returns copies with modifications (much like DateTime.Add, for example).

So you could have an operation like...

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;
    }
}

Then calling code would need to do something like:

JavaScriptSerializer jss = new JavaScriptSerializer();
QueryResult re = jss.Deserialize<QueryResult>(json).SetRequestUrl("url");
神仙妹妹 2024-11-14 13:33:07

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

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

另外,您可能需要考虑为什么需要此功能。您不必太关心服务的使用者如何处理数据。您有什么特别的原因吗?

You've unfortunately discovered why serialization has often been considered orthogonal to proper OO design: it breaks encapsulation for the sake of convenience.

I think a wrapper object with get{} only properties is your best bet. Yeah, it's redundant, but it works, and it's pretty simple.

Also, you might want to consider why you need this functionality at all. What consumers of your service do with the data shouldn't be of much concern to you. Is there a particular reason you had in mind?

娇柔作态 2024-11-14 13:33:07

我看不出有什么好方法可以让某人设置属性时出现编译时错误,同时仍允许序列化器工作,但是如果设置的属性已经具有值,则可能会抛出异常:

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.");
    }
  }
}

只需确保您的所有类型都可为空或具有永远不会设置的默认值(即超出了可能的属性值的实际设置)。

I don't see a good way of making it a compile-time error for someone to set a property while still allowing the serializer to work, but you could throw an exception if a property is set that already has a value:

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.");
    }
  }
}

Just make sure all your types are nullable or have default values that will never be set (i.e. are outside the realistic set of possible property values).

大姐,你呐 2024-11-14 13:33:07

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

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;
        }
    }
}

you could add a mechanism to lockdown the object after it has been serialized...

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;
        }
    }
}
花桑 2024-11-14 13:33:07

您能让 QueryResult 实现接口 IQueryResult 吗?

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

使接口仅指定属性获取,而不指定属性设置。

public interface IQueryResult
{
    int Foo { get; }
}

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

当然,它不能绝对保证只读。但它确实避免了重复的类。如果可以的话,将 QueryResult 设为 internal 类,这样它们就无法像我上面那样进行强制转换。这样可以操纵它,但他们不能。

Can you make QueryResult implement an interface IQueryResult?

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

Make the interface specify only property gets, not sets.

public interface IQueryResult
{
    int Foo { get; }
}

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

Sure, it's not absolutely guaranteed read-only. But it does avoid a duplicate class. If you can, make QueryResult an internal class so they can't cast to it, like I did above. That way you can manipulate it, but they can't.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文