User:Josh Oosterman/Design Study
(4 intermediate revisions by one user not shown) | |||
Line 54: | Line 54: | ||
'''Animated Object Heirachy''' | '''Animated Object Heirachy''' | ||
− | The [[inheritance]] tree of ''AnimatedObject'' | + | The [[inheritance]] tree of ''AnimatedObject'' is shown in the above UML diagram. The two subclasses of the [[Abstract_class|abstract]] ''AnimatedObject'' are ''Prop'' and ''Figure''. A ''Prop'' represents a single solid object, such as a soccer ball (as seen in the screenshot above). Props have only a ''Sprite'' (discussed later) and a position. The ''Figure'' subclass is more complex, and is used to represent posable composite objects such as humans or animals. ''Figure'' is further explained below. ''AnimatedObject'' also uses a ''Handle'' object, accessible through its ''MainHandle'' property. The syntax "+ MainHandle { get; }" on the UML diagram represents a .NET property, equivalent to a [[Getters_and_setters|getter method]]. |
− | A ''Handle'' is used to position or pose an ''AnimatedObject''. Handles are rendered as red or blue squares, as seen in the screenshot above. The ''MainHandle'' handle is used to position the ''AnimatedObject'' within the scene. The ''Figure'' class has a further collection of handles, which can be used to move arms and legs, for example. The ''Handle'' class defines a delegate type and [[Observer|event]] called ''MoveCallback'' that is called when a handle is moved. The ''AnimatedObject'' can subscribe to this event and react when the handle is moved. | + | A ''Handle'' is used to position or pose an ''AnimatedObject'', by dragging it on the GUI. Handles are rendered as red or blue squares, as seen in the screenshot above. The ''MainHandle'' handle is used to position the ''AnimatedObject'' within the scene. The ''Figure'' class has a further collection of handles, which can be used to move arms and legs, for example. The ''Handle'' class defines a delegate type and [[Observer|event]] called ''MoveCallback'' that is called when a handle is moved. The ''AnimatedObject'' can subscribe to this event and react when the handle is moved. The ''Handle''s are tied to GUI objects, which represents an interesting design conflict with MVC (discussed later). |
− | Although | + | Although is not fully implemented at the moment, eventually the ''AnimatedObject''s would store several instances of their state, one for each key frame in the animation. Since the first design, I have put a lot of thought into this. The way this would work is as follows: |
+ | |||
+ | The way animation works is by having multiple frames. Certain frames in the animation are marked as ''keyframes'', and the rest of the frames are calculated by interpolating between keyframes. For example, if in frame 1 (a keyframe) the figure is standing upright, and in frame 10 (a keyframe) he is on his side, the position of the figure frame 5 will be half way between, calculated by averaging the position from keyframes 1 and 10. The collection of handle positions is what fully describes the pose and position of a figure, at a particular point in time. In order to store keyframes, the figures can store a map of frame numbers to [[Memento]] objects, where the Memento objects store a map of Handles to positions. When the figure is told to update to a frame between keyframes, an InterpolationStrategy would be used to calculate the intermediate Handle positions. The keyframes would be ''AnimatedObject'' specific, and not controlled by the scene. This is not implemented, but will fit seamlessly into the existing code. | ||
'''Figures''' | '''Figures''' | ||
− | Now we look at the ''Figure'' class. The subclasses ''Man'' and ''Snake'' do not need much explanation. They currently only define a constructor, which constructs the ''Limb''s and ''Joint''s in the body. The [[Factory_Method|Factory]] pattern was not used here, as it is feasible these subclasses may have other special behaviours. The ''Figure'' class does have a [[strategy]] reference to an instance of ''LimbAnimationStrategy'', which is used to calculate limb positions and angles from a handle position. This follows the strategy pattern. ''NaiveLimbStrategy'' and ''InverseKinematicStrategy'' are the current options. For an explanation of Inverse Kinemeatics, see [[Oliver_Cardwell/Project|Oliver's design study]]. | + | Now we look at the ''Figure'' class. The subclasses ''Man'' and ''Snake'' do not need much explanation. They currently only define a constructor, which constructs the ''Limb''s and ''Joint''s in the body. The [[Factory_Method|Factory]] pattern was not used here, as it is feasible these subclasses may have other special behaviours. The ''Figure'' class does have a [[strategy]] reference to an instance of ''LimbAnimationStrategy'', which is used to calculate limb positions and angles from a handle position. This follows the strategy pattern. ''NaiveLimbStrategy'' and ''InverseKinematicStrategy'' are the current options. For an explanation of Inverse Kinemeatics, see [[Oliver_Cardwell/Project|Oliver's design study]]. The reason the strategy is attached to the Figure, and not the Limb, is explained later, in the design pattern section . |
+ | |||
+ | The ''Figure'' has a root ''BodyPart''. The ''BodyPart'' class is [[Abstract_class|abstract]] and has two concrete implementations. An ''Extremity'' is one of these, which has a ''Sprite'', and is used for the hands, feet and heads of figures. A ''Limb'' is more complex as it can have children (for example, when modelling the human body the lower arm could be seen as a child of the upper arm, and the hand (an extremity) can be seen as a child of the lower arm). A ''Limb'' has a length, and a ''Pen'' which are used to render it (in a stick figure fashion). Note that in the UML, PEN is in capitals as it is a static object recycled between instances. Importantly, ''Limb'' also has a collection of connector ''Joint'' objects. The ''Joint'' objects are used to define the children of a ''Limb'', and are designed to [[Model_the_real_world|model]] real joints in a human body. An example of a ''Joint'' would be an elbow. The ''Joint'' can have an angle, which must be within certain limits (if they are specified). The syntax "double?" used in the above diagram mean a nullable double, so that null can be passed in instead of specifying limits. The ''Joint'' class stores has a 'to' field, which in the case of an elbow would be the lower arm. It also has an ''Angle'' property, which is the angle at that joint (between the parent and child body parts). | ||
− | + | When drawing or positioning a ''Figure'', the operations happen recursively. For example, if the torso is told to draw, it will tell each of it's joints to draw, which will in turn cause all limbs hanging off the torso to be drawn. | |
== Other == | == Other == | ||
Line 76: | Line 80: | ||
The ''Point'' and ''Vector'' classes next to it are geometry utility classes I have written, which are worth mentioning. There was an interesting design conflict here. On one hand, writing a new Point and Vector class could be seen to be reinventing the wheel (or repeating myself), as there are already solid geometry classes in the .NET framework. The problem with the existing point class that it does not distinguish between a point and a vector, and is used for both. Distinguishing does have a significant benefit, as the type safety helps you write valid mathematical expressions (point - point = vector, vector - vector = vector, point + point is illegal, point * scalar is illegal). It effectively gives me compile time warnings when I have the wrong math, something I found very useful in my raytracer. | The ''Point'' and ''Vector'' classes next to it are geometry utility classes I have written, which are worth mentioning. There was an interesting design conflict here. On one hand, writing a new Point and Vector class could be seen to be reinventing the wheel (or repeating myself), as there are already solid geometry classes in the .NET framework. The problem with the existing point class that it does not distinguish between a point and a vector, and is used for both. Distinguishing does have a significant benefit, as the type safety helps you write valid mathematical expressions (point - point = vector, vector - vector = vector, point + point is illegal, point * scalar is illegal). It effectively gives me compile time warnings when I have the wrong math, something I found very useful in my raytracer. | ||
− | The classes ''MainForm'' | + | The classes ''MainForm'' and ''Program'' (neither pictured) are for the .NET generated GUI for the application. This is not an important part of the OO design and is mostly ignored in the design study. In a previous version, this was included, and it should be noted that the MainForm stunk of [[Large_class_smell|Large Class Smell]], with >10 instance variables. This is an unfortunate artifact of the way C# generates forms. Each widget on the form becomes an object instance, thus unavoidably bloating the class. Because the code is automatically generated, and hidden in a secondary file, it does not negatively impact development. |
'''Style & Encapsulation''' | '''Style & Encapsulation''' | ||
Line 82: | Line 86: | ||
Not only the OO design aspects were considered in the implementation. I have used C# coding conventions in the implementation, which are indirectly important as they help the communication and understanding of the design. Class and method names start with an upper case letter, variables start with a lower case letter, and member variables (fields) all have the prefix "m_". | Not only the OO design aspects were considered in the implementation. I have used C# coding conventions in the implementation, which are indirectly important as they help the communication and understanding of the design. Class and method names start with an upper case letter, variables start with a lower case letter, and member variables (fields) all have the prefix "m_". | ||
− | The code is barely commented at all. This is not because I am lazy - It is because I genuinely believe there is not a lot that [[Comments_smell| | + | The code is barely commented at all. This is not because I am lazy - It is because I genuinely believe there is not a lot that needs commenting [[Comments_smell|(see Comment Smell)]]. As long as the design is understood, there should be no nasty surprises at the code level. |
Although I'm a slow convert to the idea, [[Object_Encapsulation|Object Encapsulation]] has been used throughout the design. Public fields are avoided altogether, and are instead wrapped by public Properties [[Getters_and_setters|(getters and setters)]] if required. Fields are marked as protected so that the subclasses can get at them. The small remaining 'fear' of object encapsulation I have is the nuisance of exposing your object internals over the API boundary, which is not an issue in this personal project. Object encapsulation is also incredibly useful for extending my decorator and ''AnimatedObject'' heirachies. | Although I'm a slow convert to the idea, [[Object_Encapsulation|Object Encapsulation]] has been used throughout the design. Public fields are avoided altogether, and are instead wrapped by public Properties [[Getters_and_setters|(getters and setters)]] if required. Fields are marked as protected so that the subclasses can get at them. The small remaining 'fear' of object encapsulation I have is the nuisance of exposing your object internals over the API boundary, which is not an issue in this personal project. Object encapsulation is also incredibly useful for extending my decorator and ''AnimatedObject'' heirachies. | ||
Line 108: | Line 112: | ||
'''Observer''' | '''Observer''' | ||
− | The observer pattern was ''kind of'' used with the ''Handle'' class. This is so that the figures and objects can adjust themself as the handles are moved. This was actually implemented using C# events, which is a language supported Observer system. The implication of this is that I don't have to use MVC, as the domain of animation and the presentation layer/view are conceptually so intimately linked. | + | The observer pattern was ''kind of'' used with the ''Handle'' class. This is so that the figures and objects can adjust themself as the handles are moved. This was actually implemented using C# events, which is a language supported Observer system. The implication of this is that I don't have to use MVC, as the domain of animation and the presentation layer/view are conceptually so intimately linked. This is further discussed later. |
* Subject = ''Handle'' | * Subject = ''Handle'' | ||
* Observer = ''Figure'', ''Prop'' | * Observer = ''Figure'', ''Prop'' | ||
Line 153: | Line 157: | ||
This was resolved by changing Joint to internally redirect Draw and MoveTo calls to child limbs. The Joint class itself is now much more important as a class, as it imposes joint angle limits (i.e. elbows only move 180 degrees). | This was resolved by changing Joint to internally redirect Draw and MoveTo calls to child limbs. The Joint class itself is now much more important as a class, as it imposes joint angle limits (i.e. elbows only move 180 degrees). | ||
+ | |||
+ | '''Bob Martin's SOLID principles''' | ||
+ | |||
+ | Since I was already disowning the advice of Mr. Riel, I thought it would be good to try and stick with one set of rules... | ||
+ | |||
+ | The [[Single_responsibility_principle|Single responsibility principle]] was something I took fairly seriously. Beside the 1 case mentioned below (See MVC conflict), I feel I followed this. This was made easier based on my real world modelling, which helped me draw good boundaries around class behaviours. | ||
+ | |||
+ | The [[Open_closed_principle|Open closed principle]] is implied by my original requirements (I want it to be easily extendible). The method of extension in the system is via inheritance, as there are Stable abstractions everywhere. An example of extension points is the various animation strategies, figure types, prop types, and background decorators. | ||
+ | |||
+ | My design conforms to the [[Interface_segregation_principle|Interface segregation principle]]. The object interfaces are all very thin, another side effect of having Stable abstractions. The largest class interface is that of Vector/Point, which only support the relevant mathematical operations, and could hardly be called a [[Fat_interfaces|fat interface]]. | ||
== Maxims Violated == | == Maxims Violated == | ||
Line 160: | Line 174: | ||
I decided not to strictly follow MVC in my design. For example, '''the BodyParts''' are responsible for both modelling the state of a body, and drawing themselves. This is another area of design conflict. | I decided not to strictly follow MVC in my design. For example, '''the BodyParts''' are responsible for both modelling the state of a body, and drawing themselves. This is another area of design conflict. | ||
I feel that because the domain I am modelling is animation, where the domain objects are graphical entities, the separation of model and presentation don't make much sense. For example, I could break up the figure class to only store joint angles, and have the View class render the stick figure, but as they're both factors of Animation I'd like to [[Keep_related_data_and_behavior_in_one_place]]. I ended up avoiding MVC for simplicity, as it seemed like overkill for my domain. | I feel that because the domain I am modelling is animation, where the domain objects are graphical entities, the separation of model and presentation don't make much sense. For example, I could break up the figure class to only store joint angles, and have the View class render the stick figure, but as they're both factors of Animation I'd like to [[Keep_related_data_and_behavior_in_one_place]]. I ended up avoiding MVC for simplicity, as it seemed like overkill for my domain. | ||
+ | |||
+ | '''Other''' | ||
+ | |||
+ | Due to the choice to go with Object Encapsulation, I've thrown out the possibility of sticking with some of Riel's heuristics. For example, [[Hide_data_within_its_class|Hide data within its class]] and [[Avoid_protected_data|Avoid protected data]] | ||
== Code == | == Code == | ||
+ | |||
The system was implemeted in C#. To build and run is trivial -- Open Stickman.sln in Visual Studio and build. You'll need Windows and VS2008 or later. | The system was implemeted in C#. To build and run is trivial -- Open Stickman.sln in Visual Studio and build. You'll need Windows and VS2008 or later. | ||
Download Here: https://docs.google.com/leaf?id=0B0tn4Rp2yTO_YjgyODE1ZDgtYTEyOS00ZjY3LWEzMjktMmVlNjFlYmFhY2U4&hl=en&authkey=CJmTig8 | Download Here: https://docs.google.com/leaf?id=0B0tn4Rp2yTO_YjgyODE1ZDgtYTEyOS00ZjY3LWEzMjktMmVlNjFlYmFhY2U4&hl=en&authkey=CJmTig8 |
Latest revision as of 02:45, 1 October 2010
Contents |
Introduction
Overview
I wanted to create a tool to animate scenes, and humanoid characters. Eventually I want be able to create complex animations which can be loaded into a 2d game. This would take too long for a 427 assessment, so I've simplified the problem to having an interactive stickmen tool. The extensibility of the design of the system is critical so I can extend it in the future.
The system was fully designed and implemented for COSC427, and was not based on previous work. Since everyone looked at my design study I have made the following changes:
- Better explanation of project & concept of Handles
- Minor OO improvements
- Updated UML and split it into parts
- Started on animation code. This is in the source, but is excluded from my design study for size.
- More thorough discussion of design forces & maxims
A screenshot of the current version is shown below.
In order to understand some of the classes I'll explain the method of interaction. A scene consists of several figures (such as a person, or snake), and props (such as a soccer ball) which can be added using the menu on the right. The background and weather effects can be customised, using the menu also. Each figure has multiple 'handles', which can be clicked and dragged to manipulate the figure. The primary handle (red in the above screenshot) is used to move the figure around the scene. The other handles (blue in the above screenshot), are used to pose the figure. For example, dragging a red handle upwards would cause a figure to lift their arm.
Often, animation systems support a full range of transformations such as translation, rotation, scaling etc. This system deliberately constrains the types of movement allowed. That is, at each joint in a figure, there is only a rotation property. Translation (i.e. separating limbs) and scaling (i.e. leg changing size) are not supported, as these are not sensible actions for stick figures to take. The figure's body model is designed to mimic a real person, and support joint angle limits, etc.
System Goals
Within my 427 Project
- Customisable backgrounds
- Posable figures (Such as Stickmen)
- Props
- Extensible - so I can add the below features eventually
Outside the scope of this project, but to be added in the future:
- Interpolation between Keyframes
- Advanced Inverse Kinematics & Constraints on Joints
- Export angles & positions as animation file to be used in games
Tour of the Design
This section explains the system, with UML diagrams. Note that throughout, the format "m_fieldName" is used for private fields, this is C# convention.
Scene
At the top left is the IDrawable interface. This defines just one method, but is used throughout the design. The Draw method takes a reference to a Graphics context.
Above we cal also see the Scene class. The scene class is just that - a scene in an animation. The application currently lets you use only one scene object, but it is not a singleton as it is feasible multiple scenes could be supported in the future. The Scene class consists of a background, and a collection of AnimatedObjects. The Scene controls the animation, where the NumFrames property corresponds to the total number of frames in the animation. The scene can only be updated (Using UpdateToFrame) to any frame between 0 and this amount, otherwise an exception is thrown.
The Background of a scene can be a SimpleBackground, which just displays a static image. It can optionally be decorated (see Decorator pattern) with weather effects using the LightningBackground or RainBackground classes. These both inherit from the intermediate abstract class RandomBackgroundDecorator, which contains a random generator, used for the random elements in these backgrounds. There was a design conflict in choosing to use the decorator pattern here. The alternative idea I had was to support background 'Layers'. In this case, you could add an ImageBackgroundLayer, or RainBackgroundLayer, or whatever each to a layer collection. I decided to go with the decorator pattern as it is more flexible. For example, if I wanted to allow Earthquakes as a background effect, I could do this with a decorator and not from discrete layers. The decorator approach is more flexible, and could easily be extended to emulate the layers idea anyway.
After some thought, I've decided that the ordering of AnimatedObjects in a Scene IS important, so instead of using a set I used a List<AnimatedObject>. Currently they are drawn in the order they were added, but using a List<> collection I could easily make the depth order customisable in the future.
Animated Objects
Animated Object Heirachy
The inheritance tree of AnimatedObject is shown in the above UML diagram. The two subclasses of the abstract AnimatedObject are Prop and Figure. A Prop represents a single solid object, such as a soccer ball (as seen in the screenshot above). Props have only a Sprite (discussed later) and a position. The Figure subclass is more complex, and is used to represent posable composite objects such as humans or animals. Figure is further explained below. AnimatedObject also uses a Handle object, accessible through its MainHandle property. The syntax "+ MainHandle { get; }" on the UML diagram represents a .NET property, equivalent to a getter method.
A Handle is used to position or pose an AnimatedObject, by dragging it on the GUI. Handles are rendered as red or blue squares, as seen in the screenshot above. The MainHandle handle is used to position the AnimatedObject within the scene. The Figure class has a further collection of handles, which can be used to move arms and legs, for example. The Handle class defines a delegate type and event called MoveCallback that is called when a handle is moved. The AnimatedObject can subscribe to this event and react when the handle is moved. The Handles are tied to GUI objects, which represents an interesting design conflict with MVC (discussed later).
Although is not fully implemented at the moment, eventually the AnimatedObjects would store several instances of their state, one for each key frame in the animation. Since the first design, I have put a lot of thought into this. The way this would work is as follows:
The way animation works is by having multiple frames. Certain frames in the animation are marked as keyframes, and the rest of the frames are calculated by interpolating between keyframes. For example, if in frame 1 (a keyframe) the figure is standing upright, and in frame 10 (a keyframe) he is on his side, the position of the figure frame 5 will be half way between, calculated by averaging the position from keyframes 1 and 10. The collection of handle positions is what fully describes the pose and position of a figure, at a particular point in time. In order to store keyframes, the figures can store a map of frame numbers to Memento objects, where the Memento objects store a map of Handles to positions. When the figure is told to update to a frame between keyframes, an InterpolationStrategy would be used to calculate the intermediate Handle positions. The keyframes would be AnimatedObject specific, and not controlled by the scene. This is not implemented, but will fit seamlessly into the existing code.
Figures
Now we look at the Figure class. The subclasses Man and Snake do not need much explanation. They currently only define a constructor, which constructs the Limbs and Joints in the body. The Factory pattern was not used here, as it is feasible these subclasses may have other special behaviours. The Figure class does have a strategy reference to an instance of LimbAnimationStrategy, which is used to calculate limb positions and angles from a handle position. This follows the strategy pattern. NaiveLimbStrategy and InverseKinematicStrategy are the current options. For an explanation of Inverse Kinemeatics, see Oliver's design study. The reason the strategy is attached to the Figure, and not the Limb, is explained later, in the design pattern section .
The Figure has a root BodyPart. The BodyPart class is abstract and has two concrete implementations. An Extremity is one of these, which has a Sprite, and is used for the hands, feet and heads of figures. A Limb is more complex as it can have children (for example, when modelling the human body the lower arm could be seen as a child of the upper arm, and the hand (an extremity) can be seen as a child of the lower arm). A Limb has a length, and a Pen which are used to render it (in a stick figure fashion). Note that in the UML, PEN is in capitals as it is a static object recycled between instances. Importantly, Limb also has a collection of connector Joint objects. The Joint objects are used to define the children of a Limb, and are designed to model real joints in a human body. An example of a Joint would be an elbow. The Joint can have an angle, which must be within certain limits (if they are specified). The syntax "double?" used in the above diagram mean a nullable double, so that null can be passed in instead of specifying limits. The Joint class stores has a 'to' field, which in the case of an elbow would be the lower arm. It also has an Angle property, which is the angle at that joint (between the parent and child body parts).
When drawing or positioning a Figure, the operations happen recursively. For example, if the torso is told to draw, it will tell each of it's joints to draw, which will in turn cause all limbs hanging off the torso to be drawn.
Other
Util
Finally, the Sprite class can be seen here. A Sprite is a static image that can be used in the animation. For example, the Extremities or Backgrounds. Internally, it just wraps a .NET Bitmap object, but the interface does not depend on this. Sprites can be created by either loading an image file, or creating an empty image of a specified size and colour. The benefit of the Sprite class is to provide resource sharing, using the flyweight design pattern. I defend that this was worthwhile doing, as it is so lightweight, and I eventually will GNI (vs YAGNI).
The Point and Vector classes next to it are geometry utility classes I have written, which are worth mentioning. There was an interesting design conflict here. On one hand, writing a new Point and Vector class could be seen to be reinventing the wheel (or repeating myself), as there are already solid geometry classes in the .NET framework. The problem with the existing point class that it does not distinguish between a point and a vector, and is used for both. Distinguishing does have a significant benefit, as the type safety helps you write valid mathematical expressions (point - point = vector, vector - vector = vector, point + point is illegal, point * scalar is illegal). It effectively gives me compile time warnings when I have the wrong math, something I found very useful in my raytracer.
The classes MainForm and Program (neither pictured) are for the .NET generated GUI for the application. This is not an important part of the OO design and is mostly ignored in the design study. In a previous version, this was included, and it should be noted that the MainForm stunk of Large Class Smell, with >10 instance variables. This is an unfortunate artifact of the way C# generates forms. Each widget on the form becomes an object instance, thus unavoidably bloating the class. Because the code is automatically generated, and hidden in a secondary file, it does not negatively impact development.
Style & Encapsulation
Not only the OO design aspects were considered in the implementation. I have used C# coding conventions in the implementation, which are indirectly important as they help the communication and understanding of the design. Class and method names start with an upper case letter, variables start with a lower case letter, and member variables (fields) all have the prefix "m_".
The code is barely commented at all. This is not because I am lazy - It is because I genuinely believe there is not a lot that needs commenting (see Comment Smell). As long as the design is understood, there should be no nasty surprises at the code level.
Although I'm a slow convert to the idea, Object Encapsulation has been used throughout the design. Public fields are avoided altogether, and are instead wrapped by public Properties (getters and setters) if required. Fields are marked as protected so that the subclasses can get at them. The small remaining 'fear' of object encapsulation I have is the nuisance of exposing your object internals over the API boundary, which is not an issue in this personal project. Object encapsulation is also incredibly useful for extending my decorator and AnimatedObject heirachies.
Design Patterns
Strategy
The strategy pattern was used for limb animation. This is because I may want to add new behaviors in the future, once I actually understand Inverse Kinematics... Currently only the naive strategy is implemented fully. There is a reason this was attached to the figure, rather than each limb. A limb is actually one contiguous body part (i.e. upper arm, lower arm etc). When you drag a handle, the inverse kinematic calculation actually works on a string of limbs. Picture grabbing someones hand and dragging them along -- the animation strategy here affects the upper arm, lower arm, and perhaps even torso and legs depending how far you drag them.
- Context = Figure
- Strategy = LimbAnimationStrategy
- ConcreteStrategy = NaiveLimbStrategy
- ConcreteStrategy = InverseKinematicStrategy
Decorator
The Background classes participate in the decorator pattern. This is because that most of the kind of behaviors you might want in a background should be stackable, and I can't anticipate them all up front. The rationale for this was discussed earlier.
- Component = Background
- ConcreteComponent = SimpleBackground
- Decorator = BackgroundDecorator
- ConcreteDecorator = RainBackground, LightningBackground
Observer
The observer pattern was kind of used with the Handle class. This is so that the figures and objects can adjust themself as the handles are moved. This was actually implemented using C# events, which is a language supported Observer system. The implication of this is that I don't have to use MVC, as the domain of animation and the presentation layer/view are conceptually so intimately linked. This is further discussed later.
- Subject = Handle
- Observer = Figure, Prop
Flyweight
A very lightweight version of flyweight was used with the Sprite class so that it could avoid loading many instances of the same image into memory. Sprite has a static method to load a Sprite, which checks its pool and returns a mutable reference to the shared object.
- Flyweight, FlyweightFactory = Sprite.
Interesting Maxims Followed
Big Design Up Front
I'm generally pretty opposed to this, but because this system was so small and simple I designed it almost entirely before I implemented it. This worked well for me. The only exception here was the animation details (versus the posing details). I have since thought this out, and have a design pretty much ready to implement.
Model the real world
This is a rule that I seem to have problem using generally, but in this case it worked well. Some concepts in the design (InverseKinematicsStrategy and Handle) do not have real world equivalents. The important concepts of a Scene, Prop and Figure all model their real world equivalents. I was initially having trouble designing the body modelling classes, but drew inspiration from the real world concepts of Limbs and Joints.
Acyclic Depedencies Principle
The implemented system here is divided into 5 packages, which have acyclic dependencies.
- The Stickman.GraphicUtil package contains Sprite, Vector and Point classes. This depends on no other packages.
- The Stickman.AnimatedObjects package contains the classes seen in the middle diagram above, specifically the figure classes (Joint, Limb, Extremity etc) are included in the subpackage Stickman.AnimatedObjects.Figures. This package heavily relies on the GraphicUtil class.
- The Stickman package contains the Scene class, which depends on Stickman.GraphicUtil, and Stickman.AnimatedObjects.
- The package Stickman.GUI contains the graphical user interface for the app, and depends on all of the above. The communication channel back to the figure from the UI is using the delegate/event OnMoved on Handle, so this is only very weakly coupled back.
The logical separation here is fairly obvious, and will pay off by forcing me to think things through if I ever try to add a new class which doesn't have a logical 'home'.
The packages in the design are acyclic. depends on Stickman.AnimatedObjects, Stickman.AnimatedObjects.Figures and Stickman, which in turn use the util package Stickman.GraphicUtil.
Encapsulate That Which Varies
Implementation details are hidden behind the interface, and object encapsulation is used. All fields in the system should be hidden with protected visibility.
No Concrete Base Classes
All base classes in the design (AnimatedObject, Figure, Background and BodyPart) are sensible abstractions which can not be directly instantiated. On the whole I do not agree with this rule, but it seems to be fitting here.
Law of Demeter
The Law of Demeter was previously broken in my design. While I think it is a stupid rule, it brought to my attention a poor part of my design. The Joint class didn't really have any behavior, instead, it acts as a connector which holds a reference to another BodyPart. This means we had ugly things like the MoveTo() method in Limb calling "j.To.MoveTo()", a long ugly chained call.
This was resolved by changing Joint to internally redirect Draw and MoveTo calls to child limbs. The Joint class itself is now much more important as a class, as it imposes joint angle limits (i.e. elbows only move 180 degrees).
Bob Martin's SOLID principles
Since I was already disowning the advice of Mr. Riel, I thought it would be good to try and stick with one set of rules...
The Single responsibility principle was something I took fairly seriously. Beside the 1 case mentioned below (See MVC conflict), I feel I followed this. This was made easier based on my real world modelling, which helped me draw good boundaries around class behaviours.
The Open closed principle is implied by my original requirements (I want it to be easily extendible). The method of extension in the system is via inheritance, as there are Stable abstractions everywhere. An example of extension points is the various animation strategies, figure types, prop types, and background decorators.
My design conforms to the Interface segregation principle. The object interfaces are all very thin, another side effect of having Stable abstractions. The largest class interface is that of Vector/Point, which only support the relevant mathematical operations, and could hardly be called a fat interface.
Maxims Violated
Model View Controller
I decided not to strictly follow MVC in my design. For example, the BodyParts are responsible for both modelling the state of a body, and drawing themselves. This is another area of design conflict. I feel that because the domain I am modelling is animation, where the domain objects are graphical entities, the separation of model and presentation don't make much sense. For example, I could break up the figure class to only store joint angles, and have the View class render the stick figure, but as they're both factors of Animation I'd like to Keep_related_data_and_behavior_in_one_place. I ended up avoiding MVC for simplicity, as it seemed like overkill for my domain.
Other
Due to the choice to go with Object Encapsulation, I've thrown out the possibility of sticking with some of Riel's heuristics. For example, Hide data within its class and Avoid protected data
Code
The system was implemeted in C#. To build and run is trivial -- Open Stickman.sln in Visual Studio and build. You'll need Windows and VS2008 or later. Download Here: https://docs.google.com/leaf?id=0B0tn4Rp2yTO_YjgyODE1ZDgtYTEyOS00ZjY3LWEzMjktMmVlNjFlYmFhY2U4&hl=en&authkey=CJmTig8