我刚刚花了最后几天为我的游戏构建货币系统,并且想知道你们是否对我(如果有的话)如何改善它有任何建议。在显示代码之前,让我解释一下目前的系统。它的工作原理与《魔兽世界》非常相似,因为当您获得100铜时,铜会自动转换为银。然后,将银转换为金,将金转换为铂。对于玩过《无尽的任务》的人来说,这个系统似乎更加熟悉。

我已经对它的根结构进行了一些优化,例如如何将所有货币分母保持在一个单长。使用这段时间,我可以进行计算以“伪造”其他面额。本质上,所有东西都在内部保留为铜。

我还暗示最大基本分母值为“ 999999999”。使用这个任意数字可以使我将总货币的上限限制为999铂金,99黄金,99银色和99铜。

我只是想知道你们是否有任何建议(除了“嘿,您应该在那里完全使用var!”),这可能有助于改善当前的实现。

这是当前的代码:

using System;

namespace Some.Arbitrary.Framework
{

    public enum Coins
    {
        /// <summary>
        /// Copper is the lowest denominator of currency.
        /// It requires 100 Copper to make 1 Silver.
        /// </summary>
        Copper = 1,
        /// <summary>
        /// Silver is the second most common form of currency.
        /// It requires 100 Silver to Make 1 Gold.
        /// </summary>
        Silver = 2,
        /// <summary>
        /// Gold is the most common form of currency. It takes
        /// part in most expensive transactions.
        /// It requires 100 Gold to make 1 Platinum.
        /// </summary>
        Gold = 3,
        /// <summary>
        /// Platinum is a coin which most people never see. A single
        /// Platinum coin can purchase almost anything.
        /// 1 Platinum Coin = 100 Gold.
        /// 1 Platinum Coin = 10,000 Silver.
        /// 1 Platinum Coin = 1,000,000 Copper.
        /// </summary>
        Platinum = 4
    }

    public class MoneyBag : IEquatable<MoneyBag>, IComparable<MoneyBag>
    {

        private long _baseDenomination;
        public const string CopperName = "Copper";
        public const string SilverName = "Silver";
        public const string GoldName = "Gold";
        public const string PlatinumName = "Platinum";
        public const char CopperAbbreviation = 'c';
        public const char SilverAbbreviation = 's';
        public const char GoldAbbreviation = 'g';
        public const char PlatinumAbbreviation = 'p';
        public const long MaximumBaseDenomination = 999999999;
        public static readonly MoneyBag FilledBag = new MoneyBag(MaximumBaseDenomination);
        public static readonly MoneyBag EmptyBag = new MoneyBag();

        public long BaseDenomination
        {
            get
            {
                return _baseDenomination;
            }
            set
            {
                _baseDenomination = value;
                // Clamp if required.
                if (_baseDenomination > MaximumBaseDenomination)
                {
                    _baseDenomination = MaximumBaseDenomination;
                }
                if (_baseDenomination < 0)
                {
                    _baseDenomination = 0;
                }
            }
        }

        /// <summary>
        /// The total amount of Copper.
        /// </summary>
        public int Copper
        {
            get
            {
                return ComputeCopper(_baseDenomination);
            }
        }

        /// <summary>
        /// The total amount of Silver.
        /// </summary>
        public int Silver
        {
            get
            {
                return ComputeSilver(_baseDenomination);
            }
        }

        /// <summary>
        /// The total amount of Gold.
        /// </summary>
        public int Gold
        {
            get
            {
                return ComputeGold(_baseDenomination);
            }
        }

        /// <summary>
        /// The total amount of Platinum.
        /// </summary>
        public int Platinum
        {
            get
            {
                return ComputePlatinum(_baseDenomination);
            }
        }

        public bool IsFull
        {
            get
            {
                return _baseDenomination == MaximumBaseDenomination;
            }
        }

        public bool IsEmpty
        {
            get
            {
                return _baseDenomination == 0;
            }
        }

        public bool HasPlatinum
        {
            get
            {
                return Platinum > 0;
            }
        }

        public bool HasGold
        {
            get
            {
                return Gold > 0 || Platinum > 0;
            }
        }

        public bool HasSilver
        {
            get
            {
                return Silver > 0 || Gold > 0 || Platinum > 0;
            }
        }

