Net中有一个DateTime结构类,涉及时间和日期,这个类大量使用。可是,他的名称已经显著的表明他是表达某个具体的时刻。当我们不需要每天的具体时间时,如:我的程序逻辑仅仅需要年月(发工资的周期?),这个DateTime显得有些累赘,甚至不合用。
一般人们解决的方式,仍然使用DateTime而从数据上,设置hour,mintue等等为0。 然而,这与DDD的理念相背,名称有与含义有偏差,另外,数据一致性的维护,散布在各个角落,如,保证日期始终为1,小时,分钟为0。另外,与月份相关的功能,如:得到下一个月份,要么用DateTime本身的功能(AddMonths),要么提炼出一个Utitlies出来。 前者,需要开发者时刻重复DateTime到YearMonth的映射逻辑,后者是个反模式。 (本文版权属于© 2012 - 2013 予沁安)
这里,我创建出一个基本类型YearMonth,可以作为代码的基本砖块。
[代码] 用Extension的方式,来增强代码流畅性和可读性
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对象做测试
- public class YearMonthSpecs
- {
- protected static YearMonth subject;
- }
[代码] 通过构造器,创建YearMonth对象
- 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)
- public class When_create_year_month_through_fluent_interface
- {
- private It should_create_year_month_properly =
- () => 2012.year(3).ShouldEqual(new YearMonth(2012,3));
- }
[代码] 通过字符串创建
- 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模式,特别处理:世界末日的下一个月还是世界末日,创世纪的上一个月还是创世纪
- 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结构类型的完整代码
- 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);
- }
- }
- }