当使用多态性没有意义时,如何实现特定于类型的功能?

发布于 2024-10-12 19:04:18 字数 994 浏览 3 评论 0原文

未正确利用 OOP 语言的常见危险信号如下所示:

if (typeof(x) == T1)
{
    DoSomethingWithT1(x);
}
else if (typeof(x) == T2)
{
    DoSomethingWithT2(x);
}

此类设计问题的标准“修复”是使 T1T2 共享一个接口,通过继承基类型或实现公共接口(以支持它的语言);例如,在 C# 中,解决方案可能是:

public interface IT
{
    void DoSomething();
}

但是,有时您希望实现根据对象类型而不同的功能,但该功能属于该对象的类型;因此,多态性似乎是错误的方法。

例如,考虑提供给定数据块视图的 UI 的情况。假设此视图能够根据所呈现的数据类型呈现各种布局和控件,那么如何在没有一堆 if/else 的情况下实现这种特定于类型的呈现声明?

由于我希望显而易见的原因,在这种情况下将渲染逻辑放在类型本身中对我来说是一个非常糟糕的决定。另一方面,如果不将数据对象的类型与其视觉呈现耦合起来,我很难看出如何避免 if/else 场景。

这是一个具体的例子:我正在开发一个交易应用程序,该应用程序对各种市场产品使用许多不同的定价模型。这些不同的模型由继承自公共 PricingModel 基础的类型表示;每种类型都与一组完全不同的参数相关联。当用户想要查看特定定价模型(针对特定产品)的参数时,当前这些参数通过检测模型类型并显示一组适当的控件的表单来显示。我的问题是如何比当前更优雅地实现这一点(使用一个大的 if/else 块)。

我意识到这可能看起来是一个非常基本的问题;这只是我的知识差距之一(坚实的 OOP 原则?设计模式?常识?),我认为是时候修复了。

A common red flag that an OOP language is not being leveraged properly looks like this:

if (typeof(x) == T1)
{
    DoSomethingWithT1(x);
}
else if (typeof(x) == T2)
{
    DoSomethingWithT2(x);
}

The standard "fix" for such design issues is to make T1 and T2 both share an interface, either through inheritance of a base type or implementation of a common interface (in languages that support it); for example, in C# a solution might be:

public interface IT
{
    void DoSomething();
}

However, sometimes you want to implement functionality that differs based on the type of an object but that functionality does not belong within that object's type; thus polymorphism seems the wrong way to go.

For example, consider the case of a UI that provides a view of a given clump of data. Supposing this view is capable of rendering various layouts and controls depending on the type of data being presented, how would you implement this type-specific rendering without a bunch of if/else statements?

For reasons that I hope are obvious, putting the rendering logic in the type itself strikes me as a very bad decision in this case. On the other hand, without coupling the type of data object to its visual presentation I have a hard time seeing how the if/else scenario is avoided.

Here's a concrete example: I work on a trading application which utilizes many different pricing models for various market products. These different models are represented by types inheriting from a common PricingModel base; and each type is associated with a completely different set of parameters. When the user wants to view the parameters for a particular pricing model (for a particular product), currently these are displayed by a form which detects the type of the model and displays an appropriate set of controls. My question is how this could be implemented more elegantly than it is currently (with a big if/else block).

I realize this probably seems like a very basic question; it's just one of those gaps in my knowledge (of solid OOP principles? design patterns? common sense?) that I figured it's about time to fix.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

指尖上得阳光 2024-10-19 19:04:18

我们按类型将此类功能注入(Spring.Net)到字典中。

IDictionary<Type, IBlahImplementor> blahImplementors;

blahImplementors[thingy.GetType()].Do(thingy);

该字典可以由提供该功能的一种存储库来管理。

作为实现细节,实现者通常知道它所依赖的类型,并且可以自己提供它:

interface IBlahImplementor
{
  Type ForType { get; }

  void Do(object thingy);
}

然后将其添加到字典中,如下所示:

IEnumerably<IBlahImplementor> blahImplementors;
foreach (var implementor in blahImplementors)
{
  blahImplementors.Add(implementor.ForType, implementor);
}

备注:恕我直言,了解某些事物不属于类非常重要,即使提供特定于子类型的实现会让生活变得更轻松。


编辑:终于明白了你的具体例子。

它实际上是关于实例化正确的 UI 控件来显示定价模型参数。按照我上面描述的模式应该是可能的。如果您没有用于定价模型的单个 UI 控件,您可以创建它或编写 UI 配置器或类似的东西来设置所需的控件。

interface IPricingModelUiConfigurer
{
  Type PricingModelType { get; }
  void SetupUi(Control parent, IPricingModel model);
}

We are injecting (Spring.Net) such functionality into dictionaries by type.

