Можно ли в C# создать фабрику, которая объединяет интерфейсы?

var mergedInstance = MergeFactory<InterfaceOne, InterfaceTwo>();

((InterfaceOne)mergedInstance).InterfaceOneMethod();
((InterfaceTwo)mergedInstance).InterfaceTwoMethod();

Кто-нибудь может порекомендовать шаблон дизайна или точный синтаксис, который бы сделал что-то подобное?

Внутри MergeFactory я представляю что-то вроде этого:

MergeFactory<Iface1, Iface2>() :
    where Iface1: IMergeable, Iface2: IMergeable
{
    IMergeable instance = Iface1Factory.CreateIface1Instance();
    instance.Merge(Iface2Factory.CreateIface2Instance());
}

5 ответов

Решение

Какой бы бессмысленной ни была эта конструкция, по какой-то причине она заинтриговала меня, и я быстро смоделировал реализацию Castle DynamicProxy для создания объектов, которые объединяют несколько интерфейсов.

Фабрика mixin предоставляет два метода:

object CreateMixin(params object[] objects)

Возвращает объект, который реализует любое количество интерфейсов. Чтобы добраться до реализованного интерфейса, вы должны привести возвращенный объект к этому интерфейсу.

TMixin CreateMixin<TMixin, T1, T2>(T1 obj1, T2 obj2)

Возвращает интерфейс, который реализует два других интерфейса для строгой типизации. Этот объединяющий интерфейс должен существовать во время компиляции.

Вот объекты:

public interface ICat {
    void Meow();
    int Age { get; set; }
}

public interface IDog {
    void Bark();
    int Weight { get; set; }
}

public interface IMouse {
    void Squeek();
}

public interface ICatDog : ICat, IDog {
}

public interface ICatDogMouse : ICat, IDog, IMouse {
}

public class Mouse : IMouse {

    #region IMouse Members

    public void Squeek() {
        Console.WriteLine("Squeek squeek");
    }

    #endregion
}

public class Cat : ICat {
    #region ICat Members

    public void Meow() {
        Console.WriteLine("Meow");
    }

    public int Age {
        get;
        set;
    }

    #endregion
}

public class Dog : IDog {
    #region IDog Members

    public void Bark() {
        Console.WriteLine("Woof");          
    }

    public int Weight {
        get;
        set;
    }

    #endregion
}

Сделать заметку о ICatDog Я подумал, что было бы неплохо, если бы динамический прокси-сервер возвращал что-то строго типизированное и могло бы использоваться там, где принят любой интерфейс. Этот интерфейс должен быть определен во время компиляции, если действительно желательна строгая типизация. Теперь для завода:

using Castle.DynamicProxy;

public class MixinFactory {
    /// <summary>
    /// Creates a mixin by comining all the interfaces implemented by objects array.
    /// </summary>
    /// <param name="objects">The objects to combine into one instance.</param>
    /// <returns></returns>
    public static object CreateMixin(params object[] objects) {

        ProxyGenerator generator = new ProxyGenerator();
        ProxyGenerationOptions options = new ProxyGenerationOptions();

        objects.ToList().ForEach(obj => options.AddMixinInstance(obj));

        return generator.CreateClassProxy<object>(options);
    }


    /// <summary>
    /// Creates a dynamic proxy of type TMixin. Members that called through this interface will be delegated to the first matched instance from the objects array
    /// It is up to the caller to make sure that objects parameter contains instances of all interfaces that TMixin implements
    /// </summary>
    /// <typeparam name="TMixin">The type of the mixin to return.</typeparam>
    /// <param name="objects">The objects that will be mixed in.</param>
    /// <returns>An instance of TMixin.</returns>
    public static TMixin CreateMixin<TMixin>(params object[] objects)
    where TMixin : class {
        if(objects.Any(o => o == null))
            throw new ArgumentNullException("All mixins should be non-null");

        ProxyGenerator generator = new ProxyGenerator();
        ProxyGenerationOptions options = new ProxyGenerationOptions();
        options.Selector = new MixinSelector();

        return generator.CreateInterfaceProxyWithoutTarget<TMixin>(options, objects.Select(o => new MixinInterceptor(o)).ToArray());
    }
}

public class MixinInterceptor : IInterceptor {
    private object m_Instance;

    public MixinInterceptor(object obj1) {
        this.m_Instance = obj1;
    }

    public Type ObjectType {
        get {
            return m_Instance.GetType();
        }
    }

    #region IInterceptor Members

    public void Intercept(IInvocation invocation) {
        invocation.ReturnValue = invocation.Method.Invoke(m_Instance, invocation.Arguments);
    }


    #endregion
}
public class MixinSelector : IInterceptorSelector{
    #region IInterceptorSelector Members

