事件 + 适配器模式

3

我有一个适配器模式应用在一个泛型类上,它实质上是在类型之间进行适配:

class A<T> { event EventHandler e; }
class Aadapter<T1, T2> : A<T1> { A<T2> a; Aadapter(A<T2> _a) { a = _a; }  }

问题在于A包含一个事件。我希望所有分配给Adapter的事件处理程序都能够传递到A。
如果我可以将a的事件处理程序分配给Adapter的事件处理程序,那就太好了,但这是不可能的?
这里的想法是,A实际上几乎只是A,但我们需要一种适应它们的方法。由于事件的工作方式,我无法有效地做到这一点,除非手动添加两个事件处理程序,并且当它们被调用时,它们会将其“中继”到另一个事件中。虽然这不太美观,但如果我有像下面这样的东西,似乎会更好:
class A<T> { event EventHandler e; }
class Aadapter<T1, T2> : A<T1> { event *e; A<T2> a; Aadapter(A<T2> _a) { a = _a; e = a.e; }  }

从某种意义上说,我们有一个指向事件的指针,可以将a2的事件分配给它。

我怀疑是否有任何简单的方法,但也许有人有一些想法来使其工作。

(顺便说一句,我意识到使用虚拟事件是可能的,但如果可能的话,我想避免这种情况)

3个回答

2
我想这是您想要的内容:
    class A<T>
    {
        public virtual event EventHandler e;
    }

    class Aadapter<T1, T2> : A<T1> 
    { 
        A<T2> a; 
        Aadapter(A<T2> _a) { a = _a; } 
        public override event EventHandler  e
        {
            add { a.e += value; }
            remove { a.e -= value; }
        }
    }

或者将其链接起来。
class A<T>
{
    public event EventHandler e;

    protected void ChainEvent(object sender, EventArgs eventArgs)
    {
        e(sender, eventArgs);
    }
}
class Aadapter<T1, T2> : A<T1> 
{ 
    A<T2> a; 
    Aadapter(A<T2> _a)
    {
        a = _a;
        a.e += ChainEvent;
    }
}

