Wrapping an object with another in VB 2013 - vb.net

I am trying to wrap an instance of a created class with another that share the same super class in Visual Basic 2013. I'm new to VB and been struggling with this.
This is for a Java to VB conversion..
Code example of what I have written:
' Abstract product creator Class
Public MustInherit Class PizzaMaker
' Abstract Method to create a pizza
Public MustOverride Function createPizza(ByRef size As Integer, ByRef crust As String) As Pizza
Public Function orderPizza(ByRef size, ByRef crust) As Pizza
Dim pizza As Pizza
pizza = createPizza(size, crust)
Return pizza
End Function
End Class
' Concrete factory Class
Public Class MargheritaMaker
Inherits PizzaMaker
' Override abstrat method in superclass
Public Overrides Function createPizza(ByRef size As Integer, ByRef crust As String) As Pizza
Dim pizza As New MargheritaPizza(size, crust)
Return pizza
End Function
End Class
' Abstract component product Class
Public MustInherit Class Pizza
' Consatant variables used for pizza size
Public Const SMALL As Integer = 1
Public Const MEDIUM As Int16 = 2
Public Const LARGE As Int16 = 3
Public Const EXTRA_LARGE As Int16 = 4
' Crust type of pizza
Private crustType As String
' Size of the pizza
Private size As Int16
' Description of the pizza
Public description As String = "Pizza"
' Abstract method to return the cost of the pizza
Public MustOverride Function getCost() As Double
' Returns size description
Public Function getSizeDescription() As String
Dim desc As String
' Determin pizza size and return String description
If (size = 1) Then
desc = "Small"
ElseIf (size = 2) Then
desc = "Medium"
ElseIf (size = 3) Then
desc = "Large"
Else
desc = "Extra Large"
End If
Return desc
End Function
Public Function getCrust() As String
Return crustType
End Function
' Sets the pizza crust type
Public Sub setCrust(ByRef crust)
crustType = crust
End Sub
' Returns the pizza size
Public Function getSize() As Integer
Return size
End Function
' Set the size of our Pizza
Public Sub setSize(ByVal i)
size = i
End Sub
' Returns the String description of the pizza
Public Function getDescription() As String
Return getSizeDescription() + " " + crustType + " " + description
End Function
End Class
' Concrete component product Class defining a Margherita Pizza
Public Class MargheritaPizza
Inherits Pizza
'Dim cost
' Constructor set's the Pizza size, crust type & description
Sub New(ByRef size As Integer, ByRef crust As String)
setSize(size)
setCrust(crust)
description = "Margherita Pizza"
End Sub
' Returns the Pizza base cost based on it's size
Public Overrides Function getCost() As Double
Dim cost As Double
If (getSize() = Pizza.SMALL) Then
'Console.Write("in if" & vbNewLine)
cost = 9.5
ElseIf (getSize() = Pizza.MEDIUM) Then
cost = 10.5
ElseIf (getSize() = Pizza.LARGE) Then
cost = 11.5
ElseIf (getSize() = Pizza.EXTRA_LARGE) Then
cost = 12.5
End If
'Console.Write("in if" * vbNewLine)
Return cost
End Function
End Class
' Abstract component product decorator Class
Public MustInherit Class PizzaDecorator
Inherits Pizza
' Abstract method that returns decorator description
Public MustOverride Overloads Function getDescription()
End Class
' Concrete component product decorator Class (used as Object wrapper)
Public Class Cheese
Inherits PizzaDecorator
Dim pizza ' As Pizza
' Check that the construtor paramaters are correct!!! Also check scope of variables!!!
Sub New(ByVal pizz)
pizza = pizz
'pizza.setSize(pizz.getSize())
'pizza.setCrust(pizz.getCrust())
End Sub
' Returns cost of product by delegating call to wrapped objects
Public Overrides Function getCost() As Double
Dim cost ' As Double = pizza.getCost()
If (pizza.getSize() = pizza.SMALL) Then
Console.Write(" In cheese pizza = SMALL" & vbNewLine)
cost += 0.1
ElseIf (pizza.getSize() = pizza.MEDIUM) Then
cost += 0.2
ElseIf (pizza.getSize() = pizza.LARGE) Then
cost += 0.3
ElseIf (pizza.getSize() = pizza.EXTRA_LARGE) Then
cost += 0.4
End If
Console.Write(" Pizza size = " + pizza.getSize().ToString & vbNewLine)
Console.Write(" in end if" & vbNewLine)
Return cost + pizza.getCost()
End Function
Public Overrides Function getDescription() As Object
Return pizza.getDescription() + ", Extra Cheese"
End Function
End Class
Then run a test with this:
Public Class TestForm
Public margheritaM ' As MargheritaMaker
Public pizza ' As Pizza
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Dim m As New MargheritaMaker()
'Dim pizza
'Dim margheritaM As New MargheritaMaker()
margheritaM = New MargheritaMaker()
pizza = margheritaM.createPizza(1, "Deep Pan")
MargheritaBox.AppendText(pizza.getDescription() & vbNewLine)
End Sub
Private Sub CheeseButton_Click(sender As Object, e As EventArgs) Handles CheeseButton.Click
'pizza As New Cheese(pizza)
pizza = New Cheese(pizza)
MargheritaBox.AppendText(pizza.getDescription() & vbNewLine)
End Sub
Private Sub CostButton_Click(sender As Object, e As EventArgs) Handles CostButton.Click
MargheritaBox.AppendText(pizza.getCost() & vbNewLine)
End Sub
Private Sub PepperoniButton_Click(sender As Object, e As EventArgs) Handles PepperoniButton.Click
pizza = New Pepperoni(pizza)
MargheritaBox.AppendText(pizza.getDescription() & vbNewLine)
End Sub
End Class
I assumed I could create a MargheritaPizza objcet with Button1 click and assign a pizza object to pizza via it's factory method createPizza
then on cheeseButton click I could wrap the pizza object created with the call pizza = New Cheese(pizza)!! The cheese class encapsulates the extra topping for the pizza. I thought that I could then call cost on the original pizza object wiich would delegate the cost through the wrapped objects?? As in the Decorator Pattern.
Some screen shot of output below:
Here I clicked create pizza then calculate cost then extra cheese finally calculate cost, all seems well!
This time I also clicked extra cheese and calculate cost, but the cost is not delegating through the objects correctly!
Here I added several extra cheese and also show some output to the console window for the test, the console window shows the size of the pizza each time the object is wrapped and shows that pizza size is 0 except on the inner most wrapper...What am I doing wrong??
My first language is Java and have no problem with this technique in the past, but new to VB 2013 and appreciate some help here..
All constructive comments welcome.
Many thanks
Please ignore code that's been commented out during testing