        public bool HasCopper
        {
            get
            {
                return Copper > 0 || Silver > 0 || Gold > 0 || Platinum > 0;
            }
        }

        public MoneyBag()
        {

        }

        public MoneyBag(int platinum, int gold, int silver, int copper)
        {
            Add(platinum, gold, silver, copper);
        }

        public MoneyBag(long baseDenomination)
        {
            BaseDenomination = baseDenomination;
        }

        public void Add(int platinum, int gold, int silver, int copper)
        {
            BaseDenomination += platinum * 1000000;
            BaseDenomination += gold * 10000;
            BaseDenomination += silver * 100;
            BaseDenomination += copper;
        }

        public void Add(int amount, Coins type)
        {
            if (amount <= 0) return;
            switch (type)
            {
                case Coins.Copper:
                    Add(0, 0, 0, amount);
                    break;
                case Coins.Silver:
                    Add(0, 0, amount, 0);
                    break;
                case Coins.Gold:
                    Add(0, amount, 0, 0);
                    break;
                case Coins.Platinum:
                    Add(amount, 0, 0, 0);
                    break;
            }
        }

        public void Add(MoneyBag other)
        {
            BaseDenomination += other._baseDenomination;
        }

        public void Subtract(int platinum, int gold, int silver, int copper)
        {
            BaseDenomination -= platinum * 1000000;
            BaseDenomination -= gold * 10000;
            BaseDenomination -= silver * 100;
            BaseDenomination -= copper;
        }

        public void Subtract(int amount, Coins type)
        {
            if (amount <= 0) return;
            switch (type)
            {
                case Coins.Copper:
                    Subtract(0, 0, 0, amount);
                    break;
                case Coins.Silver:
                    Subtract(0, 0, amount, 0);
                    break;
                case Coins.Gold:
                    Subtract(0, amount, 0, 0);
                    break;
                case Coins.Platinum:
                    Subtract(amount, 0, 0, 0);
                    break;
            }
        }

        public void Subtract(MoneyBag other)
        {
            BaseDenomination -= other._baseDenomination;
        }

        public void Empty()
        {
            _baseDenomination = 0;
        }

        public void Fill()
        {
            _baseDenomination = MaximumBaseDenomination;
        }

        public static MoneyBag operator +(MoneyBag b1, MoneyBag b2)
        {
            return new MoneyBag(b1._baseDenomination + b2._baseDenomination);
        }

        public static MoneyBag operator -(MoneyBag b1, MoneyBag b2)
        {
            return new MoneyBag(b1._baseDenomination - b2._baseDenomination);
        }

        public bool Equals(MoneyBag other)
        {
            return _baseDenomination == other._baseDenomination;
        }

        public override bool Equals(object obj)
        {
            return (obj is MoneyBag) && Equals((MoneyBag)obj);
        }

        public override int GetHashCode()
        {
            return _baseDenomination.GetHashCode();
        }

        public static bool operator ==(MoneyBag a, MoneyBag b)
        {
            if (ReferenceEquals(a, null)) return false;
            if (ReferenceEquals(b, null)) return false;
            return a.Equals(b);
        }

        public static bool operator !=(MoneyBag a, MoneyBag b)
        {
            if (ReferenceEquals(a, null)) return false;
            if (ReferenceEquals(b, null)) return false;
            return !a.Equals(b);
        }

        public static bool operator <(MoneyBag a, MoneyBag b)
        {
            return a.CompareTo(b) < 0;
        }

        public static bool operator >(MoneyBag a, MoneyBag b)
        {
            return a.CompareTo(b) > 0;
        }

        public int CompareTo(MoneyBag other)
        {
            // The shit was null, dumbass!
            if (other == null) return 0;
            if (_baseDenomination > other._baseDenomination)
            {
                return 1;
            }
            if (_baseDenomination < other._baseDenomination)
            {
                return -1;
            }
            // They were equal.
            return 0;
        }

        public static void ComputeWealth(long baseDenomination, out int platinum, out int gold, out int silver, out int copper)
        {
            platinum = ComputePlatinum(baseDenomination);
            gold = ComputeGold(baseDenomination);
            silver = ComputeSilver(baseDenomination);
            copper = ComputeCopper(baseDenomination);
        }

