I'm about to use the 'strategy' pattern in my iOS app, but feel like my approach violates the somehow fundamental MVC pattern.
My app is displaying visual "stories", and a Story
consists (i.e. has @properties
) of one Photo
and one or more VisualEvent
objects to represent e.g. animated circles or moving arrows on the photo. Each VisualEvent
object therefore has a eventType
@property
, that might be e.g. kEventTypeCircle
or kEventTypeArrow
. All events have things in common, like a startTime
@property
, but differ in the way they are being drawn on the StoryPlayerView
.
Currently I'm trying to follow the MVC pattern and have a StoryPlayer
object (my controller) that knows about both the model objects (like Story
and all kinds of visual events) and the view object StoryPlayerView
.
To chose the right drawing code for each of the different visual event types, my StoryPlayer
is using a switch
statement.
@implementation StoryPlayer
// (...)
- (void)showVisualEvent:(VisualEvent *)event onStoryPlayerView:storyPlayerView
{
switch (event.eventType) {
case kEventTypeCircle:
[self showCircleEvent:event onStoryPlayerView:storyPlayerView];
break;
case kEventTypeArrow:
[self showArrowDrawingEvent:event onStoryPlayerView:storyPlayerView];
break;
// (...)
}
But switch statements for type checking are bad design, aren't they? According to Uncle Bob they lead to tight coupling and can and should almost always be replaced by polymorphism.
Having read about the "Strategy"-Pattern in Head First Design Patterns, I felt this was a great way to get rid of my switch statement.
So I changed the design like this:
All specialized visual event types are now subclasses of an abstract VisualEvent
class that has a showOnStoryPlayerView:
method.
@interface VisualEvent : NSObject
- (void)showOnStoryPlayerView:(StoryPlayerView *)storyPlayerView; // abstract
Each and every concrete subclass implements a concrete specialized version of this drawing behavior method.
@implementation CircleVisualEvent
- (void)showOnStoryPlayerView:(StoryPlayerView *)storyPlayerView
{
[storyPlayerView drawCircleAtPoint:self.position
color:self.color
lineWidth:self.lineWidth
radius:self.radius];
}
The StoryPlayer
now simply calls the same method on all types of events.
@implementation StoryPlayer
- (void)showVisualEvent:(VisualEvent *)event onStoryPlayerView:storyPlayerView
{
[event showOnStoryPlayerView:storyPlayerView];
}
The result seems to be great: I got rid of the switch statement, and if I ever have to add new types of VisualEvents in the future, I simply create new subclasses of VisualEvent
. And I won't have to change anything in StoryPlayer
.
But of cause this approach violates the MVC pattern since now my model has to know about and depend on my view! Now my controller talks to my model and my model talks to the view calling methods on StoryPlayerView
like drawCircleAtPoint:color:lineWidth:radius:
. But this kind of calls should be controller code not model code, right??
Seems to me like I made things worse.
I'm confused! Am I completely missing the point of the strategy pattern? Is there a better way to get rid of the switch statement without breaking model-view separation?
1 Answer 1
You are trying to merge two levels of functionality into a single class. That is almost guaranteed to cause a mess in your design. Instead, how about this:
Create another set of classes parallel to the Event classes. Each xxVisualEvent
will have a corresponding xxVisualEventRenderer
that encapsulates its rendering/display logic. These classes know about both the Model and the View and are essentially extensions of the Controller.
Create a Mapper class (basically a factory), which takes in a VisualEvent
and creates a VisualEventRenderer
based on the type. Yes, this means a type-based switch, but the class has no logic other than to construct Renderer instances.
Then you can call the Render/Draw method on these Renderer classes instead of on the Event classes. You will retain all the advantages of your approach, such as the loose coupling, etc.
-
I followed your advice, it works and feels good :)Goodsquirrel– Goodsquirrel06/10/2014 18:56:39Commented Jun 10, 2014 at 18:56
-
@Goodsquirrel glad it helped!metacubed– metacubed06/10/2014 20:42:59Commented Jun 10, 2014 at 20:42
Explore related questions
See similar questions with these tags.