Wrapping
Firstly, and this is only my opinion, the term wrapping are used when you're dealing with NotInheritable classes.
Example:
Public Class String2
Public Sub New(s As String)
Me.s = s
End Sub
Default Public ReadOnly Property Chars(index As Integer) As Char
Get
Return Me.s.Chars(index)
End Get
End Property
Public ReadOnly Property Length() As Integer
Get
Return s.Length
End Get
End Property
Private ReadOnly s As String
End Class
Base classes
Secondly, you're overcomplicating this. The following code are stripped to make it more readable. Note that this is not a final solution.
Start by defining a Crust and a Size enum.
Public Enum PizzaCrust As Integer
[Default] = 0
DeepPan = 1
End Enum
Public Enum PizzaSize As Integer
S = 0
M = 1
L = 2
XL = 3
End Enum
You only need two base classes, Pizza and Extra.
Public MustInherit Class Pizza
Public Property Crust() As PizzaCrust
MustOverride ReadOnly Property Description() As String
Public ReadOnly Property Extras() As List(Of Extra)
MustOverride ReadOnly Property Name() As String
Public Property Size() As PizzaSize
Public Function CalculateCost() As Decimal
Dim value As Decimal = Me.GetBaseCost(Me.Size)
For Each extr As Extra In Me.Extras
value += extr.Cost
Next
Return value
End Function
Protected MustOverride Function GetBaseCost(size As PizzaSize) As Decimal
End Class
Public MustInherit Class Extra
Public MustOverride ReadOnly Property Name() As String
Public MustOverride ReadOnly Property Description() As String
Public MustOverride ReadOnly Property Cost() As Decimal
End Class
Pizzas
Create a namespace named Pizzas and put all of your pizzas here.
Namespace Pizzas
Public Class Margarita
Inherits Pizza
Public Overrides ReadOnly Property Description() As String
Public Overrides ReadOnly Property Name() As String
Protected Overrides Function GetBaseCost(size As PizzaSize) As Decimal
Select Case size
Case PizzaSize.S
Return 10
Case PizzaSize.M
Return 15
Case PizzaSize.L
Return 20
Case PizzaSize.XL
Return 30
Case Else
Return 0
End Select
End Function
End Class
Public Class Quattro
Inherits Pizza
Public Overrides ReadOnly Property Description() As String
Public Overrides ReadOnly Property Name() As String
Protected Overrides Function GetBaseCost(size As PizzaSize) As Decimal
End Class
End Namespace
Extras
Finally, create a namespace named Extras and put all of the extras here.
Namespace Extras
Public Class Cheese
Inherits Extra
Public Overrides ReadOnly Property Cost() As Decimal
Public Overrides ReadOnly Property Description() As String
Public Overrides ReadOnly Property Name() As String
End Class
Public Class Ham
Inherits Extra
Public Overrides ReadOnly Property Cost() As Decimal
Public Overrides ReadOnly Property Description() As String
Public Overrides ReadOnly Property Name() As String
End Class
Public Class Pineapple
Inherits Extra
Public Overrides ReadOnly Property Cost() As Decimal
Public Overrides ReadOnly Property Description() As String
Public Overrides ReadOnly Property Name() As String
End Class
End Namespace
Usage
Now, to create a new Margarita pizza, one would do it like this:
Dim pizza As New Pizzas.Margarita()
pizza.Size = PizzaSize.L
pizza.Crust = PizzaCrust.DeepPan
pizza.Extras.Add(New Extras.Ham())
pizza.Extras.Add(New Extras.Cheese())