        public static int ComputeCopper(long baseDenomination)
        {
            return (int)Math.Floor((double)Math.Abs(baseDenomination % 100));
        }

        public static int ComputeSilver(long baseDenomination)
        {
            return (int)Math.Floor((double)Math.Abs((baseDenomination / 100) % 100));
        }

        public static int ComputeGold(long baseDenomination)
        {
            return (int)Math.Floor((double)Math.Abs((baseDenomination / 10000) % 100));
        }

        public static int ComputePlatinum(long baseDenomination)
        {
            return (int)Math.Floor((double)Math.Abs(baseDenomination / 1000000));
        }

        public override string ToString()
        {
            return
                "" + Platinum + PlatinumAbbreviation + "," +
                Gold + GoldAbbreviation + "," +
                Silver + SilverAbbreviation + "," +
                Copper + CopperAbbreviation;
        }
    }
}


简洁的用法示例(随意使用):

using Some.Arbitrary.Framework;

namespace SomeGameOrSomething
{
    static class Program
    {
        static void Main()
        {
            MoneyBag bag = new MoneyBag(1,22,44,55);
            bag.Subtract(0,50,22,0);
            Console.WriteLine(bag);
            Console.Read();
        }
    }
}


编辑/更新

如果有兴趣的人在获得所有很棒的建议后,我最终得到了什么,代码可以在这里找到:http://pastebin.com/sqVjZYry

评论

计算HasFoo时,只需重用高阶函数。 HasGold =黄金> 0 ||例如,HasPlatinum。

我为您进行了优化:999999999小于int.MaxValue,因此您可以将其全部存储为int而不是long。您也可以使用uint,因为您不允许使用负数。

旁注-在大多数RPG中,钱实际上只存储在铜中。仅出于显示目的,它分为金/银/板。另外,您让我想启动Win3.11 VM并立即播放《风之城堡》 ...

我为您做了大量优化:1024 * 128 * 128 * 128-1很小,可以容纳一个有符号的int,因此您可以轻松地放入999 * 128 * 128 * 128。这意味着您可以使用位移而不是乘以10的幂,仍然可以容纳同样多的值。

@ wizzwizz4甚至没有关闭。左移或右移仅比mod快3倍。 (自己运行... pastebin.com/rNEztD3h)考虑到RPG中发生的所有其他情况,这不是“大规模”的改进。这并不是代码的关键组成部分。如果花费超过服务器时间0.01%的钱进行计算,则某处存在严重问题。

#1 楼

对我来说,有几件事情很重要:

public const string CopperName = "Copper";
public const string SilverName = "Silver";
public const string GoldName = "Gold";
public const string PlatinumName = "Platinum";


...名称和描述性文本应该(几乎)总是放在资源文件中。为什么?国际化。这意味着如果名称更改,您不必重新编译代码。
是的,您确实必须以某种方式获取查找键,但是它们实际上并不属于这里-这是功能,但是您要输出到播放器。

public long BaseDenomination
{
     get
     {
         return _baseDenomination;
     }
     set
     {
         _baseDenomination = value;
         // Clamp if required.
         if (_baseDenomination > MaximumBaseDenomination)
         {
             _baseDenomination = MaximumBaseDenomination;
         }
         if (_baseDenomination < 0)
         {
             _baseDenomination = 0;
         }
     }
 }


在这里使用标准的最大/最小功能可能更常见:

public long BaseDenomination
{
     get
     {
         return _baseDenomination;
     }
     set
     {
         // Clamp if required.
         _baseDenomination = Math.Max(0, Math.Min(MaximumBaseDenomination, value));
     }
 }  


另外,使用并返回的值是long,但是您的最大基数恰好适合int,您可以考虑更改它。

public void Add(int platinum, int gold, int silver, int copper)
{
    BaseDenomination += platinum * 1000000;
    BaseDenomination += gold * 10000;
    BaseDenomination += silver * 100;
    BaseDenomination += copper;
}


所有这些数字?这些被称为“幻数”,而您则不需要它们。相反,您应该定义和使用常量,如下所示:

private const int SilverInCopper = 100;
private const int GoldInCopper = SilverInCopper * 100;
private const int PlatinumInCopper = GoldInCopper * 100;


