2009-12-16 90 views
9

我目前正在编写一个C#库以简化实现小物理模拟/实验。C#中的虚拟方法或事件#

主要组件是SimulationForm,它在内部运行定时器循环并隐藏用户的样板代码。

  1. Init()(初始化一切)
  2. Render(Graphics g)(渲染当前的仿真状态)
  3. Move(double dt)(移动实验dt秒)

我:实验本身只是通过三种方法来定义只是想知道让用户实现这些功能有什么更好的选择:

1)虚拟方法,由继承的形式覆盖

protected virtual void Init() {} 
... 

2)活动

public event EventHandler<MoveSimulationEventArgs> Move = ... 
... 

编辑:注意方法应该是不抽象反正。实际上,还有更多,其中没有一个已经实现了。因为许多模拟不需要它们,所以将它们排除在外通常很方便。

很酷的事情有关这是一个“范式”是,你可以写

partial class frmMyExperiment : SimulationForm { 
} 

,你就完全能够与设计师和所有继承的控制和设置/特性交互。我不想通过完全不同的方法失去这个功能。

回答

17

我更喜欢在这种情况下使用虚拟方法。

此外,如果方法是要求为了功能,您可以使您的类为abstract class。这在可用性方面是最好的,因为它会导致编译器强制使用。如果用户试图在不实现方法的情况下使用你的类/表单,编译器会发出抱怨。

事件在这里有点不合适,因为这实际上不是你想要的不仅仅是单个实现(事件允许多个订阅者),而且它在概念上更多地是对象的功能,而且更少由对象引起的通知。

+2

对所需方法的“抽象”+1。 – 2009-12-16 20:29:58

+1

IMO,摘要FTW。 – Yoopergeek 2009-12-16 20:30:32

+0

摘要不适合,但“功能但notifcation”是一个好点+1 – Dario 2009-12-16 20:44:30

3

还有另一种选择,从函数式编程中借用:将扩展点公开为类型为Func或Action的属性,并让您的实验因此提供委托。

例如,在你的模拟亚军,你必须:

public class SimulationForm 
{ 
    public Action Init { get; set; } 
    public Action<Graphics> Render { get; set; } 
    public Action<double> Move { get; set; } 

    //... 
} 

而且在你的模拟你必须:

public class Simulation1 
{ 
    private SimulationForm form; 

    public void Simulation1() 
    { 
     form = new SimulationForm(); 
     form.Init = Init; 
     form.Render = Render; 
     form.Move = Move; 
    } 

    private void Init() 
    { 
     // Do Init code here 
    } 

    private void Render(Graphics g) 
    { 
     // Do Rendering code here 
    } 

    private void Move(double dt) 
    { 
     // Do Move code here 
    } 
} 

编辑:对于一件很无聊的例子正是我在一些面向私人的代码中使用的这种技术(用于私人游戏项目),请查看my delegate-powered-collection blog post

+0

+1为一个新颖的想法。我不确定这是否是“最佳”方法。 – ScottS 2009-12-16 20:48:04

+0

我不知道这是否是最好的方法 - 我只是想指出另一种选择。 – 2009-12-16 20:59:22

+1

@Erik:将我的评论与“过去我对某些特定问题使用此策略”进行了评论:请记住,这不是人们想要扩展程序的最常见方式之一,这可能会导致一些混淆打算使用API​​。衡量这种优势与其他开发人员不得不花时间去了解它的可能性相重要。 :) – 2009-12-17 06:29:34

5

的决定一点帮助:

是否要通知其他(多个)对象?然后使用事件。

你想让不同的实现可能/使用多态吗?然后使用虚拟/抽象方法。

在某些情况下,即使组合两种方式也是一个很好的解决方案。

13

有趣的是,我不得不在最近的一个设计中做出类似的选择。一种方法并不严格优于另一种方法。

在你的例子中,如果没有额外的信息,我可能会选择虚拟方法 - 特别是因为我的直觉让我相信你会用继承来模拟不同类型的实验,而且你不需要具有多重订户(如事件允许)。