Related

How to determine why my code is not working?

Imports Kerry_Sales_Project
' base class
Public Class Bonus
Public Property SalesId As String
Public Property Sales As Double
' default constructor
Public Sub New()
_SalesId = String.Empty
_Sales = 0
End Sub
' parameterized constructor
Public Sub New(ByVal strId As String, ByVal dblSold As Double)
SalesId = strId
Sales = dblSold
End Sub
' GetBonus method
Public Overridable Function GetBonus() As Double
Return _Sales * 0.05
End Function
Public Shared Widening Operator CType(v As PremiumBonus) As Bonus
Throw New NotImplementedException()
End Operator
End Class
' derived class
Public Class PremiumBonus
Public Property Sales As Double
Public Property strId As String
' default constructor
Public Sub New(strId As String)
MyBase.New()
End Sub
' parameterized constructor
Public Sub New(ByVal strId As String, ByVal dblSold As Double)
MyBase.New(strId, dblSold)
End Sub
' class method
Public Overrides Function GetBonus() As Double
Return MyBase.GetBonus() + (Sales - 2500) * 0.01
End Function
End Class
In effect, you forgot to place Inherits Bonus in the Premium Bonus class. It would be something like
Public Class PremiumBonus: Inherits Bonus
And the other thing is the CType operator overload.
Public Shared Widening Operator CType(v As PremiumBonus) As Bonus
Throw New NotImplementedException()
End Operator
This operator overload is not necessary because the PremiumBonus class is a child of Bunus and they are contained.

Advantage or Disadvantage between Two Class Modules