您的Add(和Subtract)方法有一个更为严重的缺陷,但是:您没有请注意整数溢出。他们将接受1200枚铂金币,并愉快地将钱包的内容设置为零(由于进行了钳位-尽管这假定它接近开始时的最大值)。您也不必担心正负硬币的混合,这可能很奇怪。

如果将其更改为结构没关系,但是这里没有null检查(将抛出NullReferenceException):

public bool Equals(MoneyBag other)
{
    return _baseDenomination == other._baseDenomination;
}


接下来的两个合在一起:

public static bool operator ==(MoneyBag a, MoneyBag b)
{
    if (ReferenceEquals(a, null)) return false;
    if (ReferenceEquals(b, null)) return false;
    return a.Equals(b);
}

public static bool operator !=(MoneyBag a, MoneyBag b)
{
    if (ReferenceEquals(a, null)) return false;
    if (ReferenceEquals(b, null)) return false;
    return !a.Equals(b);
}


具有以下效果:



((MoneyBag)null) == ((MoneyBag)null)返回false 。

((MoneyBag)null) != ((MoneyBag)null)也返回false。

同样,这是否成为结构无关紧要,但是您需要验证一个类的实例是否都不为null。一方面,这将破坏平等的交换性质。相互之间实现这些运算符也更常见:

public static bool operator !=(MoneyBag a, MoneyBag b)
{
    return !(a == b);
}


比较运算符也没有null检查:

public static bool operator <(MoneyBag a, MoneyBag b)
{
    return a.CompareTo(b) < 0;
}

public static bool operator >(MoneyBag a, MoneyBag b)
{
    return a.CompareTo(b) > 0;
}


同样,结构没有问题,但是效果不好,否则:您将获得随机失败( NullReferenceException)来自其他代码,具体取决于像哪种元素排序选择枢轴。您还缺少了大于或等于和小于或等于,并且您可能想以其余四个加三个等于三个为基础(按照C / C ++约定,使用<,小于)。

public int CompareTo(MoneyBag other)
{
    // The shit was null, dumbass!
    if (other == null) return 0;
    if (_baseDenomination > other._baseDenomination)
    {
        return 1;
    }
    if (_baseDenomination < other._baseDenomination)
    {
        return -1;
    }
    // They were equal.
    return 0;
}


作为一种样式,通常认为在您为企业编写的代码中起誓或使用其他粗俗的语言,或计划向公众发布时,这是不良的形式。
更重要的是,现在null被认为与所有其他元素相同。这完全打破了平等的交换性质。如果您的集合中包含null元素,则它将中断排序和搜索(如果您期望其他内容时是否为null或断言错误,将取决于它)。相反,您应该对'底部'的空值进行排序:

 // If other is not a valid object reference, this instance is greater.
 if (other == null) return 1;


非通用IComparable参考也指出:


根据定义,任何对象的比较结果都大于(或跟随)null,并且两个null引用的比较结果彼此相等。


(出于某种原因,该注释不在通用文档中,这是对某人的严厉监督)

此外,由于要根据内部表示进行排序,因此可以使用long.CompareTo(...)

public int CompareTo(MoneyBag other)
{
    if (other == null) return 1;
    return _baseDenomination.CompareTo(other._baseDenomination);
}


...是否使用实际基础字段还是公共成员由您自己决定。

最后,关于所有ComputeX方法:

public static int ComputeCopper(long baseDenomination)
{
    return (int)Math.Floor((double)Math.Abs(baseDenomination % 100));
}

public static int ComputePlatinum(long baseDenomination)
{
    return (int)Math.Floor((double)Math.Abs(baseDenomination / 1000000));
}


...您在其中所做的所有数学运算都是整数运算。如果您不知道这是什么,我建议您仔细阅读,但要点是:

int i1 = 10;
int i2 = 3;
Console.Out.WriteLine(10 / 3); // Prints '3'


本质上,语言(几乎所有计算机语言都以这种方式工作)正在截断结果(对于正数,这等于下限)。
它们还将负数变成正数!这可能在代码的其他地方可以利用-钳位到0或返回负值。
哦,ComputePlatinum也遇到整数溢出:输入是long,但是输出是int。足够大的正值会变成...别的,很有可能是负数。您应该在这里返回long,或者首先只获取int。 (或者使用checked并引发异常,但这可能有问题)在任何情况下,我都可能按照以下方式编写方法:

