As part of the twelve days of code, I’m building a Pomodoro style task tracking application and blogging about it. You can play along at home too. This post talks about the process of defining the object model and parts of the presentation model.
Most of the Model-View-ViewModel examples I’ve seen have not put emphasis on the design of the Model, so I’m not sure that I can call what I’m doing proper MVVM, but i like to have a fully functional application independent of its presentation and then extend parts of the model into presentation model objects. As I was designing the object model, I chose to use interfaces rather than concrete classes. There are several reasons for this, but the main factor was to apply a top-down Test-Driven-Development methodology.
On Top-Down Test-Driven-Development…
The theory behind “top-down TDD” is that by using the Application or other top-level object as the starting point, I only have to work with that object and its immediate dependencies rather than the entire implementation. As dependencies for that object are realized, I create a simple interface rather than a fully flushed out implementation -- I find using interfaces really helps to visualize the code’s responsibilities. Using interfaces also makes the design more pliable, in that I can move responsibilities around without having to overhaul the implementation.
This approach plays well into test-driven-development since tools like RhinoMocks and Moq can quickly generate mock implementations for interfaces at runtime. These dynamic implementations make it possible to define how dependencies will operate under normal (and irregular) circumstances which allows me to concentrate on the object I’m currently constructing rather than several objects at once. After writing a few small tests, I make a conscious effort to validate or rework the object model.
Using the Context/Specification strategy I outlined a few posts ago, the process I followed resembled something like this:
- Design a simple interface (one or two simple methods)
- Create the concrete class and the test fixture
- After spending a few minutes thinking about the different scenarios or states this object could represent, I create a test class for that context
- Write a specification (test) for that context
- Write the assertion for that specification
- Refine the context / subject-initialization / because
- Write the implementation until the test passes
- Refactor.
- Add more tests.
(Confession: I started writing the ITaskApplication first but then switched to ITaskSessionController because I had a clearer vision of what it needed to do.) More on tests later, first let’s look more at the object model.
The Pomodoro Object Model
Here’s a quick diagram of the object model as seen by its interfaces. Note that instead of referring to “Pomodoro” or “Tomato”, I’m using a more generic term “Task”:
In the above diagram, the Task Application represents the top of the application where the user will interact to start and stop (cancel) a task session. By design, the application seats one session at a time and it delegates the responsibility for constructing and initializing a task session to the Session Controller. The Task Application is also responsible for monitoring the session’s SessionFinished event, which when fired, the Application delegates to the AlarmController and SessionController to notify the user and track completion of the session.
Presently the TaskController acts primarily as a factory for new sessions and has no immediate dependencies, though it would logically communicate with a persistence store to track usage information (when I get around to that).
The TaskSession object encapsulates the internal state of the session as well as the timer logic. When the timer completes, the SessionFinished event is fired.
The Realized Implementation
Using a TDD methodology, I created concrete implementations for each interface as I went. At this point, the division between Presentation Model (aka ViewModel) and Controller Logic (aka Model) are clearly visible:
To help realize the implementation, I borrowed a few key classes from the internets:
BaseViewModel
When starting out with the Presentation Model, I found this article as a good background and starting point for the Model-View-ViewModel pattern. The article provides a ViewModelBase implementation which contains the common base ViewModel plumbing, including the IDisposable pattern. The most notable part is the implementation for INotifyPropertyChanged which contains debug only validation logic that throws an error if the ViewModel attempts to raise a PropertyChanged event for a property that does not exist. This simple trick does away with stupid bugs related to ViewModel binding errors caused by typos.
I’ve also added BeginInitialization and EndInitialization members which prevent the PropertyChanged event from firing during initialization. This trick comes in handy when the ViewModel is sufficiently complicated enough that raising the event needlessly impacts performance.
For some reason, I prefer the name BaseViewModel over ViewModelBase. How about you?
DelegateCommand
When it came time to add Commands to my ViewModels, I considered brining Prism to my application primarily to get the DelegateCommand implementation. Perhaps I missed something, but I was only able to find the source code for download. Ultimately, I chose to take the DelegateCommand code-file and include it as part of the Core library instead of compiling the libraries for Prism. The decision to not compile the libraries was based on some tweaking and missing references for .NET 4.0, and the projects were not configured to compile against a strong-name which I would need for my strongly-named application.
The DelegateCommand provides an implementation of the ICommand interface that accepts delegates for the Execute and CanExecute methods, as well as a hook to raise the CanExecuteChanged event. At some point, I may choose to pull the implementation for the Start and Cancel commands out of the TaskApplicationViewModel and into separate classes.
More About the Tests
As promised, here is some more information about the tests and methodology. Rather than list the code for the tests, I thought it would be fun to list the names of the tests that were created during this stage of the development process. Because I’m using a context/specification test-style, my tests should read like specifications:
TaskApplicationSpecs:
- when_a_task_completes
- should_display_an_alarm
- should_record_completed_task
- when_a_task_is_cancelled
- should record cancellation
- when_a_task_is_started
- should_create_a_new_session
- ensure_new_tasks_cannot_be_started
- ensure_running_task_can_be_cancelled
- when_no_task_is_running
- ensure_new_task_can_be_started
- ensure_cancel_task_command_is_disabled
- when_cancelling_a_tasksession
- ensure_session_is_stopped
- when_creating_a_tasksession
- should_create_a_task_with_a_valid_identifier
- ensure_start_date_is_set
- ensure_session_is_started
- ensure_session_length_is_set
- when_finishing_a_tasksession
- ensure_session_is_stopped
- given_a_default_session
- should_be_disabled
- should_not_have_start_date_set
- should_not_have_end_date_set
- should_have_valid_id
- when_session_is_ended
- ensure_endtime_is_recorded
- when_session_is_started
- should_be_active
- should_have_initial_time_interval_set
- should_have_less_time_remaining_than_original_session_length
- should_notify_the_ui_to_update_the_counter_periodically
- should_record_start_time_of_task
- ensure_end_time_of_task_has_not_been_recorded
- when_timer_completes
- should_raise_finish_event
- ensure_finish_event_is_only_raised_once
- should_run_the_counter_down_to_zero
- should_disable_task
- ensure_endtime_has_not_been_recorded
Next Steps
The next logical step in the twelve days of code is creating the View and wiring it up to the ViewModel.
0 comments:
Post a Comment