What would be the advantages or disadvantages of using Class1 instead of Class2?
The quantity information stored in each instance of the class will be adjusted up and down as needed (via the functions, and while it seems to make sense to me that I would only need to make these variables public so that they are visible from outside the class, I feel that there is most likely some reason that this shouldn't been done.
Class1
Option Explicit
Public Sequence As String
Public Quantity As Double
Public Sub AddQty(sAddQty As Double)
Quantity = Quantity + AddQty
End Sub
Public Sub SubQty(sSubQty As Double)
Quantity = Quantity - sSubQty
End Sub
Class2
Option Explicit
Private iSeq As String
Private iQty As Double
Public Property Get Qty() As Double
Qty = iQty
End Property
Public Property Let Qty(lQty As Double)
iQty = lQty
End Property
Public Property Get Sequence() As String
Sequence = iSeq
End Property
Public Property Let Sequence(lSeq As String)
iSeq = lSeq
End Property
Public Sub AddQty(sAddQty As Double)
iQty = iQty + AddQty
End Sub
Public Sub SubQty(sSubQty As Double)
iQty = iQty - sSubQty
End Sub
In terms of interfaces, the two are exactly equivalent, because public fields are exposed as Property members. If you added a 3rd class module and wrote this:
Implements Class1
You would be forced by the compiler to add these members:
Private Property Get Class1_Sequence() As String
End Property
Private Property Let Class1_Sequence(ByVal RHS As String)
End Property
Private Property Get Class1_Quantity() As Double
End Property
Private Property Let Class1_Quantity(ByVal RHS As Double)
End Property
Private Sub Class1_AddQty(sAddQty As Double)
End Sub
Private Sub Class1_SubQty(sSubQty As Double)
End Sub
If you added another class module and wrote this:
Implements Class2
You would be forced by the compiler to have essentially the exact same members:
Private Property Get Class2_Sequence() As String
End Property
Private Property Let Class2_Sequence(ByVal RHS As String)
End Property
Private Property Get Class2_Qty() As Double
End Property
Private Property Let Class2_Qty(ByVal RHS As Double)
End Property
Private Sub Class2_AddQty(sAddQty As Double)
End Sub
Private Sub Class2_SubQty(sSubQty As Double)
End Sub
When properties do nothing and there's no incentive to properly encapsulate their values, go ahead and have public fields.
However there's little need for AddQty or SubQty instance methods when the backing field exposes a Property Let accessor - one could simply do foo.Quantity = foo.Quantity + 2 instead. An API that appears to provide multiple ways to do the same thing, is a confusing API.
So what you do, is you define an explicit interface that defines the API you want to work with:
Public Property Get Quantity() As Double
End Property
Public Property Get Sequence() As String
End Property
Public Sub AddQty(ByVal value As Double)
End Sub
Public Sub SubQty(ByVal value As Double)
End Sub
And then make your class Implements this interface (say, ISomething), and the rest of the code works with this ISomething interface that only exposes the members you want it to be able to work with - and that excludes the class' Property Let members; the rest of the code only sees what it needs to see, and can only access what it needs to access.
Dim foo As ISomething
Set foo = New Something
'foo.Quantity = 42 ' illegal
Dim bar As Something
Set bar = foo
bar.Quantity = 42 ' ok
bar.AddQty 2
Debug.Print foo.Quantity ' should be 44

How Do I Create an Extension of a Single Class Property

I have a primitive Class that looks like this:
Public Class BaseGeoData
Property GeoOrigin As String
Property GeoDestination As String
Property TravelDistance As Double?
Property TravelTime As Double?
Public Sub New()
End Sub
End Class
Public Class GeoData
Inherits BaseGeoData
Public Sub New(geoOrigStr As String, geoDestStr As String)
GeoOrigin = geoOrigStr
GeoDestination = geoDestStr
TravelDistance = 5000 'in meters
TravelTime = 360 'in minutes
End Sub
End Class
I want to be able to add 2 extensions that will return converted values like this:
TravelDistance.ToMiles()
TravelTime.ToHours()
When I add a Module to extend the class, it offers the extension to the entire class, most properties of which will never use the extension. How can I just offer the extensions to the properties that need them?
Introduce own type of "Unit" for measurement values
Public MustInherit Class Unit
Public ReadOnly Property Value As Double
Public MustOverride ReadOnly Property Name As String
Public Sub New(value As Double)
Me.Value = value
End Sub
Public Overrides Function ToString() As String
Return $"{Value} {Name}"
End Function
End Class
Public Class Meter
Inherits Unit
Public Sub New(value As Double)
MyBase.New(value)
End Sub
Public Overrides ReadOnly Property Name As String
Get
Return "m"
End Get
End Property
End Class
Public Class Mile
Inherits Unit
Public Sub New(value As Double)
MyBase.New(value)
End Sub
Public Overrides ReadOnly Property Name As String
Get
Return "mi"
End Get
End Property
End Class
And extension methods for creating unit and convertions
Public Module UnitConversions
<Extension>
Public Function Meters(value As Integer) As Meter
Return New Meter(value)
End Function
<Extension>
Public Function Miles(value As Integer) As Mile
Return New Mile(value)
End Function
<Extension>
Public Function ToMiles(meters As Meter) As Mile
Dim miles = meters.Value * 0.00062137
Return New Mile(miles)
End Function
<Extension>
Public Function ToMeters(miles As Mile) As Meter
Dim meters = miles.Value * 1609.344
Return New Meter(meters)
End Function
End Module
Then you can use value in more readable manner
TravelDistance = 5000.Meters() ' meters
' Conversion
geoData.TravelDistance.ToMiles() ' miles
Console.WriteLine(geoData.TravelDistance) ' print 3.10685 mi
You can only add extension methods into types (i.e. classes).
TravelDistance is of type Double? so you have to add an extention method into Double?.
Note that it would make the method available for every Double?, which may not be something you want.
I really like Plutonix's resolution and is the same one I would go for first.
Its simple and resolves your initial problem.
Public Class BaseGeoData
Property GeoOrigin As String
Property GeoDestination As String
Property TravelDistance As Double?
Property TravelTime As Double?
Public Sub New()
End Sub
End Class
Public Class GeoData
Inherits BaseGeoData
Public Sub New(geoOrigStr As String, geoDestStr As String)
GeoOrigin = geoOrigStr
GeoDestination = geoDestStr
TravelDistance = 5000 'in meters
TravelTime = 360 'in minutes
End Sub
Function DistanceMiles() As Double
DistanceMiles = (TravelDistance/1609.344)
End Function
Function TimeHours() As Double
DistanceMiles = (TravelTime /60)
End Function
End Class

Incompatible interface

Please see the code below:
Public Class clsCar
Implements IVehicle
Public Function getWheels() As Integer Implements IVehicle.getWheels
Return 4
End Function
End Class
Public Interface IVehicle
Function getWheels() As Integer
End Interface
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim list As List(Of IVehicle) = New List(Of IVehicle)
Dim v1 As IVehicle = New clsCar
Dim v2 As IVehicle = New clsBus
list.Add(v1)
list.Add(v2)
Dim IVehicle As IVehicle = New clsCar
Dim IVehicle2 As IVehicle = New clsBus
For Each IVehicle In list
MsgBox(IVehicle.getWheels())
Next
End Sub
End Class
I want to add a new function to the clsBus class:
Public Function getWheels(ByVal strCarType As String) As Integer
If strCarType = "Robin Ryliant" Then
Return 3
Else
Return 4
End If
End Function
How do I call this from the FOR EACH statement? At the moment it will call getWheels with no arguments.
You will have to add the method overload to the interface in order to be able to call it from a IVehicle variable.
Public Interface IVehicle
Function getWheels() As Integer
Function getWheels(ByVal strCarType As String) As Integer
End Interface
But probably it is better to have different, more specialized car types
Public Class clsCar
Implements IVehicle
Public Overridable Function getWheels() As Integer Implements IVehicle.getWheels
Return 4
End Function
End Class
Public Class clsRobinRyliantCar
Inherits clsCar
Public Overrides Function getWheels() As Integer
Return 3
End Function
End Class
This does not break the inheritance hierarchy and is purely polymorphic.
I think I would go for something more like this, with the number of wheels being an instance property (code in LINQPad format):
Sub Main
Dim list As List(Of IVehicle) = New List(Of IVehicle)()
list.Add(New clsCar("Ford Focus", 4))
list.Add(New clsCar("Robin Ryliant", 3))
list.Add(New clsBus())
For Each v In list
Console.WriteLine(String.Format("{0}:{1}", v.GetVehicleType(), v.GetWheels()))
Next
End Sub
' Define other methods and classes here
Public Interface IVehicle
Function GetVehicleType() As String
Function GetWheels() As Integer
End Interface
Public MustInherit Class clsVehicle
Implements IVehicle
Protected Property VehicleType As String
Protected Property Wheels As Integer
Protected Sub New(vehicleType As String, wheels As Integer)
Me.VehicleType = vehicleType
Me.Wheels = wheels
End Sub
Public Function GetVehicleType() As String Implements IVehicle.GetVehicleType
Return Me.VehicleType
End Function
Public Function GetWheels() As Integer Implements IVehicle.GetWheels
Return Me.Wheels
End Function
End Class
Public Class clsCar
Inherits clsVehicle
Public Sub New(vehicleType As String, wheels As Integer)
MyBase.New(vehicleType, wheels)
End Sub
End Class
Public Class clsBus
Inherits clsVehicle
Public Sub New()
MyBase.New("Bus", 4)
End Sub
End Class
If your clsBus and clsCar are meant to refer to a specific car then the type should be a member of that class already, not something you pass in when you want to get information. To this end I'd suggest that you have "type" as something you can pass in the constructor and then the method on the bus would have no parameters and would just refer to its internal type to determine how many wheels it has.
I'm not too fluent with VB.NET so would probably make mistakes in example code but hopefully you get what I mean. If not I'll knock up some code. :)