public static int ComputeCopper(int baseDenomination)
{
    return baseDenomination % SilverInCopper;
}

public static int ComputeSilver(int baseDenomination)
{
    return baseDenomination % GoldInCopper / SilverInCopper ;
}

// I assume you can figure out ComputeGold

public static int ComputePlatinum(int baseDenomination)
{
    return baseDenomination / PlatinumInCopper;
}


评论


\ $ \ begingroup \ $
非常感谢您指出加减法中的疏漏。我将立即修复它。
\ $ \ endgroup \ $
– Krythic
16 Mar 16 '16 at 10:57

\ $ \ begingroup \ $
我也只想说,您的所有建议都很棒,并且经过深思熟虑。
\ $ \ endgroup \ $
– Krythic
16 Mar 16 '16 at 11:03

\ $ \ begingroup \ $
“国际化”也许意味着“内部化”? AFAIK国际化仅用于使某些内容在所有地区/平台之间都兼容。
\ $ \ endgroup \ $
– TylerH
16 Mar 17 '16 at 16:49

\ $ \ begingroup \ $
@TylerH可以肯定,这是自动更正的结果。不过,他的观点仍然明确。
\ $ \ endgroup \ $
– Krythic
16 Mar 17 '16 at 17:39

\ $ \ begingroup \ $
@TylerH-不,我的意思是国际化。但是,即使您不打算将其分布到多个区域,通常也最好将字符串从程序员那里拿走。我们常常不知道该写些什么(这是另一门学科)。它使您可以将工作转移给非程序员。即使对于单个“区域”,通常也有特定的法律要求。多个国家/地区对双语(或更多语言)提出了要求-在加拿大,如果您在魁北克经商,则必须同时包括法语和英语。首先假设事物将被更广泛地使用。
\ $ \ endgroup \ $
–钟表缪斯
16 Mar 17 '16 at 22:01

#2 楼


public enum Coins



枚举类型名称应为单数,除非用[Flags]属性修饰:



对Enum类型和值名称使用Pascal大小写。
尽量少用缩写。
不要对Enum类型名称使用Enum后缀。
对于大多数Enum类型,请使用单数名称。属于位字段的枚举类型的名称。
始终将FlagsAttribute添加到位字段枚举类型。

https://msdn.microsoft.com/zh-cn/library/4x252001( v = vs.71).aspx


因此,CoinType是一个更好的名称。现在,由于这不是[Flags]枚举,因此基本的int值完全没有意义,不需要完全指定。



public const char CopperAbbreviation = 'c';
public const char SilverAbbreviation = 's';
public const char GoldAbbreviation = 'g';
public const char PlatinumAbbreviation = 'p';



从概念上讲,这些不是常数。它们是您在此版本的代码中现在要使用的值。问题是,通过将它们公开为public const字段,如果发布了库,您几乎已经陷入了困境:如果将来的版本更改了这些值,则您将强制重新编译所有客户端代码,因为const值在编译时被“烧入到位”-因此,如果我正在使用您的库,而现在您要发布新版本,则必须重新编译代码以获取框架提供的新值。

另一方面,如果您有这样的产品:

public static readonly char CopperAbbreviation = 'c';
public static readonly char SilverAbbreviation = 's';
public static readonly char GoldAbbreviation = 'g';
public static readonly char PlatinumAbbreviation = 'p';


..那么我可以将旧版本换成新版本,更新的内容而无需重新编译任何内容。

这就是说,这些常量的唯一用途似乎是在ToString实现中,该实现将所有值连接在一起:


public override string ToString()
{
    return
        "" + Platinum + PlatinumAbbreviation + "," +
        Gold + GoldAbbreviation + "," +
        Silver + SilverAbbreviation + "," +
        Copper + CopperAbbreviation;
}



这些串联不是很漂亮,而""就像是一种破解方法,可以让+运算符进行编译并隐式获取数值转换为字符串。

如果该ToString替代用于调试目的,那么我什至不会理会XxxxAbbreviation的值-让客户端代码弄清楚它,如果您确实想要ToString替代,那么您可以简单地做到这一点:

