没有什么问题是不能通过增加一个抽象层解决的
起源
最近做FHIR(Fast Health Interoperability Resources)相关的项目,FHIR有很多个版本(STU3
、R4
、R5
等).
在一个项目里面每个版本的.net包是不兼容的,虽然是不同的NuGet包名(Hl7.Fhir.R4.Core
、Hl7.Fhir.STU3.Core
),但是模型都在一个命令空间下(Hl7.Fhir.Model
).
如果同时引用两个编译器就会报错,同时自己使用的时候也不知道用的是哪个版本.
那么如何实现在一个项目里面,支持对两个FHIR版本包的操作???
反射反射,程序员的快乐
当然脑子里的第一个反应肯定是反射了,通过反射加载包,然后反射执行不同包的不同类的方法.
public static class FHIRMultiVersionUtility
{
private static Dictionary<FHIRVersion, Assembly> fhirAssemblyDic = new Dictionary<FHIRVersion, Assembly>();
static FHIRMultiVersionUtility()
{
fhirAssemblyDic.Add(FHIRVersion.R4, Assembly.LoadFrom(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "lib", "r4", "Hl7.Fhir.R4.Core.dll")));
fhirAssemblyDic.Add(FHIRVersion.STU3, Assembly.LoadFrom(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "lib", "stu3", "Hl7.Fhir.STU3.Core.dll")));
}
public static string Serialize(object resource, ResourceFormat type, FHIRVersion version)
{
string fhirString = null;
var assembly = fhirAssemblyDic[version];
var serializerType = assembly.GetType($"Hl7.Fhir.Serialization.Fhir{type}Serializer");
var serializer = Activator.CreateInstance(serializerType, new object[] { null });
try
{
// xml json 序列化参数不一样,xml多一个root的参数
var argsLen = type == ResourceFormat.Json ? 3 : 4;
var args = new object[argsLen];
args[0] = resource;
args[1] = Enum.Parse(assembly.GetType("Hl7.Fhir.Rest.SummaryType"), "False");
for (int i = 2; i < argsLen; i++)
{
args[i] = null;
}
fhirString = (string)serializerType.GetMethod("SerializeToString").Invoke(serializer, args);
}
catch{}
return fhirString;
}
}
以上便是一小段通过反射写的狗屎序列化代码...
当项目前期的时候,需求还比较好,还能通过反射愉快的玩耍,但是当需求越来越多,时间越来越久之后,这就会发现简直鬼都不认识的代码...
懂抽象的程序员才是真正的快乐
作为一个有追求的程序员,一定要竭力阻止屎山代码的形成,那要如何改进呢???
...thinking...
答案是抽象一层,然后在不同的类库项目里面实现它,不同的类库项目就可以引用不同的FHIR开发包了,项目中只引用抽象层,用工厂反射加载抽象的实现.
public interface IFHIRService
{
FHIRVersion Version { get; }
string Serialize(object resource, ResourceFormat type);
public static class FHIRServiceFactory
{
private static Dictionary<FHIRVersion, IFHIRService> fhirServiceDictionary = new Dictionary<FHIRVersion, IFHIRService>();
private static bool isInitial = false;
public static void Initial()
{
if (isInitial)
return;
var libPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "lib");
Directory.GetDirectories(libPath).ToList().ForEach(implementPath =>
{
var interfaceAssemblyName = typeof(FHIRServiceFactory).Assembly.GetName().Name.Replace("Abstractions", string.Empty);
var fileInfo = Directory.GetFiles(implementPath).Select(f => new FileInfo(f))
.FirstOrDefault(f => f.Name.StartsWith(interfaceAssemblyName) && f.Name != interfaceAssemblyName);
if (fileInfo != null)
{
var assembly = Assembly.LoadFrom(fileInfo.FullName);
var fhirServiceType = assembly.DefinedTypes.FirstOrDefault(t => typeof(IFHIRService).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
if (fhirServiceType != null)
{
var fhirService = (IFHIRService)Activator.CreateInstance(fhirServiceType);
if (fhirServiceDictionary.ContainsKey(fhirService.Version))
{
fhirServiceDictionary[fhirService.Version] = fhirService;
}
else
{
fhirServiceDictionary.Add(fhirService.Version, fhirService);
}
}
}
});
isInitial = true;
}
public static IFHIRService Create(FHIRVersion version)
{
if (fhirServiceDictionary.ContainsKey(version))
{
return fhirServiceDictionary[version];
}
return null;
}
}
public class FHIRService : IFHIRService
{
private FhirJsonSerializer jsonSerializer = new FhirJsonSerializer();
private FhirXmlSerializer xmlSerializer = new FhirXmlSerializer();
public FHIRVersion Version => FHIRVersion.R4;
public string Serialize(object resource, ResourceFormat format)
{
if (format == ResourceFormat.Json)
{
return jsonSerializer.SerializeToString(resource);
}
else if (format == ResourceFormat.Xml)
{
return xmlSerializer.SerializeToString(resource);
}
return null;
}
}
有了FHIRService这一层的抽象之后,就可以使用硬编码实现FHIR各个版本的实现了,再也不用恶心的反射了.
经过这次事件对抽象又有了一丝丝的领悟.
没有什么问题是不能通过增加一个抽象层解决的,如果有,在增加一层. ----鲁迅.
Don't take it seriously!!!