I am pretty sure that everyone already used a micro-class in code. Everytime you use a static class that contains only constants, you are using a micro-class (for example ResourceKeys or Roles).
public static class Roles
{
public const string Administrator = "Administrator";
public const string PowerUser = "PowerUser";
public const string User = "User";
}
What is a micro-class?
A micro-class is a basically a wrapper class around a built-in Type (or Types). It could have overridden equality members, GetHashCode and/or have implicit operators.
public class MicroClass
{
public MicroClass(string value)
{
Value = value;
}
public string Value { get; private set; }
}
Is “property” just any “built-in type”?
Any time you are using a built-in Type, you should be asking yourself is {property} just any {built-in Type}.
- Path: Is Path just any string?
- BirthDate: Is BirthDate just any nullable DateTime?
- Amount: Is amount just any double?
- Angle: Is Angle just any double?
- Email: Is Email just any string?
When the answer to the question is ’no’, then the property is a good candidate for a micro-class.
Examples
Horoscope
When thinking about a Horoscope sign, one could just say it’s a string holding the name of the sign:
public static class Horoscope
{
public const string Aquarius = "Aquarius";
}
Or we could use an Enum to define possible values:
public enum HoroscopeEnum
{
Aquarius = 0
}
Or we could use a micro-class for it:
public class Horoscope
{
public static readonly Horoscope Aquarius = new Horoscope("Aquarius");
private Horoscope(string name)
{
Name = name;s
}
public string Name { get; private set}
}
Although the micro-class needs a little more code, it has the advantage when it comes to adding behavior and properly encapsulation that behavior. As shown here:
public class Horoscope
{
public static readonly Horoscope Aquarius = new Horoscope("Aquarius", new DateTime(1753, 1, 20), new DateTime(1753, 2, 18));
public static readonly Horoscope Pisces = new Horoscope("Pisces", new DateTime(1753, 2, 19), new DateTime(1753, 3, 20));
public static readonly Horoscope Aries = new Horoscope("Aries", new DateTime(1753, 3, 21), new DateTime(1753, 4, 19));
public static readonly Horoscope Taurus = new Horoscope("Taurus", new DateTime(1753, 4, 20), new DateTime(1753, 5, 20));
public static readonly Horoscope Gemini = new Horoscope("Gemini", new DateTime(1753, 5, 21), new DateTime(1753, 6, 21));
public static readonly Horoscope Cancer = new Horoscope("Cancer", new DateTime(1753, 6, 22), new DateTime(1753, 7, 22));
public static readonly Horoscope Leo = new Horoscope("Leo", new DateTime(1753, 7, 23), new DateTime(1753, 8, 22));
public static readonly Horoscope Virgo = new Horoscope("Virgo", new DateTime(1753, 8, 23), new DateTime(1753, 9, 22));
public static readonly Horoscope Libra = new Horoscope("Libra", new DateTime(1753, 9, 23), new DateTime(1753, 10, 23));
public static readonly Horoscope Scorpio = new Horoscope("Scorpio", new DateTime(1753, 10, 24), new DateTime(1753, 11, 21));
public static readonly Horoscope Sagittarius = new Horoscope("Sagittarius", new DateTime(1753, 11, 22), new DateTime(1753, 12, 21));
public static readonly Horoscope Capricorn = new Horoscope("Capricorn", new DateTime(1753, 12, 22), new DateTime(1753, 1, 19));
private Horoscope(string name, DateTime minDate, DateTime maxDate)
{
Name = name;
MinimumDate = minDate;
MaximumDate = maxDate;
}
public string Name { get; private set; }
public DateTime MinimumDate { get; private set; }
public DateTime MaximumDate { get; private set; }
public static IEnumerable All
{
get
{
yield return Aquarius;
yield return Pisces;
yield return Aries;
yield return Taurus;
yield return Gemini;
yield return Cancer;
yield return Leo;
yield return Virgo;
yield return Libra;
yield return Scorpio;
yield return Sagittarius;
yield return Capricorn;
}
}
public bool Matches(DateTime date)
{
var month = date.Month;
var day = date.Day;
if (month == 2 && day == 29) day--;
var bDate = new DateTime(1753, month, day);
return MinimumDate <= bDate && bDate <= MaximumDate;
}
public static Horoscope For(DateTime date)
{
return All.Single(x => x.Matches(date));
}
public override string ToString()
{
return Name;
}
}
BirthDate
With a birthdate we are usually not interested in hours, minutes or seconds, we just want to store the date of birth:
public class BirthDate
{
private readonly DateTime? birthDay;
public BirthDate(DateTime? birthday)
{
birthDay = birthday.HasValue ? birthday.Value.Date : birthday;
}
public BirthDate(int year, int month, int day)
{
birthDay = new DateTime(year, month, day);
}
public DateTime? BirthDay
{
get { return birthDay; }
}
public override string ToString()
{
return string.Format("{0:dd/MM}", birthDay);
}
}
Calculating the age becomes as easy as this:
public int CalculateAge(DateTime date)
{
date = date.Date;
if (!BirthDay.HasValue || BirthDay.Value > date) return 0;
var age = date.Year - BirthDay.Value.Year;
if (date < BirthDay.Value.AddYears(age)) age--;
return age;
}
Now with reusing the horoscope micro-class, we can add that behavior to birthdate:
public Horoscope GetHoroscope()
{
return !birthDay.HasValue ? null : Horoscope.For(birthDay.Value);
}
Angle
How about an angle micro-class, that has by default a degrees, minutes, seconds ToString() representation and normalizes negative angles etc
public class Angle
{
public Angle()
{
}
public Angle(double value)
{
this.Value = Normalize(value);
}
public double Value { get; private set; }
public int Degrees
{
get { return (int) Math.Floor(ToTimeSpan().TotalHours); }
}
public int Minutes
{
get { return ToTimeSpan().Minutes; }
}
public int Seconds
{
get { return ToTimeSpan().Seconds; }
}
private TimeSpan ToTimeSpan()
{
return TimeSpan.FromHours(Math.Abs(Value));
}
private double Normalize(double angle)
{
if (angle == 0.0) return 0.0;
var rest = Math.Abs(angle)%360;
var sign = Math.Sign(angle);
return sign == -1 ? rest\*sign + 360.0 : rest;
}
public override string ToString()
{
return string.Format("{0}° {1}' {2}\"", Degrees, Minutes, Seconds);
}
}