C# 接口和泛型协同工作
C# Interfaces & Generics working together
我有一个域对象 Address,它可以从各种数据源填充,这需要大量映射代码。为了“Closed to Modification”,我希望能够为每个数据源创建单独的 "Mappers"。然后我可以将映射器传递到 Address 的实例中,瞧!获得适当的数据实体作为响应。反之亦然,我还想在 Address 上实现一个方法,该方法允许我将实体映射到新实体或填充 Address[ 的现有实例=54=].
我创建我的地址对象...
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; }
}
现在我创建了几个 类,它们将有助于将特定数据实体对象映射到此 Address 对象。
//
// 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;
}
}
现在我有了我的映射器,我在 Address 上创建了一个方法,我可以将它们传入,以便于转换数据。所以你可以在下面的代码中看到我不得不重载这些方法,因为每个映射器都有自己的类型。这意味着每次我想添加一个新的数据源来填充 Address 对象时,我都必须重新打开 Address 并添加新的重载方法。啊……不,谢谢你("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);
}
}
这让我想到了接口和泛型……我已经涉足多年,但它们缺乏必要性并没有迫使我加深对它们的理解(我相信这阻碍了我)。
回到手头的问题...我只想要 Address 中的两个映射方法,即 "closed for modification"。他们需要为我 运行 放入的任何数据源容纳任何映射器。映射器封装了所有特定的映射逻辑,Address 并不真正关心细节。它只是想"MapTo"。
伪代码解决方案看起来像这样...
public class Address
{
public Address MapToModel(EntityMapper mapper)
{
...
}
public EntityAddress MapToEntity(EntityMapper mapper)
{
...
}
}
看来我可以为映射器创建一个接口,以便所有映射器都将实现相同的两个方法...
MapModelToEntity();
MapEntityToModel();
我从那个开始...
public interface IEntityAddressMapper
{
Address MapEntityToModel();
T MapModelToEntity<T>(Address model);
}
您或许能看出我从哪里开始 运行 陷入困境。由于 "MapModelToEntity" 的 return 类型因数据源而异,我不知道该怎么做。我选择让它成为通用的;这些在其他领域对我有用。我继续在我的映射器中实施它,希望答案会自己揭晓。
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;
}
}
这似乎让我现在可以毫无问题地实现接口,但我似乎已经将负担转移到现在正在破坏的方法上......
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)
{
...
}
}
我一直在原地打转,多年来一直在做这件事。我需要解决这个问题!!任何帮助将不胜感激。
谢谢
您需要从一个通用接口开始,该接口因两个类型参数而异,而不是一个。这将解决您眼前的问题,但在解决之后,我想建议一个不同的方法。
解决方案
考虑这个界面
public interface IMapper<TModel, TEntity>
{
TEntity MapModelToEntity(TModel source);
TModel MapEntityToModel();
}
它允许您创建可以传递给您的特定实现 Address
class:
public class DatabaseAddressMapper : IMapper<Address, DB1_ADDRESS>
{
public DB1_ADDRESS MapModelToEntity(Address source) { ... }
Address MapEntityToModel()
}
public class WSAddressMapper : IMapper<Address, WS_ADDRESS>
{
public WS_ADDRESS MapModelToEntity(Address source) { ... }
Address MapEntityToModel()
}
并修改 Address
使其具有一些可以接受您的映射器的通用方法
// only need TEntity for this generic method because we know we are an Address
public Address MapToModel<TEntity>(IMapper<Address, TEntity> mapper)
{
return mapper.MapEntityToModel();
}
// only need TEntity for this generic method because we know we are an Address
public TEntity MapToEntity<TEnity>(IMapper<Address, TEntity> mapper)
{
return mapper.MapModelToEntity(this);
}
通过此设置,您的 Address
class 现在遵循 Open/Closed Principal 因为它可以接受任何支持 Address
和任意类型的映射器。这也将您的 Address
与其他类型完全分离,这很好。
备选
这里还有改进的余地,而且比较简单。问问自己:为什么 Address
需要知道任何关于映射的知识?这只是一个地址。映射是其他事情的关注点:也许是调用者本身?
您可以完全删除 Address
中的方法,并在您调用这些方法的调用方中删除这些方法
var mapper = new WSAddressMapper();
var model = address.MapToModel<WS_ADDRESS>(mapper);
var entity = address.MapToEntity();
可以直接调用mapper
var model = mapper.MapEntityToModel<WS_ADDRESS>();
var entity = mapper.MapModelToEntity(address);
这意味着您的 Address
现在也服从 Single Responsibility Principle!毕竟是您的调用者在启动映射; 它应该有这个责任,而不是地址本身。
继续加油!
为什么一个接口可以双向映射?毕竟,您更有可能为一段代码映射一个方向(例如保存到数据库)并在另一段代码中映射另一个方向(例如从数据库读取)。
让我们把界面分成两部分,每部分只有一个方法。我会把这个实现留给你,但你得到的是:另一个 SOLID pillar, the Interface Segregation Principle。呜呜呜!
我有一个域对象 Address,它可以从各种数据源填充,这需要大量映射代码。为了“Closed to Modification”,我希望能够为每个数据源创建单独的 "Mappers"。然后我可以将映射器传递到 Address 的实例中,瞧!获得适当的数据实体作为响应。反之亦然,我还想在 Address 上实现一个方法,该方法允许我将实体映射到新实体或填充 Address[ 的现有实例=54=].
我创建我的地址对象...
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; }
}
现在我创建了几个 类,它们将有助于将特定数据实体对象映射到此 Address 对象。
//
// 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;
}
}
现在我有了我的映射器,我在 Address 上创建了一个方法,我可以将它们传入,以便于转换数据。所以你可以在下面的代码中看到我不得不重载这些方法,因为每个映射器都有自己的类型。这意味着每次我想添加一个新的数据源来填充 Address 对象时,我都必须重新打开 Address 并添加新的重载方法。啊……不,谢谢你("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);
}
}
这让我想到了接口和泛型……我已经涉足多年,但它们缺乏必要性并没有迫使我加深对它们的理解(我相信这阻碍了我)。
回到手头的问题...我只想要 Address 中的两个映射方法,即 "closed for modification"。他们需要为我 运行 放入的任何数据源容纳任何映射器。映射器封装了所有特定的映射逻辑,Address 并不真正关心细节。它只是想"MapTo"。
伪代码解决方案看起来像这样...
public class Address
{
public Address MapToModel(EntityMapper mapper)
{
...
}
public EntityAddress MapToEntity(EntityMapper mapper)
{
...
}
}
看来我可以为映射器创建一个接口,以便所有映射器都将实现相同的两个方法...
MapModelToEntity();
MapEntityToModel();
我从那个开始...
public interface IEntityAddressMapper
{
Address MapEntityToModel();
T MapModelToEntity<T>(Address model);
}
您或许能看出我从哪里开始 运行 陷入困境。由于 "MapModelToEntity" 的 return 类型因数据源而异,我不知道该怎么做。我选择让它成为通用的;这些在其他领域对我有用。我继续在我的映射器中实施它,希望答案会自己揭晓。
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;
}
}
这似乎让我现在可以毫无问题地实现接口,但我似乎已经将负担转移到现在正在破坏的方法上......
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)
{
...
}
}
我一直在原地打转,多年来一直在做这件事。我需要解决这个问题!!任何帮助将不胜感激。
谢谢
您需要从一个通用接口开始,该接口因两个类型参数而异,而不是一个。这将解决您眼前的问题,但在解决之后,我想建议一个不同的方法。
解决方案
考虑这个界面
public interface IMapper<TModel, TEntity>
{
TEntity MapModelToEntity(TModel source);
TModel MapEntityToModel();
}
它允许您创建可以传递给您的特定实现 Address
class:
public class DatabaseAddressMapper : IMapper<Address, DB1_ADDRESS>
{
public DB1_ADDRESS MapModelToEntity(Address source) { ... }
Address MapEntityToModel()
}
public class WSAddressMapper : IMapper<Address, WS_ADDRESS>
{
public WS_ADDRESS MapModelToEntity(Address source) { ... }
Address MapEntityToModel()
}
并修改 Address
使其具有一些可以接受您的映射器的通用方法
// only need TEntity for this generic method because we know we are an Address
public Address MapToModel<TEntity>(IMapper<Address, TEntity> mapper)
{
return mapper.MapEntityToModel();
}
// only need TEntity for this generic method because we know we are an Address
public TEntity MapToEntity<TEnity>(IMapper<Address, TEntity> mapper)
{
return mapper.MapModelToEntity(this);
}
通过此设置,您的 Address
class 现在遵循 Open/Closed Principal 因为它可以接受任何支持 Address
和任意类型的映射器。这也将您的 Address
与其他类型完全分离,这很好。
备选
这里还有改进的余地,而且比较简单。问问自己:为什么 Address
需要知道任何关于映射的知识?这只是一个地址。映射是其他事情的关注点:也许是调用者本身?
您可以完全删除 Address
中的方法,并在您调用这些方法的调用方中删除这些方法
var mapper = new WSAddressMapper();
var model = address.MapToModel<WS_ADDRESS>(mapper);
var entity = address.MapToEntity();
可以直接调用mapper
var model = mapper.MapEntityToModel<WS_ADDRESS>();
var entity = mapper.MapModelToEntity(address);
这意味着您的 Address
现在也服从 Single Responsibility Principle!毕竟是您的调用者在启动映射; 它应该有这个责任,而不是地址本身。
继续加油!
为什么一个接口可以双向映射?毕竟,您更有可能为一段代码映射一个方向(例如保存到数据库)并在另一段代码中映射另一个方向(例如从数据库读取)。
让我们把界面分成两部分,每部分只有一个方法。我会把这个实现留给你,但你得到的是:另一个 SOLID pillar, the Interface Segregation Principle。呜呜呜!