如何推广一个属性模式

3
我有一些类,其中包含多个属性,它们具有明确定义的名称和功能,但具有相同的实现。例如:
class Stats
{
    private int attack;
    public int Attack
    {
        get =>
            HasBuff ? attack + 1 : attack;
        set
        {
            if (value < 1 || value > 10)
                throw new ArgumentOutOfRangeException("Invalid value");
            attack = value;
        }
    }

    public int Defense {...}
    public int Speed {...}
}

如何将防御和速度的实现方式与攻击相似,以避免冗余并使更改变得更加容易?


你能举个例子吗?接口如何帮助我重新组织这段代码? - rzippo
你想要将验证和“有buff规则”泛化吗? - Hugo Jose
你可以实现一个PropertyBag<int>,但除非你有很多属性,否则它需要的工作量可能比它值得的要多。 - Mark Benningfield
3个回答

5
制作另一个类以概括统计数据:
public class Stat
{
    public bool HasBuff { get; set; }

    private int _stat;
    public int Score
    {
        get => HasBuff ? _stat + 1 : _stat;
        set => _stat = value;
    }
}

然后将这个技能用于您的每一个技能:

public class CombatStats
{
   public Stat Attack { get; } = new Stat();
   public Stat Defense { get; } = new Stat();
   public Stat Speed { get; } = new Stat();
}

调用代码将如下所示:

var ninja = new Ninja();
ninja.skills = new CombatStats();
var attackStrength = ninja.skills.Attack.Score;

作为进一步的改进,可以使用隐式操作符来避免创建对象和调用Score:
public class Stat
{
    ...

    public static implicit operator int(Stat stat)
    {
        return stat.Score;
    }

    public static implicit operator Stat(int value)
    {
        return new Stat()
        {
            Score = value
        };
    }
}

这样做可以使针对问题示例编写的客户端代码变得透明:
ninja.skills = new CombatStats(){
    Attack = 5,
    Defense = 2
}
int attack = ninja.skills.Attack;

在这种情况下,“Stat”属性可能应该是只读的。 - juharr
@rzippo,有人拒绝了你的“implicit”编辑,但我喜欢它,我认为我的后续批准将保留你的更改。 - McGuireV10
@McGuireV10 是的,现在看起来已经被接受了,谢谢 - rzippo

3

一种值得考虑的方法:

class Stats
{
    // other existing code here

    private int defense;

    public int Defense
    {
        get
        {
            return GetValue(defense);
        }
        set
        {
            SetValue(value, ref defense);
        }
    }

    private int GetValue(int value)
    {
        return HasBuff ? value + 1 : value;
    }

    private void SetValue(int value, ref int target)
    {
        if (value < 1 || value > 10)
            throw new ArgumentOutOfRangeException("Invalid value");
        target = value;
    }
}

Attack等现在基本上与Defence相同,但是向GetValueSetValue传递attack代替defense


2
我会选择组合。
统计:
    public class Stats
    {
        private readonly StatProperty _defense;
        private readonly StatProperty _attack;
        private readonly StatProperty _speed;

        public Stats()
        {
            _defense = new StatProperty(this);
            _attack = new StatProperty(this);
            _speed = new StatProperty(this);
        }

        public int Defense
        {
            get => _defense.Value;
            set => _defense.Value = value;
        }

        public int Attack
        {
            get => _attack.Value;
            set => _attack.Value = value;
        }
        public int Speed
        {
            get => _speed.Value;
            set => _speed.Value = value;
        }

        public bool HasBuff { get; set; }

    }

StatProperty:

    public class StatProperty
    {
        public Stats Stats { get; }

        public StatProperty(Stats stats)
        {
            Stats = stats;
        }

        private int _value = 1;
        public int Value
        {
            get => Stats.HasBuff ? _value + 1 : _value;
            set
            {
                if (value < 1 || value > 10)
                    throw new ArgumentOutOfRangeException("Invalid value");
                _value = value;
            }
        }
    }

我需要更多细节才能知道它是否是最佳选择。

如果不想在库外显示StatProperty,您还可以将其设置为internal或者如果只想在类Stats中使用,则可以设置为嵌套私有类。


这看起来与被接受的答案非常相似,但在 Stats 类中有更多的代码。 - rzippo

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