防止setter在使用Newtonsoft JSON.NET进行反序列化时重新运行操作

时间:2017-08-07 17:03:48

标签: c# .net json json.net deserialization

所以我创建了一个Monopoly控制台应用程序来探索一些设计概念,并试图更好地理解测试驱动开发。

在这种情况下,我创建了单元测试,以确保我的所有类都可以正确地序列化和反序列化(因为我将来需要它,如果它们不能,我宁愿早点知道比以后所以我可以避免返工;事实上,这样做有助于我在初始设计中遇到一些缺陷。)

此属性是较大的Property类的一部分。 Owner是拥有该财产的玩家。

我在这里想要实现的是,当玩家抵押特定房产时,该房产应标记为“抵押”,并且玩家应以现金收取购买价格的一半。当他们不归还财产时,他们会还钱。还有一些我还没有写过的其他检查;例如,游戏规则规定,如果房产仍有房屋,则不允许抵押房产。我还没有检查玩家是否有足够的钱来取消抵押财产,但我计划尽快添加,因为这是一个微不足道的规则来实施。这就是为什么我在这里使用setter而不是自动生成的属性。

这是问题所在:这序列化和反序列化而不抛出异常,但是如果我序列化抵押财产,它会在我反序列化时再次添加钱(即反序列化基本上具有重新抵押财产的效果) ,不用说单元测试失败了(应该给出明显的错误)。我认为这并不奇怪,因为这正是我写它的方式 - 当你改变它的价值时,它会抵押或不归还财产。

以下是代码:

[JsonProperty(Order = 1)]
public Player Owner
{
    get;
    set;
}

    private bool _isMortaged = false;

    [JsonProperty(Order = 2)]
    public bool IsMortgaged
    {
        get { return _isMortaged; }
        set
        {
            if (!IsOwned)
            {
                throw new InvalidOperationException("You can't mortgage a property that no one owns");
            }

            // If we're setting this property to the same thing that it
            // already was, log it. You could argue that that would indicate
            // a bug (it probably would), but throwing an exception here
            // breaks deserialization.
            Debug.WriteIf(value == _isMortaged, $"Setting IsMortgaged to the same thing as it was before. Stack trace:{Environment.NewLine}{(new StackTrace()).ToString()}");

            // If we're setting this to the same thing that it already was,
            // then the setter should have no effect at all.
            if (value != _isMortaged)
            {
                // The player is mortgaging the property
                if (value)
                {
                    _isMortaged = true;
                    Owner.Money += (PurchasePrice / 2);
                }
                // The player is unmortgaging the property
                else
                {
                    _isMortaged = false;
                    Owner.Money -= (PurchasePrice / 2);
                }
            }
        }
    }

是否有一种简单的方法可以使用不同的setter进行反序列化,或者判断我是否在setter中进行反序列化? (后者似乎是“黑客”)。我想也有可能编写一个自定义反序列化器,这可以解决我想的问题,但我更愿意在可能的情况下避免这种情况(这似乎不是一种特别“干净”的方式)。< / p>

或者,这只是一个设计缺陷吗?

2 个答案:

答案 0 :(得分:7)

这是一个设计缺陷; setters不应该有明显更新值的副作用(以及那些预期的那些,例如INotifyPropertyChanged

如果你需要特殊的行为,那么要么引入一种特殊的设置方法,要么反过来考虑它:

  

您抵押/取消抵押财产,因此您希望该财产余额的所有者更新

而不是让属性更新所有者的余额(它不应该关注:Separation of Concerns;而是可以动态计算所有者的余额属性:

public class Property {
    public bool IsMortgaged {get;set;}
}

public class Player
{
    private List<Property> _properties = new List<Property>();
    private double _cash = 0;

    public int AvailableMoney =>
        _cash + _properties.Where(p => p.IsMortgaged).Select(p => p.MortgageValue).Sum();
}

现在,显然需要更新以正确匹配您的模型,但理想情况下,您希望数据尽可能愚蠢;财产是否抵押是一个简单的真/假。这是一种特殊的平衡,并且没有理由不重新计算它。

答案 1 :(得分:1)

这违反了单一责任或分离问题。

我将采用的方法是在class PERSON inherit STRING end 上设置一个私有的setter,并使用一些已存在的JSON.NET功能对其进行反序列化。

这需要Property.IsMortgagedProperty

上的一些新方法

对于Owner,您需要添加:

Property

并在public class Property { public double Value {get;} = 5000.00 private bool _isMortgaged public bool SetMortgaged() { //Validation for if the property can be mortgaged goes here, return false if fails if(_isMortgaged == true) { return false; } _isMortgaged = true; return true; } }

Owner

这会将逻辑封装在各自的类中,public class Owner //or player { private double _cash; public double AvailableCash { get {return _cash;} } public void MortgageProperty(Property item) { if(item.SetMortgaged()) { _cash += item.Value / 2 } else { //Throw an error or something else to let the user know it failed } } } 会知道它是否可以抵押,而Property会处理这笔钱。