return string.Format("{0}p, {1}g, {2}s, {3}c", Platinum, Gold, Silver, Copper);


更好的是,用DebuggerDisplayAttribute装饰班级:

[DebuggerDisplay("{Platinum}p, {Gold}g, {Silver}s, {Copper}c")]




public static void ComputeWealth(long baseDenomination, out int platinum, out int gold, out int silver, out int copper)



这些out参数很臭。不幸的是,该方法返回void,而您只需用一条小指令就可以简单地返回MoneyBag实例:

return new MoneyBag(baseDenomination);


但是,那为什么还要用那个成员?删除它是多余的。

考虑一下,整个类型实际上封装了4个int值:请考虑使其成为不可变的struct而不是类;所有的变异方法都将简单地返回一个新值。

我只更改这两个成员:


public static readonly MoneyBag FilledBag = new MoneyBag(MaximumBaseDenomination);
public static readonly MoneyBag EmptyBag = new MoneyBag();



public static readonly MoneyBag Full = new MoneyBag(MaximumBaseDenomination);
public static readonly MoneyBag Empty = new MoneyBag();


调用时,“包”是多余的。考虑:

var emptyBag = MoneyBag.EmptyBag;
var fullBag = MoneyBag.FilledBag;


vs.

var emptyBag = MoneyBag.Empty;
var fullBag = MoneyBag.Full;


评论


\ $ \ begingroup \ $
快要结束时,我开始同意你的看法,但其他一切都在挑剔。另外,此代码仅在我的游戏中使用;它不会被分发,因此我将保留const char字段。
\ $ \ endgroup \ $
– Krythic
16 Mar 16 '16 at 2:19

\ $ \ begingroup \ $
是的,ToString开头的“”是一个肮脏的东西。 =)
\ $ \ endgroup \ $
– Krythic
16 Mar 16 '16 at 2:20

\ $ \ begingroup \ $
该类实际上应该是一个结构。仅仅是因为您对GetHashCode使用了可变字段。如果坚持最佳实践是挑剔的话,那么我就是一个快乐而自豪的挑剔者。
\ $ \ endgroup \ $
–马修·金登(Mathieu Guindon)♦
16 Mar 16 '16 at 2:23

\ $ \ begingroup \ $
我将使它成为一个结构;在发布之前,我已经考虑过这一点。
\ $ \ endgroup \ $
– Krythic
16 Mar 16 '16 at 2:28

\ $ \ begingroup \ $
@Krythic-如果确实将其设为结构,则它应该是不可变的。出于优化目的,允许CLR使用小型结构来做一些有趣的事情,对它们进行突变并不总是可以像某些人所期望的那样工作。
\ $ \ endgroup \ $
–钟表缪斯
16 Mar 16 '16 at 8:32

#3 楼

您确定您的业务逻辑确实需要了解所有这些不同类型的硬币吗?对我来说,这感觉像是不必要的麻烦。维护代码总是容易得多,该代码始终将钱简单地视为long数字(带有一些其他方法)。仅在涉及UI层时,才应考虑什么是显示钱袋的最佳方法。在那里,您可以拥有一个将货币转换为硬币的转换器,您的用户会在屏幕上看到该转换器。但是它应该是一个与MoneyBag分开的类,并且仅应作为UI层的一部分存在。

关于您现有代码的其他一些信息:



实现相等方法和运算符时,应始终尝试尽可能多地重用实现。 a != b!(a == b)相同,a - ba + (-b)等,例如,您可以将相等实现为:

public override bool Equals(object obj)
{
    return Equals(obj as MoneyBag);
}

public static bool operator ==(MoneyBag a, MoneyBag b)
{
    return a.Equals(b);
}

public static bool operator !=(MoneyBag a, MoneyBag b)
{
    return !a.Equals(b); // or return !(a == b)
}

public bool Equals(MoneyBag other)
{
    //here goes the actual implementation, 
    //which you reuse in all other methods
}


这种方式可以保证所有相等方法总是返回相同的结果。而且,修复相等逻辑中的错误就像修复单个方法一样容易。例如,目前将null传递给IEquatable.Equals方法将实现throw,而使用==则可以正常工作。例如,

您具有可变的哈希码,这通常是个坏主意。

