- Both peasants and soldiers can eat and talk
- Peasants can move slowly (well walk) while soldiers can move fast
- Peasants cannot fight whereas soldiers can fight
A person well conversant with OOPS would start out by building this design based on a GameCharacter abstract class that will provide the default implementations for the common methods say eat and talk while leaving the implementation of methods such as move and fight to the sub classes.
GameCharacter Class Listing
Public MustInherit Class GameCharacter
Public Sub Eat()
Console.WriteLine("Character is eating")
End Sub
Public Sub Talk()
Console.WriteLine("Character is talking")
End Sub
Public MustOverride Sub Move()
Public MustOverride Sub Fight()
End Class
The Peasant class and the Soldier class can then inherit from the GameCharacter class providing concrete implementations of the Move and the Fight methods
Peasant Class Listing
Public Class Peasant
Inherits GameCharacter
Public Overrides Sub Fight()
Console.WriteLine("Character cannot fight")
End Sub
Public Overrides Sub Move()
Console.WriteLine("Character is moving slowly")
End Sub
End Class
Soldier Class Listing
Public Class Soldier
Inherits GameCharacter
Public Overrides Sub Fight()
Console.WriteLine("Character is Fighting")
End Sub
Public Overrides Sub Move()
Console.WriteLine("Character is moving fast")
End Sub
End Class
Let us now consider the introduction of a new character – the Cavalry man with the following characteristics
- Cavalry man can fight
- Cavalry man moves by riding a horse
Using our existing design, this is fairly easy to accomplish by creating another class – CavalryMan which implements the GameCharacter class and providing appropriate implementations for the Move and Fight methods. For sake of simplicity, I am ignoring the Horse at this point of time since it does not provide any independent behavior of its own.
CavalryMan Class Listing
Public Class CavalryMan
Inherits GameCharacter
Public Overrides Sub Fight()
Console.WriteLine("Character is fighting")
End Sub
Public Overrides Sub Move()
Console.WriteLine("Character is riding a horse")
End Sub
End Class
A quick view of the class diagram shows a fairly decent class hierarchy and the design seems to fit the requirements quite aptly.
If you review the code, you will find that both the Soldier and the CavalryMan provide the same implementation for the Fight method. Although, my example uses a single line of code to provide the appropriate implementation, real world classes will have complex logic that would be repeated in both these classes to provide this implementation. An alternative to this design might inspire a few programmers to suggest that we move the Fight method implementation to the GameCharacter abstract class and override this behavior in the Peasant sub-class. We can then consolidate the implementation at one location without need to rewrite the same implementation in the Soldier and CavalryMan sub-classes.
GameCharacter Class Listing
Public MustInherit Class GameCharacter
Public Sub Eat()
Console.WriteLine("Character is eating")
End Sub
Public Sub Talk()
Console.WriteLine("Character is talking")
End Sub
Public Overridable Sub Fight()
Console.WriteLine("Character is Fighting")
End Sub
Public MustOverride Sub Move()
End Class
Soldier Class Listing
Public Class Soldier
Inherits GameCharacter
Public Overrides Sub Move()
Console.WriteLine("Character is moving fast")
End Sub
End Class
CavalryMan Class Listing
Public Class CavalryMan
Inherits GameCharacter
Public Overrides Sub Move()
Console.WriteLine("Character is riding a horse")
End Sub
End Class
Clearly, the Peasant class requires no change and the original overriding of the Fight method provides its default behavior. The Class diagram is identical to our original class diagram barring the default method implementation of the Fight method and the missing implementations in the Soldier and the CavalryMan class.
Now, if we have to introduce another character – the Mason who exhibits the following characteristics
- Mason cannot fight, a behavior that he shares with the peasant
- Mason moves fast, a behavior that he shares with the soldier
Ah, we have a challenge at hand now. If we were to use our last idea, we could simply provide a default implementation of the Move method as well in the GameCharacter class, allowing the Mason and Soldier to move fast and therefore, requiring no special implementation of these methods in the Soldier and Mason classes. The Peasant and CavalryMan classes already provide their implementation of the Move method. The Fight method, would however pose a challenge of sorts. We would need to implement the behavior in the Mason class to override the default implementation in the GameCharacter class.
GameCharacter Class Listing
Public MustInherit Class GameCharacter
Public Sub Eat()
Console.WriteLine("Character is eating")
End Sub
Public Sub Talk()
Console.WriteLine("Character is talking")
End Sub
Public Overridable Sub Fight()
Console.WriteLine("Character is Fighting")
End Sub
Public Overridable Sub Move()
Console.WriteLine("Character is moving fast")
End Sub
End Class
Soldier Class Listing
Public Class Soldier
Inherits GameCharacter
'Wow no implementation,
'does this mean the GameCharacter is a soldier by default. Hmm!
End Class
Mason Class Listing
Public Class Mason
Inherits GameCharacter
Public Overrides Sub Fight()
Console.WriteLine("Character cannot fight")
End Sub
End Class
Well, looks like we have a working solution to our requirement.
However, this design suffers from certain flaws that are readily visible. One of most obvious flaws is the GameCharacter representing the Soldier class by default. In a truly object oriented design, this could be read as GameCharacter and Soldier class being the same. Extending this statement to the implementing classes implies that
- Peasant is a type of Soldier who walks slowly and does not fight
- Mason is a type of Soldier who walks fast and does not fight
- CavalryMan is a type of Soldier who rides instead of walking
Barring the last one pertaining to the CavalryMan, the other two definitions do not fit our logical definition of these characters.
The OOPS solution to this would be to increase the depth of inheritance by introducing an additional sub-class that can act as a super-type for characters that fight. The Soldier and CavalryMan could then extend from this FightingCharacter class while the Peasant and the Mason could extend from the GameCharacter Class.
GameCharacter Class Listing
Public MustInherit Class GameCharacter
Public Sub Eat()
Console.WriteLine("Character is eating")
End Sub
Public Sub Talk()
Console.WriteLine("Character is talking")
End Sub
Public Overridable Sub Fight()
Console.WriteLine("Character cannot fight")
End Sub
Public MustOverride Sub Move()
End Class
FightingCharacter Class Listing
Public MustInherit Class FightingCharacter
Inherits GameCharacter
Public Overrides Sub Fight()
Console.WriteLine("Character is fighting")
End Sub
End Class
Soldier Class Listing
Public Class Soldier
Inherits FightingCharacter
Public Overrides Sub Move()
Console.WriteLine("Character is moving fast")
End Sub
End Class
CavalryMan Class Listing
Public Class CavalryMan
Inherits FightingCharacter
Public Overrides Sub Move()
Console.WriteLine("Character is riding a horse")
End Sub
End Class
Peasant Class Listing
Public Class Peasant
Inherits GameCharacter
Public Overrides Sub Move()
Console.WriteLine("Character is moving slowly")
End Sub
End Class
Mason Class Listing
Public Class Mason
Inherits GameCharacter
Public Overrides Sub Move()
Console.WriteLine("Character is moving fast")
End Sub
End Class
The default Fight implementation of the GameCharacter class would prevent the character from fighting and therefore, the Peasant and Mason class would not need to provide any implementation for the Fight method. The default Fight implementation of the FightingCharacter class would provide the necessary Fighting ability to the class overriding the implementation of the GameCharacter class. The Soldier and CavalryMan class would not be required to provide any implementation of the Fight method simply inheriting the implementation of the FightingCharacter.
However, we would still need to resolve the Move implementation where classes in different hierarchies – Soldier from the FightingCharacter->GameCharacter hierarchy and Mason from the GameCharacter hierarchy need to provide the same implementation.
This approach would therefore not work out for us. Even complicating the design by creating a FastMovingCharacter class which extends the Gaming character and provides a “is moving fast” implementation of the Move method, then extending the Mason class and trying to extend the Soldier from the same class is not possible. This is primarily because multiple inheritance is not available to us and even using a language permitting this would complicate the application since it won’t be clear about the exact nature of implementation.
This brings us to revealing the second flaw in the design. The use of overriding is a cautious exercise and overriding without understanding its impact might complicate the understanding of the classes and their exact implementation nature.
Well, so what is the best design for this little requirement? Please welcome the Strategy pattern. It helps address requirements of our characters – Peasant, Mason, Soldier and Cavalryman by translating their Move and Fight methods into strategies. Each Strategy is declared within an interface and an unlimited number of implementations are then possible for this strategy. In our example, we can declare …
- A couple of strategies for Fighting – Is Fighting and Cannot Fight.
- Three strategies for Moving – Moving Fast, Moving Slowly, Riding a horse
Each strategy interface provides the methods required for the strategy. Let us consider these interfaces - FightStrategy interface for providing the Fight functionality and the MoveStrategy interface for providing the Move functionality.
FightStrategy Interface Listing
Public Interface FightStrategy
Sub Fight()
End Interface
MoveStrategy Interface Listing
Public Interface MoveStrategy
Sub Move()
End Interface
We can then implement the actual Fight strategies using the RealFighter and the WimpyFella classes where each class implements the FightStrategy providing a unique implementation for the Fight method.
RealFighter Class Listing
Public Class RealFighter
Implements FightStrategy
Public Sub Fight() Implements FightStrategy.Fight
Console.WriteLine("Character is fighting")
End Sub
End Class
WimpyFella Class Listing
Public Class WimpyFella
Implements FightStrategy
Public Sub Fight() Implements FightStrategy.Fight
Console.WriteLine("Character cannot fight")
End Sub
End Class
In the same fashion, let us create the three Move strategies based on our requirement – FastMover, SlowMover and the RidingMover classes that implement the Move method of the MoveStrategy
FastMover Class Listing
Public Class FastMover
Implements MoveStrategy
Public Sub Move() Implements MoveStrategy.Move
Console.WriteLine("Character is moving fast")
End Sub
End Class
SlowMover Class Listing
Public Class SlowMover
Implements MoveStrategy
Public Sub Move() Implements MoveStrategy.Move
Console.WriteLine("Character is moving slowly")
End Sub
End Class
RidingMover Class Listing
Public Class RidingMover
Implements MoveStrategy
Public Sub Move() Implements MoveStrategy.Move
Console.WriteLine("Character is riding a horse")
End Sub
End Class
We can then declare instance variables in the original GameCharacter class to represent the Fight and Move strategies. I generally use the naming convention that prefixes class names with the lower case letter o to represent objects. I will declare two variables – oMoveStrategy and oFightStrategy in my GameCharacter class. The default implementation of the Eat and Talk method can remain unchanged while the Move and Fight methods will delegate the responsibility of using the appropriate strategy to the instance variables. Although, I have left the instance variables at a Protected access scope, you might decide to use a Private scope and restrict the access via get/set methods. For sake of simplicity, I have defined a constructor that requires the two strategies. This will prevent invocation of the object’s Fight and Move methods without setting the two strategies.
GameCharacter Class Listing
Public Class GameCharacter
Protected oFightStrategy As FightStrategy
Protected oMoveStrategy As MoveStrategy
Public Sub New(ByVal oFightStrategy As FightStrategy, ByVal oMoveStrategy As MoveStrategy)
With Me
.oFightStrategy = oFightStrategy
.oMoveStrategy = oMoveStrategy
End With
End Sub
Public Sub Eat()
Console.WriteLine("Character is eating")
End Sub
Public Sub Talk()
Console.WriteLine("Character is talking")
End Sub
Public Sub Fight()
oFightStrategy.Fight()
End Sub
Public Sub Move()
oMoveStrategy.Move()
End Sub
End Class
We can now redefine our characters to extend the Gamecharacter class and all we need to provide is the correct instantiation parameters for the instance of the child class.
Peasant Class Listing
Public Class Peasant
Inherits GameCharacter
Public Sub New()
MyBase.New(New WimpyFella, New SlowMover)
End Sub
End Class
Soldier Class Listing
Public Class Soldier
Inherits GameCharacter
Public Sub New()
MyBase.New(New RealFighter, New FastMover)
End Sub
End Class
CavalryMan Class Listing
Public Class CavalryMan
Inherits GameCharacter
Public Sub New()
MyBase.New(New RealFighter, New RidingMover)
End Sub
End Class
Mason Class Listing
Public Class Mason
Inherits GameCharacter
Public Sub New()
MyBase.New(New WimpyFella, New FastMover)
End Sub
End Class
Well, that’s all there is to it.
To demonstrate the functionality, I built a console application around these classes.
modMain Module Listing
Module modMain
Sub Main()
Dim oGC As GameCharacter
Console.WriteLine("Peasant") : oGC = New Peasant : oGC.Fight() : oGC.Move()
Console.WriteLine("Soldier") : oGC = New Soldier : oGC.Fight() : oGC.Move()
Console.WriteLine("CavalryMan") : oGC = New CavalryMan : oGC.Fight() : oGC.Move()
Console.WriteLine("Mason") : oGC = New Mason : oGC.Fight() : oGC.Move()
End Sub
End Module
And the results of our sample test application
No comments:
Post a Comment