IDictionary<Type, IBlahImplementor> blahImplementors;

blahImplementors[thingy.GetType()].Do(thingy);

This dictionary could be managed by a kind of repository which provides the functionality.

As an implementation detail, the implementor usually knows the type it depends on an can provide it itself:

interface IBlahImplementor
{
  Type ForType { get; }

  void Do(object thingy);
}

Then it is added to the dictionary like this:

IEnumerably<IBlahImplementor> blahImplementors;
foreach (var implementor in blahImplementors)
{
  blahImplementors.Add(implementor.ForType, implementor);
}

Remark: IMHO, it is very important to understand that some things do NOT belong into a class, even if providing subtype-specific implementations would make life much easier.


Edit: Finally understood your concrete example.

It is actually about instancing the right UI control to show the pricing models parameters. It should be possible with the pattern I described above. If you don't have a single UI control for a pricing model, you either create it or you write a UI configurer or something like this which sets up the required controls.

interface IPricingModelUiConfigurer
{
  Type PricingModelType { get; }
  void SetupUi(Control parent, IPricingModel model);
}
揪着可爱 2024-10-19 19:04:18

您可以使用所描述的通用接口方法和命令模式来触发“功能不属于该对象类型”的方法。我认为这不会违反坚实的 OOP 原则。

you can use common interface approach as you describe and Command pattern to trigger methods with "functionality does not belong within that object's type". I think this won't break solid OOP principles.

栩栩如生 2024-10-19 19:04:18

您所描述的几乎正是访问者模式的用例。

编辑:对于您的具体示例,您可以像这样应用访问者模式:

// interface used to add external functionality to pricing models
public interface PricingModelVisitor {
    void visitPricingModel1(PricingModel1 m);
    void visitPricingModel2(PricingModel2 m);
    ...
}
// your existing base-class, with added abstract accept() method to accept a visitor
public abstract class PricingModelBase {
    public abstract void accept(PricingModelVisitor v);
    ...
}
// concrete implementations of the PricingModelBase implement accept() by calling the 
// appropriate method on the visitor, passing themselves as the argument
public class PricingModel1 : PricingModelBase { 
    public void accept(PricingModelVisitor v) { v.visitPricingModel1(this); }
    ...
}
public class PricingModel2 : PricingModel {
    public void accept(PricingModelVisitor v) { v.visitPricingModel2(this); }
    ...
}
// concrete implementation of the visitor interface, in this case with the new 
// functionality of adding the appropriate controls to a parent control
public class ParameterGuiVisitor : PricingModelVisitor {
    private Control _parent;
    public ParameterGuiVisitor(Control parent) { _parent = parent; }
    visitPricingModel1(PricingModel1 m) {
        // add controls to _parent for PricingModel1
    }
    visitPricingModel2(PricingModel2 m) {
        // add controls to _parent for PricingModel1
    }
}

现在,当您如果需要显示 PricingModelVisitor 特定子类型的参数的编辑控件,您只需调用

somePricingModel.accept(new ParameterGuiVisitor(parentControl))

它即可为您填充适当的 GUI。

What you described is pretty much exactly the use case for the Visitor Pattern.

EDIT: For your concrete example, you could apply the visitor pattern like this:

// interface used to add external functionality to pricing models
public interface PricingModelVisitor {
    void visitPricingModel1(PricingModel1 m);
    void visitPricingModel2(PricingModel2 m);
    ...
}
// your existing base-class, with added abstract accept() method to accept a visitor
public abstract class PricingModelBase {
    public abstract void accept(PricingModelVisitor v);
    ...
}
// concrete implementations of the PricingModelBase implement accept() by calling the 
// appropriate method on the visitor, passing themselves as the argument
public class PricingModel1 : PricingModelBase { 
    public void accept(PricingModelVisitor v) { v.visitPricingModel1(this); }
    ...
}
public class PricingModel2 : PricingModel {
    public void accept(PricingModelVisitor v) { v.visitPricingModel2(this); }
    ...
}
// concrete implementation of the visitor interface, in this case with the new 
// functionality of adding the appropriate controls to a parent control
public class ParameterGuiVisitor : PricingModelVisitor {
    private Control _parent;
    public ParameterGuiVisitor(Control parent) { _parent = parent; }
    visitPricingModel1(PricingModel1 m) {
        // add controls to _parent for PricingModel1
    }
    visitPricingModel2(PricingModel2 m) {
        // add controls to _parent for PricingModel1
    }
}

now, instead of using a big if-else block when you need to display the edit-controls for the parameters of a specific subtype of PricingModelVisitor, you can simply call

somePricingModel.accept(new ParameterGuiVisitor(parentControl))

and it will populate the appropriate GUI for you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文