4

Say I have a hierarchy of classes, let's use the classic Shape examples:

abstract class Shape
Circle : Shape
Square : Shape

I have a second hierarchy of renderer classes that handle the rendering of shapes in different ways:

abstract class ShapeRenderer
HtmlShapeRenderer : ShapeRenderer
WindowsFormsShapeRenderer : ShapeRenderer

Allowing these to vary independently would traditionally involve using the Bridge pattern. Allowing the rendering actions to be extended without modifying the Shape classes would traditionally involve the Visitor pattern.

However, both of these focus exclusively on extending the implementation side and not the abstraction side. Say I wanted to add a new Shape, say Triangle - I want to be able to support rendering the Triangle as well. Since both the Visitor and the Bridge pattern rely on "flattening" the abstraction hierarchy into a set of methods, e.g.:

public abstract class ShapeRenderer
{
     public abstract void RenderCircle(Circle c);
     public abstract void RenderSquare(Square s);
}

The only way to extend the Shape hierarchy is to modify the code of the base ShapeRenderer class, which is a breaking change.

Jon, to clarify: Using the Bridge or Visitor allows clients to provide alternative rendering implementations, but requires them to know about all potential Shapes. What I'd like to be able to do is allow clients to also be able to extend the Shape class and require them to provide a rendering implementation for their new class. This way, existing code can work with any type of Shape, without worrying about the particulars of rendering them.

Is there a common solution to this sort of problem usable in C#?

4

4 回答 4

2

I think it should be a breaking change. If you add a shape, the existing renderers clearly aren't going to be able to cope - they'll need to be changed.

You could change ShapeRenderer to add RenderTriangle() as a virtual (non-abstract) method which just logs the fact that it can't render appropriately, and then fix up the renderers one at a time, but fundamentally you're not going to be able to render the new type without more code.

What kind of non-breaking change are you really hoping to achieve?

于 2008-12-09T17:17:45.600 回答
2

策略模式如何?策略是对 RenderEngine 实现的引用。当你想添加一个新的形状时,你创建一个新的渲染引擎实现,它知道新的形状实现并实现相应的渲染功能。您向 Shape 添加了一个虚拟函数,该函数充当帮助函数来选择正确的形状渲染函数 - 即 Circle 对象调用 renderCircle() 函数等。

在 C++ 中,可能看起来像:

class Triangle : public Shape
{
  public:
      Triangle( const RenderEngine& whichRenderEngine );
      void render( void ) { renderStrategy->renderTriangle( *this );

  private:
      RenderEngine* renderStrategy;
};

class TriangleRender : HTMLShapeRender
{
   public:
      // if inheriting from concrete class, all other rendering functions 
      // already exist... otherwise re-implement them here.

      void renderTriangle( const Triangle& t ) { /* impl */ }
};

HTMLRenderer r; // doesn't know about Triangles.
Circle c( &r );
c.render();

Square s( &r );
s.render();

// Now we add Triangle
TriangleRenderer tr;
Triangle t( &tr );
t.render();

Square s2( &tr );  // tr still knows how to render squares... 
s2.render();
于 2008-12-10T03:50:34.450 回答
1

Design to an interface not an implementation.

Hey - I get to use the same answer twice today (i guess it's arguable that Renderer is an implementation)...

I'm not sure I'd go with the ShapeRenderer class. What about an IRenderHTML, IRenderWindows that are implemented by the shape classes?

You get extensibility with the Shapes as well as with the Renderings.

I think it may be better OO to say hey circle go render yourself, than to pass the circle to a utility class for rendering. You could readily add new shapes and new renderings by letting the shapes do the rendering themselves.

于 2008-12-09T17:22:06.933 回答
1

我的解决方案几乎肯定是使用抽象工厂,在这种情况下,我将加载一个按类型键入的 ShapeRenderers 字典,其中类型是 Shape 的子类,并让工厂提供每个 Shape 所需的 ShapeRenderer(以及可能是平台,例如 Window、Web、iPhone)。

这样做意味着添加新形状只需要更改配置存储,因此工厂知道哪些渲染器要映射到哪些形状,以及包含具体实现的新程序集。

于 2008-12-09T17:58:57.747 回答