27

What is the best practice when it comes to writing classes that might have to know about the user interface. Wouldn't a class knowing how to draw itself break some best practices since it depends on what the user interface is (console, GUI, etc)?

In many programming books I've come across the "Shape" example that shows inheritance. The base class shape has a draw() method that each shape such as a circle and square override. This allows for polymorphism. But isn't the draw() method very much dependent on what the user interface is? If we write this class for say, Win Forms, then we cannot re-use it for a console app or web app. Is this correct?

The reason for the question is that I find myself always getting stuck and hung up on how to generalize classes so they are most useful. This is actually working against me and I'm wondering if I'm "trying too hard".

5
  • Why do you want to decouple? Because you heard it was the right thing to do or do you have other reasons? Commented Jul 26, 2011 at 19:59
  • 2
    The whole "class knowing how to draw itself" is just a horrible, ancient example I wish would disappear. Especially on the game programmer's stack =) Commented Jul 26, 2011 at 20:10
  • 2
    @Chad I am not really experienced much outside of school. I've read books and I genuinely like learning and reading new things about design patterns and best practices. So yes, you can say I've heard that decoupling is good but it also makes sense. I want to write code that I can use for a desktop WinForms app, for example, then take that code and reuse as much as possible for a website or even a silverlight app.
    – Pete
    Commented Jul 27, 2011 at 1:56
  • @Pete - That is a good answer. Commented Jul 27, 2011 at 13:23
  • 2
    @Patrick: to be fair, if you're writing a Shape class, then you're probably writing the graphics stack itself, not writing a client to the graphics stack.
    – Ken Bloom
    Commented Jul 27, 2011 at 14:09

9 Answers 9

14
+50

What is the best practice when it comes to writing classes that might have to know about the user interface. Wouldn't a class knowing how to draw itself break some best practices since it depends on what the user interface is (console, GUI, etc)?

That depends on the class and the use case. A visual element knowing how to draw itself is not necessarily a violation of the single responsibility principle.

In many programming books I've come across the "Shape" example that shows inheritance. The base class shape has a draw() method that each shape such as a circle and square override. This allows for polymorphism. But isn't the draw() method very much dependent on what the user interface is?

Again, not necessarily. If you can create an interface (drawPoint, drawLine, set Color etc.), you can pretty much pass any context for drawing things onto something to the shape, for example within the shape's constructor. This would enable shapes to draw themselves on a console or any canvas given.

If we write this class for say, Win Forms, then we cannot re-use it for a console app or web app. Is this correct?

Well, that's true. If you write a UserControl (not a class in general) for Windows Forms, then you won't be able to use it with a console. But that's not a problem. Why would you expect a UserControl for Windows Forms to work with any kind of presentation? The UserControl should do one thing and do it well. It's bound to a certain form of presentation by definition. In the end, the user needs something concrete and not an abstraction. This might only be partly true for frameworks, but for end-user applications, it is.

However, the logic behind it should be decoupled, so you can use it again with other presentation technologies. Introduce interfaces where necessary, to maintain orthogonality for your application. The general rule is: The concrete things should be exchangeable with other concrete things.

The reason for the question is that I find myself always getting stuck and hung up on how to generalize classes so they are most useful. This is actually working against me and I'm wondering if I'm "trying too hard".

You know, extreme programmers are fond of their YAGNI attitude. Don't try to write everything generically and don't try too hard trying to make everything general purpose. This is called overengineering and will eventually lead to totally convoluted code. Give each component exactly one task and make sure it does it well. Put in abstractions where necessary, where you expect things to change (e.g. interface for drawing context, like stated above).

In general, when writing business applications, you should always try to decouple things. MVC and MVVM are great to decouple the logic from the presentation, so you can reuse it for a web presentation or a console application. Keep in mind that in the end, some things have to be concrete. Your users can't work with an abstraction, they need something concrete. Abstractions are only helpers for you, the programmer, to keep the code extensible and maintainable. You need to hink about where you need your code to be flexible. Eventually all abstractions have to give birth to something concrete.

Edit: If you want to read more about architecture and design techniques which can provide best practices, I suggest you read @Catchops answer and read about SOLID practices on wikipedia.

Also, for starters, I always recommend the following book: Head First Design Patterns. It'll help you understand abstraction techniques/OOP design practices, more so than the GoF book (which is excellent, it just doesn't suit beginners).