    public IInterceptor[] SelectInterceptors(Type type, System.Reflection.MethodInfo method, IInterceptor[] interceptors) {
        var matched = interceptors
            .OfType<MixinInterceptor>()
            .Where(mi => method.DeclaringType.IsAssignableFrom(mi.ObjectType))
            .ToArray();
        if(matched.Length == 0)
            throw new InvalidOperationException("Cannot match method " + method.Name + "on type " + method.DeclaringType.FullName + ". No interceptor for this type is defined");
        return matched;
    }

    #endregion
}

Использование лучше всего объясняется в этих модульных тестах. Как видите, второй метод возвращает безопасный интерфейс типа, который, по-видимому, объединяет любое количество интерфейсов.

    [TestMethod]
    public void CreatedMixinShouldntThrow() {
        ICat obj1 = new Cat();
        IDog obj2 = new Dog();

        var actual = MixinFactory.CreateMixin(obj1, obj2);
        ((IDog)actual).Bark();
        var weight = ((IDog)actual).Weight;
        ((ICat)actual).Meow();
        var age = ((ICat)actual).Age;
    }

    [TestMethod]
    public void CreatedGeneric3MixinShouldntThrow() {
        ICat obj1 = new Cat();
        IDog obj2 = new Dog();
        IMouse obj3 = new Mouse();
        var actual = MixinFactory.CreateMixin<ICatDogMouse>(obj1, obj2, obj3);

        actual.Bark();
        var weight = actual.Weight;
        actual.Meow();
        var age = actual.Age;
        actual.Squeek();
    }

Я написал об этом более подробно и предоставил источник и тесты. Вы можете найти это здесь.

Похоже, работа для шаблона адаптера

   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();

         // Create adapter and place a request
         MergeFactoryTarget target = new Adapter<AdapteeA, AdapteeB>();
         target.InterfaceACall();
         target.InterfaceBCall();
      }
   }

   /// <summary>
   /// The 'Target' class
   /// </summary>
   public class MergeFactoryTarget
   {
      public virtual void InterfaceACall()
      {
         Console.WriteLine("Called Interface A Function()");
      }

      public virtual void InterfaceBCall()
      {
         Console.WriteLine("Called Interface B Function()");
      }
   }

   /// <summary>
   /// The 'Adapter' class
   /// </summary>
   class Adapter<AdapteeType1, AdapteeType2> : MergeFactoryTarget
      where AdapteeType1 : IAdapteeA
      where AdapteeType2 : IAdapteeB
   {
      private AdapteeType1 _adapteeA = Activator.CreateInstance<AdapteeType1>();
      private AdapteeType2 _adapteeB = Activator.CreateInstance<AdapteeType2>();

      public override void InterfaceACall()
      {
         _adapteeA.InterfaceOneMethod();
      }

      public override void InterfaceBCall()
      {
         _adapteeB.InterfaceTwoMethod();
      }
   }

   /// <summary>
   /// AdapteeA Interface
   /// </summary>
   interface IAdapteeA
   {
      void InterfaceOneMethod();
   }

   /// <summary>
   /// AdapteeB Interface
   /// </summary>
   interface IAdapteeB
   {
      void InterfaceTwoMethod();
   }

   /// <summary>
   /// The 'AdapteeA' class
   /// </summary>
   class AdapteeA : IAdapteeA
   {
      public void InterfaceOneMethod()
      {
         Console.WriteLine("Called InterfaceOneMethod()");
      }
   }

   /// <summary>
   /// The 'AdapteeB' class
   /// </summary>
   class AdapteeB : IAdapteeB
   {
      public void InterfaceTwoMethod()
      {
         Console.WriteLine("Called InterfaceTwoMethod()");
      }
   }

Если вы используете.NET 4.0, реализуйте IDynamicMetaObjectProvider в прокси-классе, который принимает экземпляры классов для всех интерфейсов в конструкторе.

http://msdn.microsoft.com/en-us/vcsharp/ff800651.aspx

Проверьте Castle DynamicProxy, который использует IL Emit для создания прокси-объекта на лету. Вы можете создать прокси, который реализует два интерфейса, а затем делегирует вызовы двум инкапсулированным экземплярам. Если вам интересно, на самом деле это не так уж сложно реализовать самостоятельно, хотя есть несколько ключевых случаев, которые нужно учитывать, и классы IL Emit не особенно прощают ошибок (что делает их трудными для изучения).

Ответ Хасана (IDynamicMetaObjectProvider) - хорошая ставка, если вы используете.NET 4.0.

Вы также можете взглянуть на RealProxy / DynamicProxy, который существует с.NET 1.0. Я думаю, что именно так библиотеки, подобные Moq, подделывают отдельные интерфейсы одновременно, и я думаю, что они также позволяют вам перехватывать приведения, что должно позволить вам выполнить то, что вы ищете. Вот статья о TransparentProxy и документация MSDN для RealProxy и RealProxy.GetTransparentProxy.

Другие вопросы по тегам