views:

53

answers:

2

I'm writing a painting program. My basic classes are:

  class Workspace { Bitmap b; List<Command> undoList; }

  class Command { void execute(); }

  class ClearScreen extends Command

  class BlurEffect extends Command

  class View { Bitmap screen; }

  class Interface

The workspace object holds the bitmap which represents the program state. The Command class represents the Command pattern for executing commands on the workspace where undoing works by resetting the state of the workspace and replaying old commands. The interface object links button presses from the user to commands and the view renders the workspace state to the screen bitmap.

My issue is with representing commands. The ClearScreen command is simple; it just tells the workspace to fill the bitmap with white and it happens instantly. The BlurEffect command is more complex; blurring takes a parameter for how much to blur the screen, execution can take a bit of time and the user usually wants to try a few blur parameters before selecting one (i.e. they need to preview what the blur effect will look like before committing). How can I modify the above to support this kind of previewing?

The best I can come up with is to extend Command with something like:

  class BlurCommand extends Command
  {
    void setBlurAmount(float x) ...

    // View can use this to render a preview to the
    // screen bitmap, where the workspace bitmap isn't modified in the process
    void preview(Workspace w, Bitmap b)

    void execute() // apply blur effect to workspace 
  }

So the idea is, in the interface, clicking the "blur" button creates a new BlurCommand object, the "render the screen" method in View will then start calling the "preview" method to render the screen and "execute" is only called when the user wants to apply the effect.

Is this the cleanest way I can do this? I'm trying to stick to the Model-View-Controller design and don't want my preview behaviour complicating things.

A: 

Yes, alternatively you could apply the blur, and undo it if the operation is cancelled, or undo and redo it if a parameter is changed. If replaying the whole command stack is too time-consuming, a snapshot can be taken before the blur is applied.

Maurice Perry
Thanks, I considered this too. Blurring the image is quicker than loading a snapshot from disk. In this case, it's better to use the View's bitmap to preview the image than to preview the result on the Workspace's bitmap, which would force me to restore from a snapshot to try another blur.
BobDuck
A: 

Do you have any unit tests that test the current features of your paint program? One thing that I think would really help you arrive at a solid design is using TDD (Test Driven Development/Design). Write a test that would simulate a "preview" type action, then make that test pass. Once your existing tests and your new preview test all pass, then see what your application code looks like.

Matthew J Morrison
I'm just designing right now. Of course, unit testing is a good idea.
BobDuck
The thing about TDD is that you don't design your code first, you write your tests first and let the design of your code evolve from how your tests expect your system to work.
Matthew J Morrison
I'm not sure how this helps here. There are many implementations that would pass e.g. a blur preview and blur apply test. I'm looking for a clean implementation, not just one that will pass the tests.
BobDuck
I'm not sure that you're understanding how TDD works. Sitting down and designing a "clean" implementation up front may be fine, in theory, but all of that design work will be for nothing if when you try to implement that design it fails because of edge cases. When you write your tests first, the best design will just naturally happen. I recommend reading up on the TDD loop: Write a test, make it pass, re-factor.
Matthew J Morrison
I understand TDD, I just don't see what's wrong with doing a bit of design up front to plan for the bigger issues that are definitely going to bite you latter as well as being a bit flexible. Seem sensible to me. I've already written a prototype in the past without undo/redo in mind for example and it's a nightmare to retrofit that feature. Refactoring when you can avoid it is not fun. Having to rewrite all my command classes plus the unit tests is something I'd rather not do.
BobDuck
TDD was created because if the problems with doing design up front. It is not possible to design software in your head and have it just work. With TDD it is not possible to have software that doesn't work (according to your tests' definition of 'work'). Having said that, if TDD is not right for your project, then trying to force it would probably end up causing more problems than benefit. If it were me trying to solve your design question, I would turn to TDD for help. YMMV.
Matthew J Morrison