3
  • 1
    Good answer, but extreme programmers don't 'put in abstractions where we expect things to change'. We put in abstractions where things are changing, to DRY up the code. Commented Jul 29, 2011 at 4:53
  • 1
    @kevin cline that's awesome unless you are designing a publicly available library with an API that needs to conform to previous behaviour and interfaces. Commented Aug 1, 2011 at 14:38
  • @Magnus - yes, designing a library in a some languages, planning for backward binary compatibility, is tricky. One is forced to write all sorts of currently unneeded code to allow for future extension. This may be reasonable for languages that compile to the metal. It's silly for languages that compile to a virtual machine. Commented Aug 2, 2011 at 16:15
20

You are definitely right. And no, you are "trying just fine" :)

Read about the Single responsibility principle

Your inner working of the class and the way this information should be presented to the user are two responsibilities.

Don't be afraid to decouple classes. Rarely the problem is too much abstraction and decoupling :)

Two very relevant patterns are Model–view–controller for web applications and Model View ViewModel for Silverlight / WPF.

5
  • 1
    +1 You can even use MVC for not-web applications, at the very least it helps you think in terms that keep the responsibilities clear. Commented Jul 26, 2011 at 20:13
  • MVVM is silimar to MVC. MVC is mostly for stateless applications like web apps. Commented Jul 27, 2011 at 15:04
  • 3
    -1 In my experience one of the most common problems is premature generalization and over-engineering in general.
    – snakehiss
    Commented Jul 29, 2011 at 21:26
  • 3
    You first need to know how to engineer something in order to over-engineer it. In my experience, people 'under-engineer' software much more often. Commented Jul 29, 2011 at 21:45
  • While I appreciate efforts to improve software design, substituting a call on an interface, for a call on a class, does not decouple anything semantically. Consider what happens when using a dynamically typed language. There is far too much emphasis on superficial (syntactic) decoupling, and little on true decoupling, in the advice provided in the programmers section of stack exchange. Commented Aug 22, 2014 at 18:26
7

I use MVVM a lot and in my opinion, a business object class should never need to know anything about the user interface. Sure they might need to know the SelectedItem, or IsChecked, or IsVisible, etc but those values do not need to tie into any particular UI and can be generic properties on the class.

If you need to do something to the interface in code behind, such as setting Focus, running an Animation, handling Hot Keys, etc then the code should be part of the UI's code-behind, not your business logic classes.

So I would say don't stop trying to split apart your UI and your classes. The more decoupled they are, the easier they are to maintain and test.

6

There are a number of tried and true design patterns that have been developed over the years to address exactly what you are talking about. Other answers to your question have referred to the Single Reponsibility Principle - which is absolutely valid - and what seems to be driving your question. That principle simply states that a class needs to do one thing WELL. In other words raising Cohesion and lowering Coupling which is what good object oriented design is all about - does a class do one thing well, and not have a lot of dependencies on others.

Well...you are right in observing that if you want to draw a circle on an iPhone, it will be different than drawing one on a PC running windows. You MUST have (in this case) a concrete class that draws one well on the iPhone, and another that draws one well on a PC. This is where the basic OO tenent of inheritance that all of those shapes examples breaks down. You simply cannot do it with inheritance alone.

That is where interfaces come in - as the Gang of Four book states (MUST READ) - Always favor implementation over inheritance. In other words, use interfaces to piece together an architecture that can perform various functions in many ways without relying on hard coded dependencies.

I've seen referece to the SOLID principles. Those are great. The 'S' is the single responsiblity principle. BUT, the 'D' stands for Dependency Inversion. The Inversion of Control pattern (Dependency Injection) can be used here. It is very powerful and can be used to answer the question of how to architect a system that can draw a circle for an iPhone as well as one for the PC.

It is possible to create an architecture that contains common business rules and data access, but have various implementations of user interfaces using these constructs. It really helps, however, to have actually been on a team that implemented it and see it in action to really understand it.

This is just a quick high-level answer to a question that deserves a more detailed answer. I encourage you to look further into these patterns. Some more concrete implementation of these patters can be found as well known names of MVC and MVVM.

Good luck!

1
  • I love it when someone downvotes something without giving a comment as to why (sarcasm, of course). The principles I stated are true - would the downvoter please stand up and say why.
    – Catchops
    Commented Aug 1, 2011 at 12:41