您具有可变的静态字段(FilledBagEmptyBag),这也不是一个好主意。考虑以下代码:

var myBag = MoneyBag.EmptyBag;
//blah-blah 100 lines of code
myBag.Add(...);


您可能会想:“嘿,我不是那个家伙!我不会那样使用EmptyBag!”但是,每个THOSE家伙都可能有相同的想法。

platinum * 1000000之类的东西应该提取到专用方法中,因此您不必每次都复制粘贴(或手动计数零)需要将一种货币转换为另一种货币。


评论


\ $ \ begingroup \ $
您对相等性的建议还会由于无限递归而导致堆栈溢出。
\ $ \ endgroup \ $
– Krythic
16 Mar 16 '16 at 10:34

\ $ \ begingroup \ $
@Krythic,不,我了解得很好。您确实在内部使用了很长的时间(这就是为什么我建议这样做,您可能应该使用uint代替)。但是,您还使用CoinTypes,具有诸如Silver的属性,并且在MoneyBag上具有诸如AddCoins的方法。我要问的问题是,当您只需要一个货币时,为什么要在您的业务层中处理所有这些不同的货币和兑换?不,对等方法没有递归。但是您显然不愿意进行讽刺,所以我将以此为生。祝你今天愉快。
\ $ \ endgroup \ $
– Nikita B
16-3-16在10:38



\ $ \ begingroup \ $
@Krythic答案的最重要的一点是:为什么还要在代码中烦恼所有这些?您应该在任何地方都只使用一个整数表示金钱。在用户界面中,将有一个方法“ displayMoneyParts”,它将为玩家呈现钱款。
\ $ \ endgroup \ $
– Falco
16 Mar 16 '16 at 12:15

\ $ \ begingroup \ $
@Krythic如果您只有一个货币格式化程序类,则不会。这样,您可以重用逻辑或轻松扩展它。这将是DRY,遵循SRP,并遵守开放/关闭原则。
\ $ \ endgroup \ $
– Erik
16 Mar 16 '16 at 18:58

\ $ \ begingroup \ $
@Krythic提出他的观点的另一种方法(您似乎误解了)是,存储和修改货币的业务逻辑与显示货币的逻辑应在不同的类别中。您可能想要这样做的原因之一:对于不同的语言,拥有CurrencyConverter的多个扩展(例如)将变得微不足道。也许用一种语言,您只需要3种形式的货币。无论哪种方式,用于修改用户资金的业务逻辑都不必处理货币的转换-取决于另一类。
\ $ \ endgroup \ $
–赞恩
16 Mar 19 '16 at 17:48

#4 楼

