如何处理C#中的复合类

5
我有一个名为IDrawing的接口,以及两个实现该接口的类TextDrawingShapeDrawing。我还有更复杂的绘图类,它们也实现了IDrawing接口,但是由多个其他IDrawing类组成。
我不确定如何在View类中绘制这些复杂的类。我不想让View类学习如何绘制每个新的IDrawing类,因为这不是一个好的设计。我还有哪些其他选项?我的设计是否不正确?如果是,那么如何告诉ViewDraw方法知道复杂类的基本部分并将它们绘制出来?下面是我的代码:
   public interface IDrawing
   {
   }

   public class TextDrawing : IDrawing
   {
   }

   public class ShapeDrawing : IDrawing
   {      
   }

   public class SignDrawing : IDrawing
   {
      public TextDrawing Text { get; set; }
      public ShapeDrawing Border { get; set; }
   }

   public class MoreComplexDrawing : IDrawing
   {
      public TextDrawing Text { get; set; }
      public ShapeDrawing Border1 { get; set; }
      public ShapeDrawing Border2 { get; set; }
   }

   public class View
   {
      public void Draw(IDrawing drawing)
      {
           // The View only knows how to draw TextDrawing and ShapeDrawing.
           // These as the primitive building blocks of all drawings.
           // How can it draw the more complex ones!
           if (drawing is TextDrawing)
           {
              // draw it
           }
           else if (drawing is ShapeDrawing)
           {
              // draw it
           }
           else
           {
              // extract the drawings primitive parts (TextDrawing and ShapeDrawing) and draw them!
           }
      }
   }

更新:

我收到建议,建议在我的绘图类中实现Draw()方法,但如果我这样做,它们将不再是通用的。例如,在我有不同的绘图策略的其他项目中将无法使用它们。有没有办法使我的设计更灵活,并保持其通用性?


2
阅读策略模式并研究倒置绘制的责任。 - Nkosi
@Vahid 抽象出第三方依赖项,以便您与它们的耦合不那么紧密。视图不应关心您使用什么来绘制。在这种情况下,它的责任是编排演示文稿,而不是自己完成它。 - Nkosi
https://sourcemaking.com/design_patterns/strategy - Matthew Whited
1
@Nkosi 我明白了!这太强大了! - Vahid
2
@Vahid,很高兴你解决了问题。请记住,伴随着强大的力量而来的是巨大的责任。请负责任地使用这种力量。 - Nkosi
显示剩余4条评论
3个回答

5
我会将一个Draw()方法添加到接口中,并在每个类中实现该方法。
这样做的好处是您的View不需要关心实际类型是什么。
public interface IDrawing
{
    void Draw();
}

public class TextDrawing : IDrawing
{
    public void Draw()
    {
        // Draw a TextDrawing
    }
}

public class ShapeDrawing : IDrawing
{      
    public void Draw()
    {
        // Draw a ShapeDrawing
    }
}

public class SignDrawing : IDrawing
{
    public TextDrawing Text { get; set; }
    public ShapeDrawing Border { get; set; }

    public void Draw()
    {
        // Draw a SignDrawing
    }
}

public class MoreComplexDrawing : IDrawing
{
    public TextDrawing Text { get; set; }
    public ShapeDrawing Border1 { get; set; }
    public ShapeDrawing Border2 { get; set; }

    public void Draw()
    {
        // Draw a MoreComplexDrawing
    }
}

public class View
{
    public void Draw(IDrawing drawing)
    {
        //Draw the drawing
        drawing.Draw();
    }
}

更新 - 将对SkiaSharp的依赖抽象出来

您需要为SkiaSharp或任何实际进行绘制的外部依赖项创建一个包装器。这应该存在于与您的IDrawing接口和派生类相同的程序集中。

public interface IDrawingContext
{
    // Lots of drawing methods that will facilitate the drawing of your `IDrawing`s
    void DrawText(...);
    void DrawShape(...);
    void DrawBorder(...);
}

除了 SkiaSharp 特定的实现之外

public class SkiaSharpDrawingContext IDrawingContext
{
    // Lots of drawing methods that will facilitate the drawing of your IDrawings
    public void DrawText(...) { /* Drawing code here */ }
    public void DrawShape(...) { /* Drawing code here */ }
    public void DrawBorder(...) { /* Drawing code here */ }
}

更新IDrawing接口

public interface IDrawing
{
    void Draw(IDrawingContext drawingContext);
}

更新你的类以反映这个变化。你的类将调用IDrawingContext实现中的方法来进行绘制。

在你的应用程序中创建一个特定的依赖实现,并更新你的View类以使用新的SkiaSharpDrawingContext

public class View
{
    public void Draw(IDrawing drawing)
    {
        // This should ideally be injected using an IOC framework
        var drawingContext = new SkiaSharpDrawingContext(...);

        //Draw the drawing
        drawing.Draw(drawingContext);
    }
}

1
@Vahid 完成了。我已经将对 SkiaSharp 的依赖抽象化了。 - phuzi
感谢phuzi提供的完整解释。 - Vahid

4

您可以选择强制类实现一个名为Draw()的方法。

public interface IDrawing
{
    void Draw(); 
}

现在,您只需调用此方法,并将内部实现的责任交给各自的类即可。

public void Draw(IDrawing drawing)
{

     drawing.Draw();

例如,TextDrawing 将被强制实现此方法。
public class TextDrawing : IDrawing
{
    public void Draw()
    {
        // draw Text-like
    }
}

在您的View类中,调用drawing.Draw();将得到正确的实现。

如何告诉视图的绘制方法知道复杂类的基本部分并将其绘制出来?

现在,在复杂类中,您只需使用已经实现的简单绘制类中的方法即可。
public class SignDrawing : IDrawing
{
    public TextDrawing Text { get; set; }
    public ShapeDrawing Border { get; set; }

    public void Draw()
    {
        Text.Draw();

        Border.Draw();

        Text.Draw();
    }
}

谢谢Mong,View中的Draw方法依赖于外部库进行绘制(在我的情况下,是SkiaSharp库)。如果我在这些类中实现Draw方法,它们将不再是通用的!例如,我将无法在其他项目中使用它们,因为我有不同的绘制策略。 - Vahid
@Vahid 你不想让文本绘制始终以相同的方式绘制吗?你所说的“通用”是什么意思? - Mong Zhu
例如,我现在正在使用SkiaSharp(第三方库)来绘制我的图形。如果我将Draw()方法移动到Drawing类中,那么它们将依赖于这个特定的库,如果以后我决定使用例如WPF本身来可视化这些图形,我就会遇到问题! - Vahid

2
you can polymorphically draw without checking the type.

 public class View
   {
      public void Draw(IDrawing drawing)
      {
         drawing.Draw();
      }
   }
Encapsulate each drawing strategy (Strategy Pattern) in different class and you could inject each strategy via DI.

你说,“View中的Draw方法依赖于外部库进行绘制(在我的情况下,是SkiaSharp库)”。但现在它不知道任何关于绘图策略的事情。它们被多态地绑定。 - aspxsushil

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