正如我所说,这是一种方法,但我宁愿不采用。 (除了额外的开销之外,C#中的虚拟事件也存在已知问题) - Stretto
@Stretto 一个虚拟事件所涉及的开销是多少? - Jay
你也可以直接链式调用它 - 这看起来很不好,很不好。将其设置为虚拟的不应该有太大的开销差异 - class A<T> { public event EventHandler e; protected void ChainEvent(object sender, EventArgs eventArgs) { e(sender, eventArgs); } } class Aadapter : A { A a; Aadapter(A _a) { a = _a; a.e += ChainEvent; } } - Neil
这并不是那么简单,但基本上那是其中一种方法。你必须从“两个方面”(适配器的基础和包装对象)进行订阅。我已经发布了两种情况的示例代码,所以毫无疑问有两种可行的方法。 - Stretto
说实话,我会选择使用虚拟事件路线,并等待出现性能问题。如果这确实需要大量资源,请在那时对应用程序进行分析并修复问题,但是这种预优化往往比节省硬件成本更耗费开发人员的时间。 - Neil

0

示例展示了解决问题的“标准”方法。第一种方法使用虚拟事件/方法,而第二种方法则采用“双向”转发方案。两者都有其优缺点,但如果有一种不随事件数量增加而变得更容易的方法,那就太好了。我们想要做的是将这两个事件直接合并成一个,而不是间接地合并,这就是所有这些代码所做的事情。(如果在C#中可能的话,指针将是这样一种方法)

//#define __virtual
#define __direct

using System;
using System.Collections.Generic;
using System.Text;



namespace VirtualEvents
{



    #if __virtual
    #region
    public class A<T> 
    { 
        public virtual event EventHandler e;

        public virtual void Fire() { e(this, null); }
    }

    public class Aadapter<T1, T2> : A<T1> 
    { 
        A<T2> a;


        public override event EventHandler e
        {
            add { a.e += new EventHandler(value); }
            remove { a.e -= new EventHandler(value); }
        }

        public override void Fire()
        {
            a.Fire();
        }

        public Aadapter(A<T2> _a) 
        { 
            a = _a; 
        } 
    }
    #endregion
    #elif __direct
    #region

    public delegate EventHandler EventHandlerPtr();
        public class eventPtr
        {
            public EventHandler _event;
        }
    public class A<T>
    {


        //internal EventHandler _event;
        public eventPtr _event = new eventPtr();

        public event EventHandler e
        {
            add { _event._event += value; }
            remove { _event._event -= value; }
        }

        public void Fire() { _event._event(this, null); }


    }

    public class Aadapter<T1, T2> : A<T1>
    {
        A<T2> a;

        public Aadapter(A<T2> _a)
        {
            a = _a;
            this._event = a._event;
        }
    }

    #endregion
    #else
    #region 
    public class A<T>
    {
        public event EventHandler e;

        public void Fire() { e(this, null); }
    }

    public class Aadapter<T1, T2> : A<T1>
    {
        A<T2> a;

        public Aadapter(A<T2> _a)
        {
            a = _a;

            a.e += new EventHandler(a_e);
            e += new EventHandler(Aadapter_e);
        }

        void Aadapter_e(object sender, EventArgs e)
        {
            a.e -= new EventHandler(a_e);
            a.Fire();
            a.e += new EventHandler(a_e);
        }

        void a_e(object sender, EventArgs e)
        {       
            this.e -= new EventHandler(Aadapter_e);
            Fire(); 
            this.e += new EventHandler(Aadapter_e);
        }
    }
    #endregion
    #endif


    class Program
    {
        static void Main(string[] args)
        {

            var a = new A<double>();
            var q = new Aadapter<int, double>(a);

            a.e += new EventHandler(a_e);
            q.e += new EventHandler(q_e);


            a.Fire();
            q.Fire();
            ((A<int>)q).Fire();

            Console.ReadKey();
        }

        static void a_e(object sender, EventArgs e)
        {
            Console.WriteLine("From a");
        }

        static void q_e(object sender, EventArgs e)
        {
            Console.WriteLine("From q");
        }

    }
}

(编辑:现在的代码包括一个新方法,它将事件封装在一个类中,现在可以轻松地分配事件并有效地表示“指针”情况。希望有人能进一步改进这些内容。)


顺便说一句,一个可行的方法是自己编写事件结构。例如,使用列表/哈希表和回调函数。在这种情况下,可以轻松地将包装对象的回调列表分配给适配器类中的引用。我想仍然可以使用事件语义来实现这一点...我得尝试一下,因为这可能是一个不错的解决方案。 - Stretto

0

为什么订阅和转发事件不够优雅?我认为它很优美。

这样做与适配器的其余部分实现方式一致。

即使你可以使用指针,但这是不一致的,因为你不想在每种情况下都这样做。

例如,如果你正在将实现INotifyPropertyChanged的类适配到一个不实现该接口但公开了一些属性(如“TitleChanged”和“MaxLengthChanged”)的接口上,那么你就不会使用指针。你的适配器将公开这两个事件,消费者将订阅。你的适配器将订阅PropertyChanged事件,并仅在收到“Title”被修改的通知时才引发“TitleChanged”,并仅在收到“MaxLength”被修改的通知时才引发“MaxLengthChanged”。所有其他通知都将被忽略。

我支持这种方法,因为我觉得它简单明了、一致性好,符合模式。


在这种情况下,适配器仅将通用类适配到自身。该类的泛型类型参数在某种程度上是“松散”绑定的,因为它没有被广泛使用。这意味着该类几乎独立于泛型参数,并且几乎不需要适配器。因此,在这种情况下,将适配器的事件与包装对象绑定比同时订阅更自然。显然,“指针方法”行不通,除非有人提出更好的方法,否则我只能从两个可行的方法中选择一个。 - Stretto

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