下面是我的一些一般性意见有关这些模式之间进行选择:

活动是巨大的:

  1. 当你不需要调用者返回任何信息,而当你希望可扩展性而不需要子类化。
  2. 如果您希望允许调用者拥有多个可以侦听和响应该事件的订户。
  3. 因为它们自然排列成template method模式 - 这有助于避免引入fragile base class problem

事件的最大问题是管理订阅者的生命期可能会变得棘手,当订阅者订阅的时间超过必要时,您可能会引入泄漏甚至功能缺陷。第二个最大的问题是,允许多个订阅者可以创建一个令人困惑的实现,其中个别订阅者互相访问 - 或展示订单依赖关系。

虚拟方法工作得很好:

  1. 当你只想继承人能够改变类的行为。
  2. 当你需要从调用返回的信息(这事件的不轻松支持)
  3. 时,你只需要一个用户到特定的扩展点
  4. 当你想衍生品的衍生品能够覆盖行为。

虚拟方法的最大问题是你可以很容易地将脆弱的基类问题引入到你的实现中。虚拟方法本质上是与派生类的契约,您必须清楚地记录文档,以便继承者可以提供有意义的实现。

虚拟方法的第二大问题是,它可以引入深层或广泛的继承树来定制在特定情况下如何定制类的行为。这可能是好的,但我通常会尝试避免继承,如果在问题域中没有明确的关系is-a

还有另一种解决方案,您可以考虑使用:strategy pattern你的班级是否支持分配一个对象(或委托)来定义Render,Init,Move等的行为方式。您的基类可以提供默认实现,但允许外部消费者改变行为。虽然与事件类似,但优点是您可以将值返回给调用代码,并且只能强制执行一个用户。

2

都没有。

你应该使用一个实现了一个接口的类来定义你需要的函数。

1

为什么界面不是这个的选择?

0

使用虚拟方法来允许基类的特殊化 - 内部细节。

从概念上讲,除了返回方法之外,您可以使用事件来表示类的“输出”。

0

我喜欢LBuskin的总结。至于脆弱的基类问题,有一种情况我想保护事件可能是最佳做法的有力竞争者:

  1. 你必须真正用于内部类的使用(而不是策略也许事件就像OP的Render一样),例如Initializing或EnabledChanging,
  2. 您有可能产生大的继承层次结构(深层和/或广泛的),并且需要对事件采取多重行动(多个非公开消费者!)
  3. 你没有w蚂蚁依靠每个派生类记住调用base.OnSomethingHappened()。 (避免脆弱的基类问题。)
  4. 为了的处理事件不要紧(以其它方式使用base.OnSomethingHappened()的链来控制秩序。)
  5. 事件适用于一些一种内部状态变化,对外部类别不会有意义(否则使用公共事件)。例如,公共初始化事件通常是发生任何内部初始化之前发生的可取消事件。受保护的Initializing事件不应该作为可取消事件暴露给该类的用户,因为该类可能已经部分初始化。

这避免了需要使用“派生类应该调用基方法”来记录虚拟受保护方法,并希望您或其他派生类的人始终读取文档并遵循它。它还避免了不确定的问题,即在重写的方法开始时还是在中间或结束时调用基方法。

注意:如果你认为一个基类连接到它自己的事件是愚蠢的,没有什么从具有基类的非虚方法阻止你。

在OP的情况下,我想初始化,并可能将事件可能符合上述标准视情况而定。通常情况下,不可能有多于一个Render方法,因此,不需要调用任何基本Render方法的抽象方法(或者虚拟方法,如果此类允许您运行模拟而不绘制任何内容),则不需要调用任何基本Render方法。如果外部组件可能知道如何将该类绘制到图形目标,那么可能使用替代的视觉样式(即2D或3D),Render的策略(公共代理)将会很有用。 Render方法似乎不太可能,但可能适用于未提及的其他功能,尤其是应用算法(趋势线平均方法等)的功能。