2

What is the best practice when it comes to writing classes that might have to know about the user interface.

Wouldn't a class knowing how to draw itself break some best practices since it depends on what the user interface is (console, GUI, etc)?

In this case you can still use MVC/MVVM and inject different UI implementations using common interface:

public interface IAgnosticChartDrawing
{
   public void Draw(ChartProperties chartProperties);
   event EventHandler ChartPanned;
}

public class GuiChartDrawer : UserControl, IAgnosticChartDrawing
{
    public void Draw(ChartProperties chartProperties)
    {
        //GDI, GTK or something else...
    }
    
    //Implement event based on mouse actions
}

public class ConsoleChartDrawer : IAgnosticChartDrawing
{
    public void Draw(ChartProperties chartProperties)
    {
        //'Draw' using characters and symbols...
    }

    //Implement event based on keyboard actions
}

IAgnosticChartDrawing guiView = new GuiChartDrawer();
IAgnosticChartDrawing conView = new ConsoleChartDrawer();

Model model = new FinancialModel();

SampleController controllerGUI = new SampleController(model, guiView);
SampleController controllerConsole = new SampleController(model, conView);

This way you will be able to re-use your Controller and Model logic while being able to add new types of GUI.

2

There are different patterns to do that: MVP, MVC, MVVM, etc...

A nice article from Martin Fowler (big name) to read is GUI Architectures: http://www.martinfowler.com/eaaDev/uiArchs.html

MVP has not been mentioned yet but it definitely deserve to be cited: take a look at it.

It's the pattern suggested by the developers of Google Web Toolkit to use, it's really neat.

You can find real code, real examples and rationale on why this approach is useful here:

http://code.google.com/webtoolkit/articles/mvp-architecture.html

http://code.google.com/webtoolkit/articles/mvp-architecture-2.html

One of the advantages of following this or similar approaches which has not been stressed enough here is the testability! In a lot of cases I would say that is the main advantage!

1
  • 1
    +1 for the links. I was reading a similar article on MSDN about MVVM and those google articles are much better though on a slightly different pattern.
    – Pete
    Commented Jul 28, 2011 at 6:21
2

...isn't the draw() method very much dependent on what the user interface is? If we write this class for say, Win Forms, then we cannot re-use it for a console app or web app. Is this correct?

Above sounds correct to me. Per my understanding, one can say it means relatively tight coupling between Controller and View in terms of MVC design pattern. This also means that to switch between desktop-console-webapp one will have to accordingly switch both Controller and View as a pair - only model remains unchanged.

...I find myself always getting stuck and hung up on how to generalize classes so they are most useful.

Well my current take on above is that this View-Controller coupling we're talking about is OK and even more, it's rather trendy in modern design.

Though, a year or two ago I also felt insecure about that. I've changed my mind after studying discussions at Sun forum on patterns and OO design.

If you're interested, try this forum yourself - it has migrated to Oracle now (link). If you get there, try pinging guy Saish - back then, his explanations on these tricky matters turned out most helpful to me. I can't tell though if he still participates - myself I haven't been there for quite a while

0
1

This is one of the places where OOP fails to do a good job at abstraction. OOP polymorphism uses dynamic dispatch over a single variable ('this'). If the polymorphism was rooted at Shape then you cannot polymorphic-ally dispatch on the renderer (console, GUI etc).

Consider a programming system which could dispatch on two or more variables:

poly_draw(Shape s, Renderer r)

and also suppose the system could give you a way to express poly_draw for various combinations of Shape types and Renderer types. It would then be easy to come up with a classification of shapes and renderers right? The type checker would somehow help you figure out is there were shape and renderer combinations which you may have missed implementing.

Most OOP languages don't support anything like above (a few do, but they are not mainstream). I would suggest you take a look at the Visitor pattern for a workaround.

1

But isn't the draw() method very much dependent on what the user interface is?

From a pragmatic sort of view, some code in your system needs to know how to draw something like a Rectangle if that's a user-end requirement. And that's going to boil down at some point to doing really low-level things like rasterizing pixels or displaying something in a console.

The question to me from a coupling standpoint is who/what should depend on this type of information, and to what degree of detail (how abstract, e.g.)?

Abstracting Drawing/Rendering Capabilities

