在C#中,是否可能创建一个合并接口的工厂?

7
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());
}

一个实现了两个接口的类? - benPearce
嗯,也许我应该更详细地阐述一下我想在MergeFactory内发生的事情。 - djmc
2
提供更多关于您动机的信息可能会有所帮助。我无法理解为什么我想要让两个不同对象的实例响应于两个不同接口,就好像它们是实现了两个不同接口的同一个对象一样。 - JasonTrue
嗯,我猜这只是因为不同的编码风格。我正在讨论它是否可能,而我自己也不知道,所以我决定在stackoverflow上提出这个问题。正如你所说,你可能不想这样做,但这是可能的吗? - djmc
5个回答

6
听起来需要使用适配器模式
   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()");
      }
   }

你还应该让 Adapter<AdapteeType1, AdapteeType2> 实现两个接口,这样适配器的实例就可以在任何一个接口被接受的地方使用。 - Igor Zevaka
1
太棒了,你们真是太厉害了。感谢你们澄清我需要使用的设计模式。 - djmc

5

虽然这个构造可能看似毫无意义,但出于某种原因,它引起了我的兴趣,我很快为创建捆绑多个接口的对象编写了一个Castle DynamicProxy实现。

混合工厂提供了两种方法:

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();
    }

我已经详细地写了关于这个问题的博客,并提供了源码和测试。您可以在这里找到它。


哈哈哈,这就是纯混合魔法。这正是我所追求的...虽然这可能不是C#中传统的编码风格,但你展示了语言的灵活性,真是太棒了...莫罗博士会感到自豪的。 - djmc

4

太好了,我在这个项目中使用4.0版本,所以这看起来是一个很好的线索。非常感谢你。 - djmc

3

看看Castle DynamicProxy,它利用IL Emit实时创建代理对象。您可以发出一个实现两个接口的代理,然后将调用委托给两个封装的实例。如果您有兴趣,自己实现这一点实际上并不太难,尽管有一些边缘情况需要考虑,而IL Emit类对错误不太宽容(使它们成为学习的挑战)。


谢谢,我和另一个朋友聊过,他也推荐这个。 - djmc

2

Hasan的回答(IDynamicMetaObjectProvider)是如果您使用.NET 4.0,则是一个不错的选择。

您还可以查看RealProxy / DynamicProxy,它们自.NET 1.0以来就存在。我认为这些是像Moq这样的库一次性伪造单个接口的方法,并且我认为它们还允许您拦截强制转换,这应该能够实现您想要的功能。这里有关于TransparentProxy的文章,这里是RealProxyRealProxy.GetTransparentProxy的MSDN文档


有趣...我有点认为4.0使C#足够灵活,可以做到这一点,但我不知道自.NET 1.0以来已经有了解决方案..谢谢 - djmc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接