问题描述
我正在尝试将共享公共父对象的模型对象的集合转换为DTO之一。同样,我想反向执行此过程-将具有公共父对象的DTO集合带入模型对象之一。
从我阅读的内容来看,工厂模式似乎是我想要的。我还有一个Producer类,通过调用相关的工厂方法来处理对象模型和DTO之间的转换。
有一些限制:
- 这是一个开放源代码库,我不想向现有类中添加方法。否则,访客模式将起作用。如果我错了,请纠正我。
- 类似地,我不想向该项目添加任何其他软件包。据我了解,AutoMapper可能是解决此问题的方法之一。
- 我是C#和设计模式的新手,所以如果我做的事情没有意义,我深表歉意。
这是一些示例代码,表示到目前为止我已经尝试过的内容。我使用了网上的一些参考资料来获得一个想法,但是关于它的某些东西似乎并不正确。这里提到了另一种方式:Is a switch statement applicable in a factory method? c#,但是我不确定这是否可以转移到这种情况。
欢迎提出任何批评或建议。
示例用法
Animal pet1 = new Pigeon("Pidgey",100,false);
Animal pet2 = new Rattlesnake("Ekans",20.0,true);
IList<Animal> myPets = new List<Animal>() { pet1,pet2 };
AnimalDTOProducer dtoProducer = new AnimalDTOProducer(new AnimalDTOFactory());
IList<AnimalDTO> myDTOs = new List<AnimalDTO>();
myDTOs = dtoProducer.ConvertAnimalCollection(myPets);
模型
public abstract class Animal
{
public Animal(string name)
{
Name = name;
}
public string Name { get; set; }
// business logic
}
public abstract class Bird : Animal
{
public Bird(string name,int maxAltitude,bool isReal)
: base(name)
{
Name = name;
MaxAltitude = maxAltitude;
IsReal = isReal;
}
public int MaxAltitude { get; set; }
public bool IsReal { get; set; }
// business logic
}
public class Pigeon : Bird
{
public Pigeon(string name,bool isReal)
: base(name,maxAltitude,isReal)
{
}
// business logic
}
public abstract class Snake : Animal
{
public Snake(string name,double length,bool isPoisonous)
: base(name)
{
Name = name;
Length = length;
IsPoisonous = isPoisonous;
}
public double Length { get; set; }
public bool IsPoisonous { get; set; }
// business logic
}
public class Rattlesnake : Snake
{
public Rattlesnake(string name,bool isPoisonous)
: base(name,length,isPoisonous)
{
}
// business logic
}
DTO
public abstract class AnimalDTO { }
public class PigeonDTO : AnimalDTO
{
public string Name { get; set; }
public int MaxAltitude { get; set; }
public bool IsReal { get; set; }
}
public class RattlesnakeDTO : AnimalDTO
{
public string Name { get; set; }
public double Length { get; set; }
public bool IsPoisonous { get; set; }
}
工厂
public interface IFactory { }
public interface IAnimalFactory : IFactory
{
Animal CreateAnimal(AnimalDTO DTO);
}
public interface IAnimalDTOFactory : IFactory
{
AnimalDTO CreateAnimalDTO(Animal animal);
}
public class AnimalFactory : IAnimalFactory
{
public Animal CreateAnimal(AnimalDTO DTO)
{
switch (DTO)
{
case PigeonDTO _:
var pigeonDTO = (PigeonDTO)DTO;
return new Pigeon(pigeonDTO.Name,pigeonDTO.MaxAltitude,pigeonDTO.IsReal);
case RattlesnakeDTO _:
var rattlesnakeDTO = (RattlesnakeDTO)DTO;
return new Rattlesnake(rattlesnakeDTO.Name,rattlesnakeDTO.Length,rattlesnakeDTO.IsPoisonous);
// And many more ...
default:
return null;
}
}
}
public class AnimalDTOFactory : IAnimalDTOFactory
{
public AnimalDTO CreateAnimalDTO(Animal animal)
{
switch (animal)
{
case Pigeon _:
var _pigeon = (Pigeon)animal;
return new PigeonDTO()
{
Name = _pigeon.Name,MaxAltitude = _pigeon.MaxAltitude,IsReal = _pigeon.IsReal
};
case Rattlesnake _:
var _rattlesnake = (Rattlesnake)animal;
return new RattlesnakeDTO()
{
Name = _rattlesnake.Name,Length = _rattlesnake.Length,IsPoisonous = _rattlesnake.IsPoisonous
};
// And many more ...
default:
return null;
}
}
}
生产者
public interface IProducer { }
public interface IAnimalProducer : IProducer
{
Animal ProduceAnimalFromDTO(AnimalDTO DTO);
}
public interface IAnimalDTOProducer : IProducer
{
AnimalDTO ProduceAnimalDTOFromAnimal(Animal animal);
}
public class AnimalProducer : IAnimalProducer
{
private IAnimalFactory factory;
public AnimalProducer(IAnimalFactory factory)
{
this.factory = factory;
}
public IList<Animal> ConvertAnimalDTOCollection(IList<AnimalDTO> DTOCollection)
{
IList<Animal> result = new List<Animal>();
foreach (AnimalDTO DTO in DTOCollection)
{
var dto = ProduceAnimalFromDTO(DTO);
if (dto != null)
result.Add(dto);
}
return result;
}
public Animal ProduceAnimalFromDTO(AnimalDTO animalDTO)
{
return this.factory.CreateAnimal(animalDTO);
}
}
public class AnimalDTOProducer : IAnimalDTOProducer
{
private IAnimalDTOFactory factory;
public AnimalDTOProducer(IAnimalDTOFactory factory)
{
this.factory = factory;
}
public IList<AnimalDTO> ConvertAnimalCollection(IList<Animal> collection)
{
IList<AnimalDTO> result = new List<AnimalDTO>();
foreach (Animal animal in collection)
{
var _animal = ProduceAnimalDTOFromAnimal(animal);
if (_animal != null)
result.Add(_animal);
}
return result;
}
public AnimalDTO ProduceAnimalDTOFromAnimal(Animal animal)
{
return this.factory.CreateAnimalDTO(animal);
}
}
更新1
正如sjb-sjb和ChiefTwoPencils在评论中所建议的那样,我从各个工厂中删除了switch语句。结果看起来像这样:public class AnimalFactory : IAnimalFactory
{
public Animal CreateAnimal(AnimalDTO DTO)
{
Type srcType = DTO.GetType();
Type modelType = Type.GetType(Regex.Replace(srcType.FullName,@"(DTO)$",""));
IList<PropertyInfo> props = new List<PropertyInfo>(srcType.GetProperties());
var propVals = props.Select(prop => prop.GetValue(DTO,null)).ToArray();
Animal animal = (Animal)Activator.CreateInstance(modelType,propVals);
return animal;
}
}
public class AnimalDTOFactory : IAnimalDTOFactory
{
public AnimalDTO CreateAnimalDTO(Animal animal)
{
Type srcType = animal.GetType();
Type dtoType = Type.GetType($"{srcType.FullName}DTO");
AnimalDTO dto = (AnimalDTO)Activator.CreateInstance(dtoType,new object[] { });
foreach (PropertyInfo dtoProperty in dtoType.GetProperties())
{
PropertyInfo srcProperty = srcType.GetProperty(dtoProperty.Name);
if (srcProperty != null)
{
dtoProperty.SetValue(dto,srcProperty.GetValue(animal));
}
}
return dto;
}
}
我在原始问题中忘记提及的一件事是,模型的构造函数可能比DTO对象具有的属性具有更多的参数。那和论据的顺序可能不一样。我认为在伪代码中,解决方案将如下所示:
void AssignParamsToConstructor()
{
// Extract constructer parameters with names into an ordered list
// Match DTO properties with extracted parameters via name and type
// Fill any missing parameters with a default value or null
// Pass the final list of parameters as an array to Activator.CreateInstance method
}
我将暂时研究一种解决此问题的方法,但是任何指点都会受到欢迎。
更新2
好的,所以我找到了一个解决之前问题的解决方案,该问题涉及使用缺少或乱序的参数调用Model构造函数。我创建了一个帮助程序类,该类基于Model构造函数参数和DTO属性的组合创建了一个有序的参数数组。然后可以将该数组传递给Activator.CreateInstance而不引起任何问题。
这是更新的AnimalFactory.CreateAnimal方法:
public Animal CreateAnimal(AnimalDTO DTO)
{
Type srcType = DTO.GetType();
Type modelType = Type.GetType(Regex.Replace(srcType.FullName,""));
object[] propVals = Helpers.GenerateConstructorArgumentValueArray(modelType,DTO);
Animal animal = (Animal)Activator.CreateInstance(modelType,propVals);
return animal;
}
这是帮助程序类:
public static class Helpers
{
public static object[] GenerateConstructorArgumentValueArray(Type type,object obj)
{
IList<(string,Type)> ctorArgTypes = new List<(string,Type)>();
IList<(string,object)> propVals = new List<(string,object)>();
// Get constructor arguments
ctorArgTypes = GetConstructorArgumentsAndTypes(type);
// Get object properties
propVals = GetObjectPropertiesAndValues(obj);
// Create args array
IList<object> paramVals = new List<object>();
foreach (var ctorArg in ctorArgTypes)
{
object val;
string _name = ctorArg.Item1.ToLower();
(string,object) _namedProp = propVals.Where(prop => prop.Item1.ToLower() == _name).FirstOrDefault();
if (_namedProp.Item2 != null)
{
val = _namedProp.Item2;
}
else
{
val = ctorArg.Item2.IsValueType ? Activator.CreateInstance(ctorArg.Item2) : null;
}
paramVals.Add(val);
}
return paramVals.ToArray();
}
private static IList<(string,Type)> GetConstructorArgumentsAndTypes(Type type)
{
List<(string,Type)> ctorArgs = new List<(string,Type)>();
TypeInfo typeInfo = type.GetTypeInfo();
ConstructorInfo[] ctors = typeInfo.DeclaredConstructors.ToArray();
ParameterInfo[] ctorParams = ctors[0].GetParameters();
foreach (ParameterInfo info in ctorParams)
{
ctorArgs.Add((info.Name,info.ParameterType));
}
return ctorArgs;
}
private static IList<(string,object)> GetObjectPropertiesAndValues(object obj)
{
List<(string,object)> props = new List<(string,object)>();
PropertyInfo[] propInfo = obj.GetType().GetProperties();
foreach (PropertyInfo info in propInfo)
{
string name = info.Name;
object val = info.GetValue(obj);
props.Add((name,val));
}
return props;
}
}
稍后,我将不得不查看此内容以了解如何对其进行改进。但是暂时,它正在发挥作用。
如果您有任何意见或建议,我将不胜感激。在找到绝对解决方案之前,我将不断更新此帖子。
解决方法
因此,我制定了一种解决方案,该解决方案似乎可以实现我的最初目标。
之所以难以解决的原因是由于原始的Factory类职责过多。它必须映射属性并创建一个新对象。将它们分开可以轻松实现本文所建议的Generic Factory:
我创建了一个简单的映射器,该映射器将自动映射Entity和DTO属性。较简单的解决方案是使用像grandaCoder建议的AutoMapper。否则,我的情况需要如此,因此自定义映射器是必须的。我还尝试最小化对System.Reflection的调用,以免性能受到太大影响。
最终结果是一个Factory,它可以在任何Entity和DTO对象之间进行转换,在它们之间映射属性,并且可以在没有默认/空构造函数的情况下实例化Entity类。
我最终对原始帖子进行了很多更改,因此我将最终结果上传到了github:https://github.com/MoMods/EntityDTOFactory
对于最终解决方案,我欢迎其他任何想法/批评。这是我第一次解决此类问题,因此很可能有一些更好的主意。
再次感谢您的帮助和建议!
,使用反射可以避免使用switch语句:
public AnimalDTO ToDTO( Animal src)
{
Type srcType = src.GetType();
Type dtoType = Type.GetType(srcType.Name + "DTO");
AnimalDTO dto = (AnimalDTO)Activator.CreateInstance(dtoType,new object[] { });
foreach (PropertyInfo dtoProperty in dtoType.GetProperties()) {
PropertyInfo srcProperty = srcType.GetProperty(dtoProperty.Name);
if (srcProperty != null) {
dtoProperty.SetValue(dto,srcProperty.GetValue(src));
}
}
return dto;
}
要获取FromDTO方法,只需反转ToDTO中src和dto的角色即可。
,在这种常见情况下,我不会重新发明轮子。
https://www.nuget.org/packages/automapper/
OR
https://github.com/MapsterMapper/Mapster
https://www.nuget.org/packages/Mapster/
.......
了解如何使用这些框架之一。
下面是mapster ..............“性能”数字.......这就是我发现它的方式(有人告诉我要注意自动映射器的性能)