I have a domain object, Address that may be populated from a variety of data sources, which requires a lot of mapping code. In the interest of "Closed to Modification" I want to be able to create individual "Mappers" for each data source. I can then pass the mapper into an instance of the Address and VOILA! get an appropriate data entity back in response. And vice-versa, I also want implement a method on that Address that will allow me to map an entity into a new or to populate an existing instance of the Address.
I create my Address object ...
public class Address
{
public string Street1 { get; set; }
public string Street2 { get; set; }
public string Street3 { get; set; }
public string Street4 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
public string PostalCode { get; set; }
}
Now I create a couple of classes that will facilitate mapping specific data entity objects to and from this Address object.
//
// Maps to and from a Database object (DB1_ADDRESS)
//
public class DB1AddressMapper
{
property DB1_ADDRESS _entity;
public DB1AddressMapper()
{
}
public DB1AddressMapper(DB1_ADDRESS entity)
{
_entity = entity;
}
public DB1_ADDRESS MapModelToEntity(Address model)
{
DB1_ADDRESS ret = new DB1_ADDRESS();
<... mapping logic goes here>
return ret;
}
public Address MapEntityToModel()
{
Address ret = new Address();
<... mapping logic goes here>
return ret;
}
}
//
// Maps to and from a WebService response (WS_ADDRESS)
//
public class WSAddressMapper
{
property WS_ADDRESS _entity;
public WSAddressMapper()
{
}
public WSAddressMapper(WS_ADDRESS entity)
{
_entity = entity;
}
public WS_ADDRESS MapModelToEntity(Address model)
{
WS_ADDRESS ret = new WS_ADDRESS();
<... mapping logic goes here>
return ret;
}
public Address MapEntityToModel()
{
Address ret = new Address();
<... mapping logic goes here>
return ret;
}
}
Now that I have my mappers I create a method on Address that I can pass them into, in order to facilitate transforming the data. So you can see in the code below that I have had to overload the methods because each mapper has its own types involved. This means that every time I want to add a new data source to populate the Address object I have to re-open Address and add new overload methods. Ugghhh ... no thanks you (what happened to "closed for modification"?)
public class Address
{
public string Street1 { get; set; }
public string Street2 { get; set; }
public string Street3 { get; set; }
public string Street4 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
public string PostalCode { get; set; }
//
// Populate "this" instance of the Address object from data found in the mapper.
// The "mapper" argument would have to have been instantiated with the entity it expects to map
// to the Domain object, Address
//
public Address MapToModel(DB1AddressMapper mapper)
{
return mapper.MapEntityToModel();
}
//
// Map "this" instance of address to a new DB1_ADDRESS instance
//
public DB1_ADDRESS MapToEntity(DB1AddressMapper mapper)
{
return mapper.MapModelToEntity(this);
}
//
// And now again for WSAddressMapper
//
public Address MapToModel(WSAddressMapper mapper)
{
return mapper.MapEntityToModel();
}
//
// Map "this" instance of address to a new WS_ADDRESS instance
//
public WS_ADDRESS MapToEntity(WSAddressMapper mapper)
{
return mapper.MapModelToEntity(this);
}
}
This leads me to interfaces and generics ... which I have dabbled in for years but the lack of necessity for them has not forced me to deepen my understanding of them (which I believe holds me back).
Back to the problem at hand ... I only want two mapping methods in Address that will be "closed for modification". They need to accommodate any mapper for any data source I run into. The mapper encapsulates all the specific mapping logic and Address doesn't really care about the details. It just wants to "MapTo".
The pseudo-code solution looks something like this ...
public class Address
{
public Address MapToModel(EntityMapper mapper)
{
...
}
public EntityAddress MapToEntity(EntityMapper mapper)
{
...
}
}
It seems that I could make an interface for the mappers so that all mappers will implement the same two methods ...
MapModelToEntity();
MapEntityToModel();
I start with that ...
public interface IEntityAddressMapper
{
Address MapEntityToModel();
T MapModelToEntity<T>(Address model);
}
You may be able to see where I start to run into trouble. Since the return type of the "MapModelToEntity" varies from data source to data source I don't know what to make this. I opt to make it a generic; those have worked for me in other areas. I press on by implementing it in my mappers in hopes that an answer will reveal itself.
public class DB1AddressMapper : IEntityAddressMapper
{
Address MapEntityToModel()
{
Address ret = new Address();
<... mapping logic goes here>
return ret;
}
//
// This is what I want but, does NOT satisfy interface
//
DB1_ADDRESS MapModelToEntity(Address model) <!-- DOES NOT SATISFY INTERFACE
{
DB1_ADDRESS ret = new DB1_ADDRESS();
<... mapping logic goes here>
return ret;
}
//
// This satisfies interface but is silly. The mapper already KNOWS the TYPE, that's the point.
// Besides this means that the consumer will have to pass in the types, which is EXACTLY what
// I am trying to avoid.
//
T MapModelToEntity<T>(Address model)
{
DB1_ADDRESS ret = new DB1_ADDRESS();
<... mapping logic goes here>
return ret;
}
}
I've tried a million different permutations so it's impractical to list them all here but suffice to say the closest I have come so far is the following ...
public interface IEntityAddressMapper<EntityType>
{
EntityType MapModelToEntity(Address mode);
void MapModelToEntity(Address model, ref EntityType entity);
Address MapEntityToModel(EntityType entity);
void MapEntityToModel(EntityType entity, ref Address model);
}
public class DB1AddressMapper : IEntityAddressMapper<DB1_ADDRESS>
{
Address MapEntityToModel()
{
Address ret = new Address();
<... mapping logic goes here>
return ret;
}
T MapModelToEntity(Address model)
{
DB1_ADDRESS ret = new DB1_ADDRESS();
<... mapping logic goes here>
return ret;
}
}
This seems to allow me to implement the Interface without a problem now, but I seem to have shifted the burden to the methods which now are breaking ...
public class Address
{
// *********************************************
// ERROR - Using generic type 'IEntityAddressMapper<EtityType>' requires one type argument
// *********************************************
public Address MapToModel(EntityMapper mapper)
{
...
}
// *********************************************
// ERROR - Using generic type 'IEntityAddressMapper<EtityType>' requires one type argument
// *********************************************
public EntityAddress MapToEntity<EntityType>(EntityMapper mapper)
{
...
}
}
I'm spinning in circles and have been for years on this. I need to sort this out!! Any help would be greatly appreciated.
Thanks
Aucun commentaire:
Enregistrer un commentaire