已经有很多针对代码样式和代码转换的好答案,所以我想换个角度:作为代码的潜在用户/调试器。
加/减不是反向操作
固守价值观对它似乎可以为我们提供安全保障具有吸引力,但同时也危及“最少惊喜原则”。如果我有500p,再加上800p,我最终会得到999p(和99g99s99c)。如果我再减去800p,则没有原来的500p;反之,相反,我只剩下不到200p的硬币。
如果您担心如何在有限的空间内显示大量资金,则可以考虑使用该组件。如果需要更多空间,它可能会逐渐省去铜/银/金币。如果我有那么多钱,我就不用担心我有多少铜币。 ;)
对失败没有反馈
(我不是C#程序员,所以我可能有这个错误。)
看来我可以从20个硬币中减去50个硬币,最终得到0个硬币。这让我感到惊讶。如果某个函数无法执行其声称要做的(或似乎声称要做的—棘手的),则应以某种方式发出错误信号,例如引发异常。
HasXYZ vs GetXYZ
您有200cp或2sp的地方,这里也是一样,HasCopper()将返回true,因为它还会检查HasSilver(),但是Copper()将返回0。在我看来,HasCopper()应该暗示Copper()> 0 ,因此HasCopper()应该返回false或Copper()应该给出铜币的总价值(200)。
简化或变大
我的基本建议是:

通过消除硬币类型并将其留在表示中,使事情变得更简单;
或一路走来并分别计算所有不同的硬币类型,即为每种类型都有一个计数器,然后添加一个Wealth()或Value()函数,通过添加所有硬币值可为您提供总价值。加法会更容易,但减法会更困难。

毕竟,如果这种区别没有意义,那么遇到4种不同硬币类型的麻烦有什么意义呢? (考虑一下如何使用该类:您将以铂金,金,银,铜币为商店中的商品设置价格吗?还是将基础设置为“价值”?)

评论


\ $ \ begingroup \ $
“ HasXYZ vs GetXYZ”:这个新实现如何呢?pastebin.com/b9mGqXdm?
\ $ \ endgroup \ $
– Krythic
16-3-17的1:34

\ $ \ begingroup \ $
“或者您会设定一个“价值”基础?”我将计算铜的基本价格,然后进行比较以查看玩家是否有足够的钱,然后如果有的话,只需从当前的钱中减去该金额即可。
\ $ \ endgroup \ $
– Krythic
16-3-17的1:44

\ $ \ begingroup \ $
“加/减不是逆运算”首先让我说,在我的游戏中实际达到999个白金可能永远不会发生。从概念上讲,铂金永远不会从敌人的遭遇中掉落,并且1或2金币也将是罕见的情况。铂金的上限(太大)几乎是任何人都不现实的。我早些时候设置了一个测试用例,每隔几秒钟就给我1枚银牌,直到花了白金才花了很长时间。唯一的另一件事就是下界,我对此感到满意,因为它不能消极。
\ $ \ endgroup \ $
– Krythic
16-3-17的1:48

\ $ \ begingroup \ $
@Krythic加/减逆:不消极是一件好事。不会在没有引发异常的情况下达到零。上限也是如此:如果您需要一个限制,请强制执行它,但是请考虑告知您的班级用户事情没有按计划进行。既然您永远都不会达到这个极限,那么异常不会伤害任何人,对吗? :)
\ $ \ endgroup \ $
– JvR
16 Mar 17 '16 at 12:39

\ $ \ begingroup \ $
其他地方的逻辑将处理该问题,例如将钱提取/存入游戏中的银行。如果银行已满,我将拒绝该操作。
\ $ \ endgroup \ $
– Krythic
16 Mar 17 '16 at 13:11

#5 楼

看来您已经完全混淆了业务和显示层/职责。您可以通过消除大多数功能中的c / s / g / p分隔来简化事情。例如,Add(int amount, Coins type)Add(int platinum, int gold, int silver, int copper)不需要存在。如果您只在显示器之外的任何地方都坚持基本价值,那么您将减少以下工作:


存储(这只是一个价值)
购物(同样,一个价值,而不是四个)
经济修复(您要重新计算所有值,还是在将来某个时候做prices*=1.1
任何会影响价格的部分功能(讨价还价?)

HasCopper和类似方法不是超级直观。

可变的空/装满的袋子是等待发生的事故。

我不明白为什么您使用1 goldHasCopper函数中。他们不应该永远是负面的,对吗?

#6 楼

使用单个数字类型。金/银/等只是您模型的一种视图,这将大大简化所有操作。所有价格实际上都在您的基本数字类型中,但是以友好的方式查看,并且所有数学运算仅在您的数字类型上执行。

#7 楼

我想知道您为什么要完全分裂价值观,甚至更奇怪为什么还没有指出。当您查看您的银行帐户时,您不会说我有10 $ 100加上50 $ 10您只是说我有$ 1500。

拆分您的货币只是引入了很多潜在的错误,而且对于前端。我只是让货币数量成为一个数字,然后让前端处理将其分为铜,银,金。我认为,显示货币的方式应该与您的游戏逻辑无关。

评论


\ $ \ begingroup \ $
这是设计决定。
\ $ \ endgroup \ $
– Krythic
16 Mar 17 '16 at 13:09

\ $ \ begingroup \ $
很抱歉,但是我看不到为什么您会在游戏逻辑中拆分黄金的价值,而又不将实际的硬币用于重量之类的事情。由于缺少硬舍入和使用基值,您似乎不愿意这样做。因此,为什么不仅仅使用和存储您的基本值并让您的前端进行分类,用户将获得的所有体验就是,他或她可能会看到硬币的图像,而在正常货币中,点或逗号将是硬币。 (1,000,000变成(g)1(s)000(c)000)我认为您可以通过在前端解决这个问题来简化您的问题。
\ $ \ endgroup \ $
–尼尔斯
16-3-18在8:21