Because if the higher-level drawing code only depends on something very abstract, that abstraction might be able to work (through substitution of concrete implementations) on all the platforms you intend to target. As a contrived example, some very abstract IDrawer interface might be capable of being implemented in both console and GUI APIs to do things like plot shapes (the console implementation might treat the console like some 80xN "image" with ASCII art). Of course that's a contrived example since that's usually not what you want to do is treat a console output like an image/frame buffer; typically most user-end needs call for more text-based interactions in consoles.

Another consideration is how easy is it to design a stable abstraction? Because it might be easy if all you're targeting is modern GUI APIs to abstract away the basic shape-drawing capabilities like plotting lines, rectangles, paths, text, things of this sort (just simple 2D rasterization of a limited set of primitives), with one abstract interface that can be easily implemented for them all through various subtypes with little cost. If you can design such an abstraction effectively and implement it on all target platforms, then I would say it's a much lesser evil, if even an evil at all, for a shape or GUI control or whatever to know how to draw itself using such an abstraction.

But say you're trying to abstract away the gory details that vary between a Playstation Portable, iPhone, an XBox One, and powerful gaming PC while your needs are to utilize the most cutting edge real-time 3D rendering/shading techniques on each one. In that case trying to come up with one abstract interface to abstract away the rendering details when the underlying hardware capabilities and APIs vary so wildly is almost certain to result in enormous time designing and re-designing, a high probability of recurring design changes with unanticipated discoveries, and likewise a lowest-common denominator solution that fails to exploit the full uniqueness and power of the underlying hardware.

Making Dependencies Flow Towards Stable, "Easy" Designs

In my field I'm in that last scenario. We target lots of different hardware with radically different underlying capabilities and APIs, and to try to come up with one rendering/drawing abstraction to rule them all is borderline hopeless (we might become world-famous just doing that effectively as it would be a game changer in the industry). So the last thing I want in my case is like the analogical Shape or Model or Particle Emitter that knows how to draw itself, even if it is expressing that drawing in the highest-level and most abstract way possible...

... because those abstractions are too hard to design correctly, and when a design is hard to get correct, and everything depends on it, that's a recipe for the most costly central design changes that ripple and break everything depending on it. So the last thing you want is for the dependencies in your systems to flow towards abstract designs too hard to get correct (too hard to stabilize without intrusive changes).

Difficult Depends on Easy, Not Easy Depends on Difficult

So what we do instead is make the dependencies flow towards things that are easy to design. It's much easier to design an abstract "Model" which is just focused on storing things like polygons and materials and get that design correct than it is to design an abstract "Renderer" which can effectively be implemented (through substitutable concrete subtypes) to service drawing requests uniformly for hardware as disparate as a PSP from a PC.

enter image description here

So we invert the dependencies away from the things that are difficult to design. Instead of making abstract models know how to draw themselves to an abstract renderer design that they all depend on (and break in their implementations if that design changes), we instead have an abstract renderer that knows how to draw every abstract object in our scene (models, particle emitters, etc), and so we can then implement an OpenGL renderer subtype for PCs like RendererGl, another for PSPs like RendererPsp, another for mobile phones, etc. In that case the dependencies are flowing towards stable designs, easy to get correct, from renderer to various types of entities (models, particles, textures, etc) in our scene, not the other way around.

enter image description here

  • I am using "stability/instability" in a slightly different sense from Uncle Bob's afferent/efferent couplings metric which is measuring more the difficulty of change as far as I can understand. I am talking more about "probability of requiring change", though his stability metric is useful there. When "probability of change" is proportional to "ease of change" (ex: the things most likely to require changes have the highest instability and afferent couplings from Uncle Bob's metric), then any such probable changes are cheap and non-intrusive to make, requiring only replacing an implementation without touching any central designs.

If you find yourself trying to abstract away something at a central level of your codebase and it's just too difficult to design, instead of stubbornly beating heads against walls and constantly making intrusive changes to it each month/year that requires updating 8,000 source files because it's breaking everything that depends on it, my number one suggestion is to consider inverting the dependencies. See if you can write the code in a way such that the thing which is so hard to design is depending on everything else that's easier to design, not having the things that are easier to design depending on the thing that's so hard to design. Note that I am talking about designs (specifically interface designs) and not implementations: sometimes things are easy to design and hard to implement, and sometimes things are hard to design but easy to implement. Dependencies flow towards designs, so the focus should only be on how difficult something is to design here to determine the direction in which dependencies flow.

