领域驱动(DDD)实战---月份类YearMonth
Net中有一个DateTime结构类,涉及时间和日期,这个类大量使用。可是,他的名称已经显著的表明他是表达某个具体的时刻。当我们不需要每天的具体时间时,如:我的程序逻辑仅仅需要年月(发工资的周期?),这个DateTime显得有些累赘,甚至不合用。
一般人们解决的方式,仍然使用DateTime而从数据上,设置hour,mintue等等为0。 然而,这与DDD的理念相背,名称有与含义有偏差,另外,数据一致性的维护,散布在各个角落,如,保证日期始终为1,小时,分钟为0。另外,与月份相关的功能,如:得到下一个月份,要么用DateTime本身的功能(AddMonths),要么提炼出一个Utitlies出来。 前者,需要开发者时刻重复DateTime到YearMonth的映射逻辑,后者是个反模式。 (本文版权属于© 2012 – 2013 予沁安)
这里,我创建出一个基本类型YearMonth,可以作为代码的基本砖块。
[代码] 用Extension的方式,来增强代码流畅性和可读性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public static class YearMonthExtension
{
public static YearMonth year( this int year, int month)
{
return new YearMonth(year, month);
}
public static bool is_later_than( this YearMonth left, YearMonth right) {
return left.CompareTo(right) > 0;
}
public static bool is_ealier_than( this YearMonth left, YearMonth right) {
return left.CompareTo(right) < 0;
}
public static YearMonth get_ealier( this YearMonth left, YearMonth right)
{
if (left.is_ealier_than(right))
return left;
return right;
}
public static YearMonth get_later( this YearMonth left, YearMonth right)
{
if (left.is_later_than(right))
return left;
return right;
}
} |
[代码] 从测试看功能:公用的测试基类,很简单,就是声明一个YearMonth对象做测试
1
2
3
4
5
|
public class YearMonthSpecs
{
protected static YearMonth subject;
}
|
[代码] 通过构造器,创建YearMonth对象
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class When_init_by_year_month
:YearMonthSpecs
{
private Because of =
() => subject = new YearMonth(2011,3);
private It year_should_set_properly =
() => subject.Year.ShouldEqual(2011);
private It month_should_set_properly =
() => subject.Month.ShouldEqual(3);
}
|
[代码] 通过流畅接口创建YearMonth: 2012.year(3)。你还可以自己定制为: 2012.年(3)
1
2
3
4
5
|
public class When_create_year_month_through_fluent_interface
{
private It should_create_year_month_properly =
() => 2012.year(3).ShouldEqual( new YearMonth(2012, 3));
}
|
[代码] 通过字符串创建
1
2
3
4
5
6
7
8
9
10
11
12
|
public class When_init_by_string : YearMonthSpecs
{
private Because of =
() => subject = new YearMonth( "2011年01月" );
private It year_should_set_properly =
() =>
{
subject.Year.ShouldEqual(2011);
subject.Month.ShouldEqual(1);
};
}
|
[代码] Special Case模式,特别处理:世界末日的下一个月还是世界末日,创世纪的上一个月还是创世纪
1
2
3
4
5
6
7
8
9
10
|
private It far_past_last_month_is_still_far_past =
() => YearMonth.FarPast.get_last().ShouldEqual(YearMonth.FarPast);
private It far_past_next_month_is_still_far_past =
() => YearMonth.FarPast.get_next().ShouldEqual(YearMonth.FarPast);
private It far_future_last_month_is_stil_far_future =
() => YearMonth.FarFuture.get_last().ShouldEqual(YearMonth.FarFuture);
private It far_future_next_month_is_stil_far_future =
() => YearMonth.FarFuture.get_next().ShouldEqual(YearMonth.FarFuture);
|
YearMonth结构类型的完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
using System;
using Skight.Arch.Domain.Interfaces;
namespace Skight.Arch.Domain.Entities
{ public struct YearMonth : IEquatable<YearMonth>, IComparable<YearMonth>
{
private readonly int ticks;
private readonly int year;
private readonly int month;
private const int MONTHS_PER_YEAR=12;
public static YearMonth FarPast = new YearMonth(0,1);
public static YearMonth FarFuture = new YearMonth(9999,12);
#region Constructors by ticks, year/month and datetime
internal YearMonth( int ticks)
{
this .ticks = ticks;
int remain;
year = Math.DivRem(ticks-1, MONTHS_PER_YEAR, out remain);
month = remain + 1;
}
public YearMonth( int year, int month)
: this (year*MONTHS_PER_YEAR + month){}
public YearMonth(DateTime date_time)
: this (date_time.Year,date_time.Month){}
public YearMonth( string yearMonth): this ( int .Parse(yearMonth.Substring(0, 4))
, int .Parse(yearMonth.Substring(5, 2)))
{}
#endregion
public int Year { get { return year; } }
public int Month { get { return month; } }
public DateTime as_date_Time()
{
return new DateTime(Year,Month,1);
}
public override string ToString()
{
return string .Format( "{0}年{1}月" , year.ToString( "0000" ), month.ToString( "00" ));
}
#region Euqals and Compare
public bool Equals(YearMonth other)
{
return other.ticks == ticks;
}
public override bool Equals( object obj)
{
if (ReferenceEquals( null , obj)) return false ;
if (obj.GetType() != typeof (YearMonth)) return false ;
return Equals((YearMonth) obj);
}
public override int GetHashCode()
{
return ticks;
}
public int CompareTo(YearMonth other)
{
return ticks.CompareTo(other.ticks);
}
#endregion
#region Discrete interface
public YearMonth get_last()
{
if (Equals(FarPast))
return FarPast;
if (Equals(FarFuture))
return FarFuture;
return new YearMonth(ticks - 1);
}
public YearMonth get_last( int Dvalue)
{
if (Equals(FarPast))
return FarPast;
if (Equals(FarFuture))
return FarFuture;
return new YearMonth(ticks - Dvalue);
}
public YearMonth get_next()
{
if (Equals(FarPast))
return FarPast;
if (Equals(FarFuture))
return FarFuture;
return new YearMonth(ticks + 1);
}
public YearMonth get_next( int DValue)
{
if (Equals(FarPast))
return FarPast;
if (Equals(FarFuture))
return FarFuture;
return new YearMonth(ticks + DValue);
}
#endregion
public static implicit operator DateTime(YearMonth year_month)
{
return year_month.as_date_Time();
}
public static implicit operator YearMonth(DateTime date_time)
{
return new YearMonth(date_time);
}
}
} |