哎呀.这里有几个要讨论的主题.
杂类
是Dto
,不是DTO
.出于同样的原因,在Java中它是DvdPlayer
,而不是DVDPlayer
-缩写,即使通常用全大写,在驼峰大小写时也是not全大写.
BD精度
记录是存储引用和值的容器.这不是价值itself的问题.因此,您想要的东西是做不到的,但是,您可以在构建过程中制作新的BD:
record BridgeDto(BigDecimal span) {
public BridgeDto {
this.span = span.setScale(2);
}
}
这会将输入的比例增加或减少到2和returns a new BigDecimal(因此,通常,BridgeDto b = new BridgeDto(x); System.out.println(b.span() == x);
将打印false
!)
如果提供的值不能缩小到2(例如,因为它是14.955),则会导致您可能希望发生的异常.有其他 Select (例如,.setScale(2, RoundingMode.FLOOR)
),但使用BigDecimal作为价格,然后引入静默舍入,总而言之,是愚蠢的.
这不是你定价的方式
只有3种重要的方式来表示金钱:
- 坚持原子性
- 允许亚原子性
- 全力以赴,有一个代表地段的值,including是哪种货币.
"原子"是指这样一种货币单位,即这种货币的"1"是银行系统为该货币所能代表的最小金额.
对于欧元、美元和许多其他货币来说,原子是"美分".对于英镑,它是便士.对于日元,它是..日元对于比特币来说,它是聪.每种货币都有原子.
我想要亚原子公司
我真的很怀疑这一点.
事情是这样的:如果你do not想要亚原子,那么you do not want 100.
想象一下下面的 idea :
你是一家银行.您允许创建共享的联合帐户,其中帐户的所有者始终拥有相同份额的帐户.亚历克斯、布伦特和卡罗琳创建了一个联合账户.
最终,欧元1.04将进入联合账户.
在这一点上,三个所有者共同决定取消联合账户.
您的银行有一个执行此操作的原始操作:"jointAccount.cancel()
".它的实现与您预期的一样:帐号是释放的,或者至少标记为非活动的,并且是the funds are transferred to the joint holders.
到目前为止,很明显,对吧?这不应该给人的印象是一个人为的例子.
问题是,how?1.04不能被3整除.即使你引入亚原子也不行!
以下是几个错误答案:
我们使用BigDecimal数学将1.04除以3.这会导致例外,因为您不能这样做(试试看!),因此,银行的文档被更新,列出了取消联名账户是不可能的,除非其中的金额完全可以被联名持有人的数量整除.
我们使用具有设置舍入模式的BigDecimal数学将1.04除以3.这会导致1美分的出现或消失,谁知道呢.一些有进取心的灵魂想出了一种方法,与100名持有者重复建立一个联合账户,向其中转账76美分,注销,然后看着银行给每个持有者转账1整分,每次这样做都能赚到24美分.银行计算出这一点,并将代码更新为始终向下舍入.在这一点上,有人计算着与100个持有者开立一个联合账户,让它变成-99美分,取消它,然后观察每个联合持有者是如何借记0美分的,因为现在它会四舍五入.银行再次注意到了这一点,并解雇了最初决定与亚原子公司打交道的开发团队.
正确答案:
- 授权"取消联合账户"的代码从根本上理解,"分配现金"不是可以概括为数学模型的东西,而是需要在任何系统中出现"分配一些现金"时进行定制设计的东西.
jointAccount.cancel()
的代码实际上将尽可能平均地分配资金/债务,然后获取剩余部分,并在帐户持有人之间随机分配(如随机数生成器中的那样).换句话说,如果亚历克斯、布伦特和卡罗琳注销了他们的欧元1.04账户,他们中的两个随机获得35美分,其中一个获得34美分.或者,银行在他们的服务条款中定义这样的情况是对银行有利的:他们都得到34美分(但如果余额是-1.04欧元,他们都会损失35美分!)
那么为什么不是BD呢?
因为这只是误导.BD的主要目的是让你代表亚原子公司.如果你认为‘任何时候任何算法涉足亚原子,那实际上都是错误的’,那么为什么要使用它呢?它只是在嘲弄你(因此,误导了任何阅读你代码的人,毕竟,如果你使用了‘主要是做Y的功能X’,人们就会假设Y是重要的,你需要a lot的 comments 来解释Y是应该避免的!
替代方案-没有亚原子
用long
来表示你的金钱价值.它们代表atomics,而不是标准货币.换句话说,Alex、Brent和Caroline的联合账户余额是long v = 104;
.
但是..这不会正确地格式化!
但BD并没有解决这个问题.任何时候要向用户显示任何货币金额,都需要将其传递给能够执行此操作的格式化程序.String.format("€%d.%02d", v / 100, Math.abs(v % 100))
美元就够了.
这感觉太低了
然后将"如何将其存储在DB中"与"如何在Java中表示"分开,并使用例如Joda-money.
外汇呢?
外汇汇率通常是一个非常长的十进制数字.对于这样的操作,获取您的值(以long
为单位),将其转换为BD,将其乘以外汇汇率(也是BD),获取结果,根据代码对其进行舍入(通常,根据发生的事情和值的符号,银行倾向于向上或向下舍入,因此,您需要一组if
条语句才能正确完成此操作),然后您返回到long
,您可以存储、发送到其他方法、进行API调用、存储在数据库中,等等.
坚持使用BD不是解决方案(考虑到您需要舍入最终结果或找出存储/转移亚原子的方法,如果您要经历所有这些麻烦,只需使用long
).
不变的DTO
那是...不对.DTO对象表示‘数据库中的行’,行通常是可变的.类应该准确地对它们所表示的事物进行建模.如果它们所代表的东西被锁定到一个您不能更改的原则(以及"数据库中的行",除非您发明了自己的数据库引擎,否则您在语义级别上可以扩展的程度受到一定程度的限制),那么您需要尽可能准确地对其进行建模,这包括如果底层概念是可变的话."首选不变性"指的是这样一种概念,即通常情况下,您可以控制事物的含义,因此构建您的应用程序时要围绕不可变性进行设计.
例如,git(版本控制系统)将所有提交视为不可变的,并相应地进行架构设计.在git架构设计中唯一可变的东西是分支名称.因此,'amend a commit'被翻译成'make an entirely new commit,given that amending is impossible as commits are immutable,then update the main
branch ref to this newly created commit'.
DTO通常有一个save()
的方法.如果您的DTO是不变的,那么您必须重新设计事物的工作方式.DTO是低级的:如果其中一个的API不完全符合您的喜好,那也没问题--创建第二个"层"API,它使用DTO来完成其工作.That one可以围绕不可变变量构建.