Value Objects är objekt som bär ett värde men har inget koncept för identitet (läs mer om dem här och här). Detta betyder att vi skapar en specifik klass för ett specifikt värde och således får värdet vara det som är viktigt för objektet, inte någon specifik identifierare . Eftersom vi kapslar in värden i objekt så blir de immutable, det vill säga att när de väl är skapade så går de inte att ändra på. En stor fördel genom att skapa dessa är att vi isolerar funktionalitet till det ställe där den hör hemma istället för att sprida det över andra klasser.
Exemplet nedan är en Person-klass med Mail som sträng. Eftersom att mail är en sträng måste vi lägga in valideringen för denna i Person-klassen (vi skall inte använda extension methods). Detta gör att vi belastar Person-klassen med ansvar den inte vill ha.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Mail { get; set; }
public int Age { get; set; }
public bool IsMailValid()
{
Regex regex = new Regex(@"[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]{2,5}");
return regex.IsMatch(Mail);
}
}
Specifikationen för koden ovan
[TestFixture]
public class PersonSpecs_When_a_person_has_a_correct_mail_address
{
private Person _person;
[TestFixtureSetUp]
public void Context()
{
_person = new Person()
{
FirstName = "John",
LastName = "Doe",
Age = 20,
Mail = "john.doe@mail.net"
};
}
[Test]
public void It_should_pass_the_mail_address_validation()
{
Assert.That(_person.IsMailValid(), Is.True);
}
}
Detta är inte så bra. Om vi istället låter Mail vara ett objekt (såsom FirstName, LastName och Age) så kan vi flytta ut valideringsansvaret till Mail-klassen:
public class Person
{
public FirstName FirstName { get; set; }
public LastName LastName { get; set; }
public Mail Mail { get; set; }
public Age Age { get; set; }
}
I mail-klassen lägger vi även till kod för operationerna == och != samt GetHashCode, ToString och Equals.
public class Mail : ICanBeValidated
{
private readonly string _mail;
public Mail(string mail)
{
_mail = mail;
}
public bool IsValid()
{
Regex regex = new Regex(@"[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]{2,5}");
return regex.IsMatch(_mail);
}
public override string ToString()
{
return _mail;
}
public static bool operator ==(Mail left, Mail right)
{
if (Object.ReferenceEquals(left, right)) return true;
if ((object)left == null || (object)right == null) return false;
return left.GetHashCode() == right.GetHashCode();
}
public static bool operator !=(Mail left, Mail right)
{
return !(left == right);
}
public override int GetHashCode()
{
return _mail.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj == null) return false;
if (obj.GetType() == this.GetType()) return obj.GetHashCode() == this.GetHashCode();
return false;
}
}
Den gamla specifikationen blir då:
[TestFixture]
public class PersonSpecs_When_a_person_has_a_correct_mail_address
{
private Person _person;
[TestFixtureSetUp]
public void Context()
{
_person = new Person()
{
FirstName = new FirstName("John"),
LastName = new LastName("Doe"),
Age = new Age(20),
Mail = new Mail("john.doe@mail.net")
};
}
[Test]
public void It_should_pass_the_mail_address_validation()
{
Assert.That(_person.Mail.IsValid(), Is.True);
}
}
Men vänta nu, vi kan ju göra specifikationen mycket enklare. Eftersom att vi har brutit ut mail så kan vi specificera valideringsmetoden utan att behöva ett person-objekt (vi kan använda vårt fina interface):
[TestFixture]
public class MailSpecs_When_having_a_correct_mail_address
{
private ICanBeValidated _mail;
[TestFixtureSetUp]
public void Context()
{
_mail = new Mail("john.doe@mail.net");
}
[Test]
public void It_should_pass_the_mail_address_validation()
{
Assert.That(_mail.IsValid(), Is.True);
}
}
Vi kan jämföra två olika Mail-objekt för att se om de är samma:
[TestFixture]
public class MailSpecs_When_having_two_mail_with_same_address
{
private Mail _mail1;
private Mail _mail2;
[TestFixtureSetUp]
public void Context()
{
_mail1 = new Mail("john.doe@mail.net");
_mail2 = new Mail("john.doe@mail.net");
}
[Test]
public void They_should_be_marked_as_equal_when_comparing_them()
{
Assert.That(_mail1 == _mail2);
}
}
Läs mer om value objects
här eller
här.