ファクトリだと、集約ルートの生成とそれ以外のエンティティの生成はアトミックに扱えないけどビルダならアトミックに扱えますし、構築中は不正な状態も扱えます。
また、構築に関するロジックがエンティティに一切なくなります。
ビルダは特に、UI への入力直後に入力チェックする場合や、ウィザード形式で入力を進めていく場合なんかに本領発揮します。
Person.cs
public sealed partial class Person
{
private Person(string name, IList<Address> addresses)
{
_name = name;
_addresses = addresses;
}
private string _name;
private readonly IList<Address> _addresses;
public string Name
{
get { return _name; }
}
public IEnumerable<Address> Addresses
{
get { return _addresses; }
}
}
Address.cs
public sealed class partial Address
{
...
}
Person.Builder.cs
partial class Person
{
public sealed class Builder
{
[Flags]
public enum States
{
Valid = 0,
NameIsEmpty = 1,
NameIsTooLong = 2,
AddressListIsEmpty = 4,
AddressListHasError = 8,
}
public static readonly int MaxNameLength = 20;
public Builder()
{
Name = null;
AddressBuilders = new List<Address.Builder>();
}
public Builder(Person target)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
Name = target.Name;
AddressBuilders = target._addresses.Select(address => new Address.Builder(address)).ToList();
}
private readonly Person _target;
public string Name;
public readonly List<Address.Builder> AddressBuilders;
public Address.Builder AddAddress()
{
var addressBuilder = new Address.Builder();
AddressBuilders.Add(addressBuilder);
return addressBuilder;
}
public void RemoveAddress(int index)
{
AddressBuilders.RemoveAt(index);
}
public States Validate()
{
return (ValidateName() | ValidateAddresses());
}
public States ValidateName()
{
if (string.IsNullOrWhitespace(Name))
{
return States.NameIsEmpty;
}
if (MaxNameLength < Name.Length)
{
return States.NameIsTooLong;
}
return States.Valid;
}
public States ValidateAddresses()
{
States result = States.Valid;
if (AddressBuilders.Length == 0)
{
result |= States.AddressListIsEmpty;
}
foreach (var addressBuilder in AddressBuilders)
{
if (addressBuilder.Validate() != Address.Builder.States.Valid)
{
result |= States.AddressListHasError;
break;
}
}
return result;
}
public Person Build()
{
if (Validate() != States.Valid)
{
throw new InvalidOperationException();
}
var addresses = AddressBuilders.Select(builder => builder.Build()).ToList();
if (_target == null)
{
return new Person(Name, addresses);
}
else
{
_target._name = Name;
_target._addresses.Clear();
foreach (var address in addresses)
{
_target._addresses.Add(address);
}
return _target;
}
}
}
}
Address.Builder.cs
partial class Address
{
public sealed class Builder
{
[Flags]
public enum States
{
...
}
internal AddressBuilder() { ... }
public States Validate() { ... }
internal Address Build() { ... }
}
}
まだ色々思案中でこれが本当に良い案なのかどうか確信は持ててないのですが。。。
[関連書籍]
ついにエリック・エヴァンスの "Domain-Driven Design" の日本語版が出版されますよ
トラックバックURL↓
http://csharper.blog57.fc2.com/tb.php/306-feb2eea1