Could someone share the way how this should be designed:
Let's say I have some data model, which is built using Entries
.
Basically, I have one abstract class Entry
(or interface IEntry
- that's not so important for the case) and have several implementations of this class - MovieEntry
, SoundEntry
, FoodEntry
, whatever...
Each of those is a wrapper for some data (url, description, number of calories, etc) and this data is grouped together in each corresponding class.
Now - if I wish to display the data for the entries on the screen (let's say movie posters and annotations for the MovieEntry
) - how should I design that?
Obviously I could provide another interface / abstract class and call it DrawableEntry
(and it would inherit Sprite
) and then build a bunch of classes like DrawableMovieEntry
and DrawableSoundEntry
which could look like:
class DrawableMovieEntry extends DrawableEntry { // which also extends 'Sprite'
private movieEntry:MovieEntry;
public override function draw(backend:*) {
// Draw everything using the 'm开发者_开发技巧ovieEntry' reference
// stored.
};
But this seems to be kind of an overkill for a small application.
Another approach is to make the MovieEntry
, SoundEntry
, ... extend sprite and provide the drawing implementations themselves - but this is obviously bad, because data becomes strongly coupled with it's visualization routines.
So - how should this be done? Maybe MVC approach has something to offer for this case?
Your use case seems to be the perfect example for the Strategy pattern or the Command pattern.
Strategy being the simpler one, here is an example:
Create an IDrawStrategy interface like this:
package {
public interface IDrawStrategy {
function draw( obj:Object ) : void;
}
}
Implement several DrawStrategies:
package {
public class SoundEntryDrawStrategy implements IDrawStrategy {
public function draw (obj:Object) : void {
// cast obj to SoundEntry and do all the drawing necessary,
// or fail if obj is null or not a SoundEntry
}
}
}
package {
public class MovieEntryDrawStrategy implements IDrawStrategy {
public function draw (obj:Object) : void {
// cast obj to MovieEntry and do all the drawing necessary
// or fail if obj is null or not a MovieEntry
}
}
}
etc.
Then add a new member to your base Entry class:
private var _drawStrategy:IDrawStrategy;
and create a setter:
public function set drawStrategy ( strat:IDrawStrategy ) : void {
_drawStrategy = strat;
}
and a draw method:
public function draw () : void {
_drawStrategy.draw( this );
}
You can now assign and execute the fitting strategies to each of your entries:
var mov:MovieEntry = new MovieEntry();
mov.drawStrategy = new MovieEntryDrawStrategy();
mov.draw();
BTW the Sprite you draw the information in can, but doesn't have to, be a member of the DrawStrategy class, but if you wanted to add a clear() method later, it would be better to keep a reference ;).
The entries you build your data model with are, among others, referred to as value objects (VO) or data value objects (DVO). To answer your last question first, I'd never have a VO extend something other than a base VO class, so don't extend Sprite, you'll regret it later.
Over to the hierarchy. You're extending the abstract class Entry
to create concrete subclasses, but since you also mention a possible interface, I'm not sure you should use extend. Only use a common base class if your value objects actually share common properties. If every entry has a title
property, fine, put that one in Entry
and subclass it. If your abstract would be empty, I'd recommend using a marker (=empty) interface instead.
I have a common marker interface for value objects, that have more specific subinterfaces to add features like xml parsing or composition. Once you start using interfaces for this, it's easy to enhance.
Then the displaying. There's not one right answer to this one, the more because your example is still pretty broad. But I'd pass the VO to the object as a whole, through a method that states that it's going to store the VO and redraw itself.
interface IEntryDisplay {
redrawWithEntry(entry:IEntry):void;
}
Use the IEntry interface to pass the object as a whole. In your implementation, use an if cascade with is Type
conditions to do the drawing.
public function redrawWithEntry(entry:IEntry):void {
this.entry = entry;
if (entry is MovieEntry) {
title.text = MovieEntry(entry).title;
} else if (entry is SoundEntry) {
title.text = "(Sound) "+SoundEntry(entry).fileName;
}
}
If you decide to use a base class for the Entry hierarchy, use that one instead of the interface. You want your methods asking for the value object type that is as close to the needed object as neccessary.
Because you store the entry in your display class, it's easy to pass along some time later when you click the display or when you want to have it do something else.
Does this help?
精彩评论