I am looking for a way to create a builder that can create nested objects.
I have already read this topic: Builder pattern with nested objects but that approach is modyfing model/entities which I'd like to avoid.
I have few models: Advertisment has an Owner, Owner has an Address and Address has a Contact entity.
public class Advertisment
{
public string AdvertismentId { get; set; }
public Owner Owner { get; set; }
}
public class Owner
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Street { get; set; }
public Contact Contact { get; set; }
}
public class Contact
{
public string PhoneNo { get; set; }
}
I would like to chain methods like this (or something similar):
var ad = new AdBuilder()
.WithId("123")
.WithOwner(x =>
x.WithName("NameOfOwner")
x.WithAddress(x => x.WithStreet("SomeStreet"))
.WithContact(y => y.WithPhoneNo("123-345-789"))));
I prepared the code here: https://rextester.com/TRN19779
I don't want to modify models because they are generated from JSON Schema and I keep them in a separte class library. I am going to use above builder to map one object to another. I am preparing an object to send to 3rd party consumer.
Would be someone so nice and help a bit?
CodePudding user response:
This can be done using interfaces. Each sub object
has an interface
used to construct it. For example, the Owner
class has the corresponding IOwner
interface which allows the user to populate the properties of the Owner
class
.
public interface IBuild
{
Advertisement Build( );
}
public interface IAdvertisement : IBuild
{
IAdvertisement WithID( string id );
IAdvertisement WithOwner( Action<IOwner> func );
}
public interface IOwner
{
IOwner WithName( string name );
IOwner WithAddress( Action<IAddress> func );
}
public interface IAddress
{
IAddress WithStreet( string street );
IAddress WithContact( Action<IContact> func );
}
public interface IContact
{
IContact WithPhoneNumber( string phoneNumber );
}
public class AdvertisementBuilder : IAdvertisement, IOwner, IAddress, IContact
{
private readonly Advertisement _advert = new( )
{ Owner = new( ) { Address = new( ) { Contact = new( ) } } };
private AdvertisementBuilder( )
{ }
public static IAdvertisement Create( ) =>
new AdvertisementBuilder( );
public IOwner WithAddress( Action<IAddress> func )
{
func( this );
return this;
}
public IAddress WithContact( Action<IContact> func )
{
func( this );
return this;
}
public IAdvertisement WithID( string id )
{
_advert.AdvertisementId = id;
return this;
}
public IOwner WithName( string name )
{
_advert.Owner.Name = name;
return this;
}
public IAdvertisement WithOwner( Action<IOwner> func )
{
func( this );
return this;
}
public IContact WithPhoneNumber( string phoneNumber )
{
_advert.Owner.Address.Contact.PhoneNo = phoneNumber;
return this;
}
public IAddress WithStreet( string street )
{
_advert.Owner.Address.Street = street;
return this;
}
// Here you can do some validation and make sure
// any required information has been filled in.
public Advertisement Build( ) => _advert;
}
static void Main( string[ ] _ )
{
var advertisement = AdvertisementBuilder.Create( )
.WithID( "Some id" )
.WithOwner( o => o.WithName( "Bill" )
.WithAddress( a => a.WithStreet( "Some street" )
.WithContact( c => c.WithPhoneNumber( "905-334-5839" ) ) ) )
.Build( );
}