Enhancing 'Rock, Paper, Scissors' to include 'Lizard, Spock' in VB.NET and making the code more extensible, maintainable and re-usable

I'm new to programming and OOP so please forgive me for my lack of knowledge.
As part of my Rock, Paper and Scissors game I have a abstract superclass (Weapon) which has subclasses (Rock, Paper and Scissors) in VB.NET like:
Public MustInherit Class Weapons
Public MustOverride Function compareTo(ByVal Weapons As Object) As Integer
End Class
Public Class Paper
Inherits Weapons
Public Overrides Function compareTo(ByVal Weapons As Object) As Integer
If TypeOf Weapons Is Paper Then
Return 0
ElseIf TypeOf Weapons Is Rock Then
Return 1
Else
Return -1
End If
End Function
End Class
Public Class Rock
Inherits Weapons
Public Overrides Function compareTo(ByVal Weapons As Object) As Integer
If TypeOf Weapons Is Rock Then
Return 0
ElseIf TypeOf Weapons Is Scissors Then
Return 1
Else
Return -1
End If
End Function
End Class
Public Class Scissors
Inherits Weapons
Public Overrides Function compareTo(ByVal Weapons As Object) As Integer
If TypeOf Weapons Is Scissors Then
Return 0
ElseIf TypeOf Weapons Is Paper Then
Return 1
Else
Return -1
End If
End Function
End Class
Also have a superclass Player which has subclasses (PlayerComputerRandom, PlayerHumanPlayer and PlayerComputerTactical) like:
Imports RockPaperScissors.Weapons
Public Class Player
Private pName As String
Private pNumberOfGamesWon As String
Public pWeapon As Weapons
Property Name() As String
Get
Return pName
End Get
Set(ByVal value As String)
pName = value
End Set
End Property
Property NumberOfGamesWon As String
Get
Return pNumberOfGamesWon
End Get
Set(ByVal value As String)
pNumberOfGamesWon = value
End Set
End Property
Property getWeapon As Weapons
Get
Return pWeapon
End Get
Set(ByVal value As Weapons)
pWeapon = value
End Set
End Property
Public Sub pickWeapon(ByVal WeaponType As String)
If WeaponType = "Rock" Then
pWeapon = New Rock()
ElseIf WeaponType = "Paper" Then
pWeapon = New Paper()
Else
pWeapon = New Scissors()
End If
End Sub
End Class
Imports RockPaperScissors.Weapons
Public Class PlayerComputerRandom
Inherits Player
Private Enum weaponsList
Rock
Paper
Scissors
End Enum
Public Overloads Sub pickWeapon()
Dim randomChoice = New Random()
Dim CompChoice As Integer = randomChoice.Next(0, [Enum].GetValues(GetType(weaponsList)).Length)
If CompChoice = "0" Then
pWeapon = New Rock()
ElseIf CompChoice = "1" Then
pWeapon = New Paper()
Else
pWeapon = New Scissors()
End If
End Sub
End Class
Public Class PlayerComputerTactical
Inherits Player
Private plastMove As String
Property lastMove() As String
Get
Return plastMove
End Get
Set(ByVal value As String)
plastMove = value
End Set
End Property
Public Overloads Sub pickWeapon()
' Add tactical player functionality
End Sub
End Class
Public Class PlayerHumanPlayer
Inherits Player
End Class
I have the GameForm class which instantiates the objects and performs various other logic used for the front-end as shown below:
Public Class GameForm
Private Sub btnRock_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRock.Click
findWinner("HumanPlayer", "Rock", "RandomComputer")
End Sub
Private Sub btnPaper_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPaper.Click
findWinner("HumanPlayer", "Paper", "RandomComputer")
End Sub
Private Sub btnScissors_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnScissors.Click
findWinner("HumanPlayer", "Scissors", "RandomComputer")
End Sub
Public Sub findWinner(ByVal p1name As String, ByVal p1WeaponSelected As String, ByVal p2Name As String)
Dim player1 = New PlayerHumanPlayer()
Dim player2 = New PlayerComputerRandom()
player1.Name = p1name
player1.pickWeapon(p1WeaponSelected) ' Should I be using the Rock Class???
player2.Name = p2Name
player2.pickWeapon()
Dim winner As Integer = player1.getWeapon().compareTo(player2.getWeapon())
Select Case winner
Case 1
txtGameStatus.Text = player1.Name() + " wins!"
Case -1
txtGameStatus.Text = player2.Name() + " wins!"
Case 0
txtGameStatus.Text = "Draw!"
End Select
End Sub
End Class
What I need to do is to be able to add new Weapons (Lizard, Spock) which I know I can do this by simply adding subclasses(Lizard, Spock) which inherit Weapons baseclass.
However, this will require code changes to ALL subclasses (Rock, Paper and Scissors) which is not really a long-term maintainable solution. Certainly not best practise.
Im new to coding and OOP so, could someone kindly show how I could enhance the existing Game to easily allow additional weapons to be added? Could I use a DB table to store the Weapons? If so, could you show how? I just want to be able long-term, reusable solution for this Game.
Any idea how I can achieve this? Any help would be greatly appreciated.
Manys thanks in advance
Though it would be possible to dynamically add new "subclasses" it doesn't make sense. Just dont see "Paper" and "Rock" (as example) as different CLASSES, but as the same class with different attributes. One Attribute of a weapon is it's "name" ("Rock"), the other attribute is how it compares to another weapon (defined by name).
** UPDATED** Example:
Private TheData() As String = {"Scissor|Paper,Spock|Lizard,Rock",
"Paper|Rock,Spock|Scissor,Lizard",
"Rock|Scissor,Lizard|Paper,Spock",
"Spock|Rock,Lizard|Scissor,Paper",
"Lizard|Scissor,Paper|Rock,Spock"}
Sub Main()
Dim Weapons As New List(Of Weapon)
For Each s In TheData
Dim spl = s.Split("|"c)
Weapons.Add(New Weapon(spl(0), spl(1).Split(","c), spl(2).Split(","c)))
Next
Dim r As New Random
Dim outcome(2) As Integer
For i = 1 To 1000000
Dim w1 = Weapons(r.Next(Weapons.Count))
Dim w2 = Weapons(r.Next(Weapons.Count))
Dim o = w1.CompareTo(w2)
outcome(o + 1) += 1
Next i
Console.WriteLine("Loose = {0}, Win = {1}, Draw = {2}", outcome(0), outcome(2), outcome(1))
Console.ReadLine()
End Sub
End Module
Public Class Weapon
Implements IComparable(Of Weapon)
Public Name As String
Private StrongerWeapons As List(Of String)
Private WeakerWeapons As List(Of String)
Public Sub New(name As String, stronger As IEnumerable(Of String), weaker As IEnumerable(Of String))
Me.Name = name
StrongerWeapons = New List(Of String)(stronger)
WeakerWeapons = New List(Of String)(weaker)
End Sub
Public Function CompareTo(other As Weapon) As Integer Implements IComparable(Of Weapon).CompareTo
Select Case True
Case Me.Name = other.Name : Return 0
Case WeakerWeapons.Contains(other.Name) : Return -1
Case StrongerWeapons.Contains(other.Name) : Return 1
Case Else : Throw New ApplicationException("Error in configuration!")
End Select
End Function
End Class
Now you would have a configurable "battle-system".
The updated example shows the system "in action". TheData is the place where your configuration is "stored" and this could be inside a text/xml file, a database or whatever.
Please note, that this is an example for configurable and not for Stone/Scissor/Paper(Lizard/Spock), because in that specific case, the "solution" would be much simpler.
also one thing if this helps you can write operator for your class for compare and other
Ex :
#Region "Operators"
Public Shared Operator =(ByVal crD1 As GPSCoordinate, ByVal crD2 As GPSCoordinate) As Boolean
Return IsEql(crD1, crD2)
End Operator
Public Shared Operator <>(ByVal crD1 As GPSCoordinate, ByVal crD2 As GPSCoordinate) As Boolean
Return Not IsEql(crD1, crD2)
End Operator
Private Shared Function IsEql(ByVal crD1 As GPSCoordinate, ByVal crD2 As GPSCoordinate) As Boolean
If crD1 Is Nothing And crD2 Is Nothing Then
Return True
ElseIf Not crD1 Is Nothing And Not crD2 Is Nothing Then
Return CBool(crD1.Value = crD2.Value)
End If
Return False
End Function
#End Region
A widely used approach for this is double dispatching. You apply this whern you need to define a behavior (or return value) that depends on two different classes. Instead of creating a switch statement, you create one message per case and let each class decide how to behave. I'm not familiar with VB, so forgive me for using another language, but I think you will get the idea:
abstract class Weapon
{
abstract public function compareTo($otherWeapon);
abstract public function compareToRock();
abstract public function compareToPaper();
}
class Rock extends Weapon
{
public function compareTo($otherWeapon)
{
return $otherWeapon->compareToRock();
}
public function compareToRock(){return 0;}
public function compareToPaper(){return -1;}
}
class Paper extends Weapon
{
public function compareTo($otherWeapon)
{
return $otherWeapon->compareToPaper();
}
public function compareToRock(){return 1;}
public function compareToPaper(){return 0;}
}
The next step would be to add the Scissors class, which would mean:
Adding the compareToScissors() abstract message in the superclass.
Adding the compareToScissors() implementation in each subclass.
Adding the Scissors class and implementing the correspondign methods.
Adding Lizard and Spock is just repeating the same steps. As you can see there are some tradeoffs here:
(+) You are adding behavior, not changing existing one (i.e. you are not modifying existing methods implementations). This is good from the maintenance and testing point of view (your tests should still work).
(+) This depends more on personal tastes, but for me is more understandable to have the switch statement separated in single methods.
(-) There is a method explosion. This is a widely known side effect of double dispatching, and is that adding a new variant means adding a new method in all the other classes.
As a final note you may consider not returning an integer but to actually model your result as an object (Win/Loose/Tie). By doing so you can then delegate behavior to the result instead fo making switch statements over it.
HTH