5

I am asking a question about design patterns. I have an IDrawing interface, and two classes called TextDrawing and ShapeDrawing that implement this interface. I also have a View class that knows how to draw these classes. However, I have more complex drawing classes that also implement the IDrawing interface, but are composed of several other IDrawing classes.

I am not sure how to draw these complex classes in my View class. I don't want to teach the View class how to draw each new IDrawing class because it would not be a good design. What other options do I have? Is my design incorrect, and if so, how can I tell the View's Draw method to know the primitive parts of the complex classes and draw them? Here is my code:

 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! } } } 

Update:

I received suggestions to implement the Draw() method in my drawing classes, but if I do this, they will not be general anymore. For example, I will not be able to use them in other projects where I have a different strategy for drawing. Is there a way to make my design more flexible and maintain its generality?

9
  • 2
    Read up on strategy pattern and also look into inverting the responsibility of drawing. Commented Aug 23, 2017 at 15:09
  • Add a draw method to your interface and just have each class handle drawing themselves. Commented Aug 23, 2017 at 15:11
  • @Nkosi Can you please see my update? Commented Aug 23, 2017 at 15:57
  • 1
    @Nkosi I got it now! This is so powerful! Commented Aug 23, 2017 at 18:28
  • 2
    @Vahid glad you figured it out. And remember with great power comes great responsibility. Use that power responsibly. Commented Aug 23, 2017 at 18:35

3 Answers 3

5

I would add a Draw() method to the interface and implement that in each of your classes.

The benefit of this is that your View doesn't care what the actual type is.

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(); } } 

UPDATE - Abstract away the dependency on SkiaSharp

You'll need to create a wrapper for SkiaSharp or whichever external dependency that will actually do the drawing. This should exist in the same assembly as your IDrawing interface and derived classes.

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

As well as the SkiaSharp specific implementation

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 */ } } 

Update the IDrawing interface to

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

Updating your classes too to reflect this change. Your classes will call methods on the IDrawingContext implementation to do the drawing.

Create a dependency specific implementation within your application(s) and update your View class to use the new 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); } } 
Sign up to request clarification or add additional context in comments.

2 Comments

@Vahid done. I've abstracted the dependency on SkiaSharp
Thank you phuzi for the complete explanations.
4

You have the choice to force the classes to implement a method called Draw().

public interface IDrawing { void Draw(); } 

now you can simply call this method and give the responsibility of the inner implenmentation to the classes on their own.

public void Draw(IDrawing drawing) { drawing.Draw(); 

For Example TextDrawing would be forced to implement this method

public class TextDrawing : IDrawing { public void Draw() { // draw Text-like } } 

In your View class the call to drawing.Draw(); will result in the correct implementation.

How can I tell the View's Draw method to know the primitive parts of the complex classes and draw them?

Now in the complex classes you would simply use the methods that are already implemented in the simple drawing classes

public class SignDrawing : IDrawing { public TextDrawing Text { get; set; } public ShapeDrawing Border { get; set; } public void Draw() { Text.Draw(); Border.Draw(); Text.Draw(); } } 

3 Comments

Thanks Mong, The Draw method in View relies on an external library to draw (in my case, it is SkiaSharp library). If I implement the Draw in these classes they will not be general anymore! For example I will not be able to use them in other projects where I have a different strategy to Draw the things.
@Vahid don't you want text draw always to draw t the same way? What do you mean exactly by "General" ?
For example I'm using SkiaSharp (a 3rd party library) now to draw my Drawings. If I move the Draw() method to Drawing classes then they will depend on this specific library and later if I decide to use for example WPF itself to visualize these drawings I will have problem!
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. 

1 Comment

you said, "The Draw method in View relies on an external library to draw (in my case, it is SkiaSharp library". But now it does not know anything about drawing strategy. they are bound polymorphically.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.