Можно ли в 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 в прокси-классе, который принимает экземпляры классов для всех интерфейсов в конструкторе.
Проверьте Castle DynamicProxy, который использует IL Emit для создания прокси-объекта на лету. Вы можете создать прокси, который реализует два интерфейса, а затем делегирует вызовы двум инкапсулированным экземплярам. Если вам интересно, на самом деле это не так уж сложно реализовать самостоятельно, хотя есть несколько ключевых случаев, которые нужно учитывать, и классы IL Emit не особенно прощают ошибок (что делает их трудными для изучения).
Ответ Хасана (IDynamicMetaObjectProvider) - хорошая ставка, если вы используете.NET 4.0.
Вы также можете взглянуть на RealProxy / DynamicProxy, который существует с.NET 1.0. Я думаю, что именно так библиотеки, подобные Moq, подделывают отдельные интерфейсы одновременно, и я думаю, что они также позволяют вам перехватывать приведения, что должно позволить вам выполнить то, что вы ищете. Вот статья о TransparentProxy и документация MSDN для RealProxy и RealProxy.GetTransparentProxy.