Single Responsibility Principle

To me SRP is not so interesting here usually (though depending on the context). I mean there's a tightrope balancing act in designing things that are clear in purpose and maintainable but your Shape objects might have to expose more detailed information if they don't know how to draw themselves, for example, and there might not be much meaningful things to do with a shape in a particular use context than construct it and draw it. There's trade-offs with just about everything, and it's not related to SRP that can make things aware of how to draw themselves capable of becoming such a maintenance nightmare in my experience in certain contexts.

It has much more to do with coupling and the direction in which dependencies flow in your system. If you're trying to port an abstract rendering interface that everything depends upon (because they're using it to draw themselves) to a new target API/hardware and realize you have to considerably change its design to make it work effectively there, then that's a very costly change to make which requires replacing implementations of everything in your system that knows how to draw themselves. And that's the most practical maintenance issue I encounter with things aware of how to draw themselves if that translates to a boatload of dependencies flowing towards abstractions which are too difficult to design correctly upfront.

Developer Pride

I mention this one point because, in my experience, this is often the biggest obstacle to flowing the direction of dependencies towards things easier to design. It's very easy for developers to get a bit ambitious here and say, "I'm gonna design the cross-platform rendering abstraction to rule them all, I'm gonna solve what other developers spend months in porting, and I'm gonna get it right and it's gonna work like magic on every single platform we support and utilize the state-of-the-art rendering techniques on every one; I already envisioned it in my head." In which case they resist the practical solution which is to avoid doing that and just flip the direction of the dependencies and translate what might be enormously costly and recurring central design changes to simply cheap and local recurring changes to implementation. There needs to be some sort of "white flag" instinct in developers to give up when something is too hard to design to such an abstract level and reconsider their whole strategy, otherwise they're in for a lot of grief and pain. I would suggest to transfer such ambitions and fighting spirit to state-of-the-art implementations of an easier thing to design than to take such world-conquering ambitions to the interface design level.

16
  • 1
    I cannot seem to grasp the idea of "invert the dependencies away from the things that are difficult to design", are you talking about inheritance only ? Using the example of PSP / OpenGL, you mean instead of making a OpenGLBillboard, you'd make a OpenGLRenderer that knows how to draw any type of IBillBoard ? But would it do that by delegating logic to IBillBoard, or would it have huge switches or IBillBoard types with conditionals ? That's what I find hard to grasp, because that doesn't seem maintainable at all ! Commented Dec 18, 2018 at 20:28
  • @SteveChamaillard The difference is, say, PSP (limited hardware) must handle volume primitives using old school screendoor transparency and a different ordering of rendering evaluation: digitalrune.github.io/DigitalRune-Documentation/media/…. When you have one central RendererPsp which knows the high-level abstractions of your game scene, it can then do all the magic and backflips it needs to render such things in a way that looks convincing on the PSP....
    – user321630
    Commented Dec 18, 2018 at 20:30
  • Whereas if you had such volume primitives requesting to render themselves, either their operations are so high-level that they're basically specifying themselves to render (which makes no difference except roundabout redundancy and further coupling), or the renderer abstraction is incapable of really doing things the most effective way possible on the PSP (at least without doing backflips, which it wouldn't need to do if the dependencies were inverted).
    – user321630
    Commented Dec 18, 2018 at 20:32
  • 1
    Ok i think I got it now. Basically you're saying that such concerns (i.e hardware) are so high level that making low level objects such as a BillBoard depend on those would be really hard to do ? Whereas a IRenderer is high level already and could depend on these concerns with a lot less trouble ? Commented Dec 18, 2018 at 20:54
  • 1
    With cutting-edge rendering concerns on disparate platforms, disparate hardware capabilities, it's too hard to design something where I'm the boss and tell it what to do. It's much easier for me to say, "Hey, I'm a cloudy/foggy thing with greyish watery particles like this, and try to make me look good please, don't capture my neck which I haven't shaved lately", and let the renderer figure out how to render me in a way as beautiful and as real-time as possible given the limitations it's working with. Any attempt to tell it what to do will almost certainly result in a counter-productive path.
    – user321630
    Commented Dec 18, 2018 at 21:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.