질문에서 한 세트의 클래스 세트에서 다른 세트 세트로의 매핑은 매우 간단 해 보입니다. 그러나 종종 입력 클래스를 기반으로 출력 클래스의 특정 생성자 및/또는 속성을 호출하려고합니다. 때로는 AutoMapper과 같은 라이브러리를 사용할 수 있습니다.
그러나 다른 경우에는 각 변환에 대한 특정 팩토리 메소드를 만들어야합니다. 귀하의 경우는 팩토리 메소드는 Bar2
등 : 당신은 사전에 위임하고 있기 때문에 모든 팩토리 메소드를 저장 한 후 공장을 선택하는 입력 형식을 사용할 수 있습니다
Foo1 CreateFoo1(Bar1 bar1) { ... }
Foo2 CreateFoo2(Bar2 bar2) { ... }
에서 Foo1
에서 Bar1
, Foo2
을 생성하는 것입니다 출력 유형을 만듭니다. 이 사전을 구축 할 수 있으며, 공장은 당신이 필요한 주조 및 호출을 할 것입니다 작은 람다를 컴파일 할 표현식을 사용할 수 있습니다 호출하는 동안 반사를 사용하는 추가 비용을 피하기 위해 반사를 사용하여
var inputType = input.GetType();
var factory = factories[inputType];
var output = factory(input);
.
이 기능은 입력 유형과 출력 유형이 병렬 클래스 계층 구조라고 가정하는 기본 클래스를 통해 노출 될 수 있습니다. 예를 들어, 모든 Foo#
클래스는 기본 클래스로 Foo
을 가질 수 있으며 모든 Bar#
클래스는 기본 닫기로 Bar
을 가질 수 있습니다. 그러나 이것이 사실이 아니라면 모든 클래스는 object
을 기본 클래스로 가지므로이 접근법은 여전히 유효합니다.팩토리 메소드는 개인이 얼마나
public class FooFactory : TypeBasedFactory<Bar, Foo>
{
private Foo1 CreateFoo1(Bar1 bar1)
{
return new Foo1(bar1.Id, bar1.Name, ...);
}
private Foo2 CreateFoo2(Bar2 bar2)
{
return new Foo2(bar2.Description, ...);
}
}
주의 사항 :
파생 팩토리 클래스는 다음과 같이 보일 것입니다. 직접 호출 할 수있는 것은 아닙니다.
public abstract class TypeBasedFactory<TInput, TOutput>
where TInput : class where TOutput : class
{
private readonly Dictionary<Type, Func<TInput, TOutput>> factories;
protected TypeBasedFactory()
{
factories = CreateFactories();
}
private Dictionary<Type, Func<TInput, TOutput>> CreateFactories()
{
return GetType()
.GetMethods(
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance)
.Where(methodInfo =>
!methodInfo.IsAbstract
&& methodInfo.GetParameters().Length == 1
&& typeof(TOutput).IsAssignableFrom(methodInfo.ReturnType))
.Select(methodInfo => new
{
MethodInfo = methodInfo,
methodInfo.GetParameters().First().ParameterType
})
.Where(factory =>
typeof(TInput).IsAssignableFrom(factory.ParameterType)
&& !factory.ParameterType.IsAbstract)
.ToDictionary(
factory => factory.ParameterType,
factory => CreateFactory(factory.MethodInfo, factory.ParameterType));
}
private Func<TInput, TOutput> CreateFactory(MethodInfo methodInfo, Type parameterType)
{
// Create this Func<TInput, TOutput>: (TInput input) => Method((Parameter) input)
var inputExpression = Expression.Parameter(typeof(TInput), "input");
var castExpression = Expression.Convert(inputExpression, parameterType);
var callExpression = Expression.Call(Expression.Constant(this), methodInfo, castExpression);
var lambdaExpression = Expression.Lambda<Func<TInput, TOutput>>(callExpression, inputExpression);
return lambdaExpression.Compile();
}
public TOutput CreateFrom(TInput input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
var inputType = input.GetType();
Func<TInput, TOutput> factory;
if (!factories.TryGetValue(inputType, out factory))
throw new InvalidOperationException($"No factory method defined for {inputType.FullName}.");
return factory(input);
}
}
CreateFactories
방법은 수있는 공공 및 민간 방법을 모두 찾기 위해 반사를 사용하여 여기에
var fooFactory = new TypeBasedFactory<Bar, Foo>();
var foo = fooFactory.CreateFrom(bar);
이
TypeBasedFactory
코드입니다 : 대신
TypeBasedFactory
올바른 공장을 호출하는
CreateFrom
방법을 선언한다
TInput
(비 추상적 추상 클래스)에서
TOuput
(파생 클래스 일 수 있음)을 만듭니다.
CreateFactory
메서드는 팩터 리 메서드를 호출하기 전에 필요한 다운 캐스팅을 수행하는 Func<TInput, TOutput>
을 만듭니다. 람다 (lambda)가 컴파일 된 후에는 그것을 호출하는데 반영의 오버 헤드가 없습니다.
TypeBasedFactory
에서 파생 된 클래스를 생성하면 리플렉션을 사용하여 팩토리 사전을 작성하므로 두 개 이상의 인스턴스 (즉, 팩토리가 싱글 톤이어야 함)를 작성하지 않아야합니다.
비슷한 것을 제안했지만 반성은하지 않았습니다. 사전의 값은 원시 형식 대신 대리자가 될 수 있습니다. –
더 명확하게하기 위해 코드 샘플을 추가했습니다. 벤치마킹을하지는 않았지만 위임자 접근 방식이 'Activator.CreateInstance'보다 빠를 것이라고 확신합니다. –
방금이 접근법을 시도하고 매우 좋아합니다. 그것은 매우 깨끗하고 쉽게 (사소한) 유지 관리됩니다.이 특정 공장의 최고 성능은 필요도 아니고 요구 사항도 아니며 그렇지 않은 경우 처음에는 @Steven의 응답과 함께 갈 것입니다. – Swim