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());
}
Какой бы бессмысленной ни была эта конструкция, по какой-то причине она меня заинтриговала, и я быстро смоделировал реализацию Castle DynamicProxy для создания объектов, объединяющих несколько интерфейсов.
Фабрика миксинов предоставляет два метода:
объект CreateMixin (params object [] objects)
Возвращает объект, реализующий любое количество интерфейсов. Чтобы добраться до реализованного интерфейса, вы должны привести возвращенный объект к этому интерфейсу.
TMixin CreateMixin (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();
}
Я написал об этом более подробно, предоставил исходный код и тесты. Вы можете найти его здесь .
Похоже на работу для Adapter Pattern
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 на прокси-классе, который принимает экземпляры классов для всех интерфейсов в конструкторе
Проверить Замок DynamicProxy , который использует IL Emit для создания прокси-объекта на лету. Вы можете создать прокси-сервер, который реализует два интерфейса, а затем делегирует вызовы двум инкапсулированным экземплярам. Если вам интересно, на самом деле это не так уж и сложно реализовать самостоятельно, хотя есть некоторые критические случаи, которые следует учитывать, а классы IL Emit не особенно прощают ошибок (что затрудняет их изучение).
Ответ Хасана (IDynamicMetaObjectProvider) - хороший вариант, если вы используете .NET 4.0.
Вы также можете изучить RealProxy / DynamicProxy, которые существуют с .NET 1.0. Я думаю, что именно так библиотеки, подобные Moq , подделывают отдельные интерфейсы за раз, и я думаю, что они также позволяют вам перехватывать приведение типов, что должно позволить вам выполнить то, что вы ищете. Вот статья о TransparentProxy , а вот документация MSDN для RealProxy и RealProxy.GetTransparentProxy .