2010-06-02 5 views
3

다음 스 니펫과 비슷한 팩토리가 있습니다. Foo는 Bar의 래퍼 클래스이며 대부분의 경우 (전부는 아님) 1 : 1 매핑이 있습니다. 일반적으로 Bar는 Foo에 대해 아무 것도 알 수 없지만 Foo는 Bar의 인스턴스를 사용합니다. 이 작업을 수행하는 더 나은/깨끗한 접근 방식이 있습니까?팩토리 메서드 Is/As 연산자 사용

public Foo Make(Bar obj) 
{ 
    if(obj is Bar1) 
     return new Foo1(obj as Bar1); 
    if(obj is Bar2) 
     return new Foo2(obj as Bar2); 
    if(obj is Bar3) 
     return new Foo3(obj as Bar3); 
    if(obj is Bar4) 
     return new Foo3(obj as Bar4); // same wrapper as Bar3 
    throw new ArgumentException(); 
} 

언뜻보기에이 질문은 중복 된 것처럼 보일 수 있지만 그와 비슷한 것을 보지 못했습니다. 다음은 아니지만 꽤 가까운 하나입니다

Factory based on Typeof or is a

답변

1

:

는 여기에 몇 가지 예제 코드입니다. 아마도 좀 더 일반적인 것으로 만들려고 노력할 것입니다.

푸가 지원하는 바에서 속성을 사용할 수 있습니다. 그런 다음 초기화 단계에서 목록을 만듭니다. 우리는 이렇게 많은 것을하고 있습니다. 새로운 클래스를 쉽게 추가하고 연결할 수 있습니다.

private Dictionary<Type, Type> fooOfBar = new Dictionary<Type, Type>(); 
public initialize() 
{ 
    // you could scan all types in the assembly of a certain base class 
    // (fooType) and read the attribute 

    fooOfBar.Add(attribute.BarType, fooType); 
} 

public Foo Make(Bar obj) 
{ 
    return (Foo)Activator.CreateInstance(fooOfBar(obj.GetType()), obj); 
} 
+0

비슷한 것을 제안했지만 반성은하지 않았습니다. 사전의 값은 원시 형식 대신 대리자가 될 수 있습니다. –

+0

더 명확하게하기 위해 코드 샘플을 추가했습니다. 벤치마킹을하지는 않았지만 위임자 접근 방식이 'Activator.CreateInstance'보다 빠를 것이라고 확신합니다. –

+0

방금이 접근법을 시도하고 매우 좋아합니다. 그것은 매우 깨끗하고 쉽게 (사소한) 유지 관리됩니다.이 특정 공장의 최고 성능은 필요도 아니고 요구 사항도 아니며 그렇지 않은 경우 처음에는 @Steven의 응답과 함께 갈 것입니다. – Swim

4

이 다음 isas 불필요한 비용입니다 호출, 참조 형식 인 경우. 일반적인 숙어는 as으로 캐스팅하고 null을 확인하는 것입니다.

마이크로 최적화에서 한 걸음 뒤로 물러나면 연결된 기사에서 기술 중 일부를 사용할 수있는 것처럼 보입니다. 구체적으로, 유형을 키잉 한 사전을 만들 수 있습니다. 값은 인스턴스를 구성하는 위임자입니다. 대표는 Bar의 (자식)을 취하여 Foo의 자식을 반환합니다. 이상적으로, Foo의 각 하위는 Foo 자체 내에서 정적 일 수있는 사전에 자신을 등록합니다. 나는 당신이 실제로 무엇을 달성하고자하는 확실하지 않다

// Foo creator delegate. 
public delegate Foo CreateFoo(Bar bar); 

// Lookup of creators, for each type of Bar. 
public static Dictionary<Type, CreateFoo> Factory = new Dictionary<Type, CreateFoo>(); 

// Registration. 
Factory.Add(typeof(Bar1), (b => new Foo1(b))); 

// Factory method. 
static Foo Create(Bar bar) 
{ 
    CreateFoo cf; 
    if (!Factory.TryGetValue(bar.GetType(), out cf)) 
     return null; 

    return cf(bar); 
} 
+0

오른쪽. 나는 "is"가 "as"로 싸여 있지만 긴 목록을 가지고 있음을 알고 있습니다 : Bar1 bar1 = obj as Bar1; if (bar1! = null) return 새 Foo1 (bar1); 은 나에게 대단히 못 생겼습니다. ;-) – Swim

+0

당신이하고있는 것보다 더 좋은 방법을 알지 못하기 때문에 이것을 계속 지켜 볼 것입니다. 그리고이 문제를 해결할 수있는 방법입니다 ... – jcolebrand

+0

수영을하고, 유연성이 필요 없다면 간단하지만 빠르지 만 추악한 방법은 그렇게 끔찍하지 않습니다. 유연성을 위해 사전 기술을 고려하십시오. –

1

질문에서 한 세트의 클래스 세트에서 다른 세트 세트로의 매핑은 매우 간단 해 보입니다. 그러나 종종 입력 클래스를 기반으로 출력 클래스의 특정 생성자 및/또는 속성을 호출하려고합니다. 때로는 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에서 파생 된 클래스를 생성하면 리플렉션을 사용하여 팩토리 사전을 작성하므로 두 개 이상의 인스턴스 (즉, 팩토리가 싱글 톤이어야 함)를 작성하지 않아야합니다.