Jump to content
Xtreme .Net Talk

Intro to Object Orientated Programming Part 5 - Simple Inheritance and Constructors


Recommended Posts

  • Administrators
Posted

Guide to Object Orientated code in .Net

 

This is a continuation of the series started in here and is preceded by this thread

 

Introduction

So far we have looked at how we can define a class to encapsulate a simple business object and as part of this hide the internal details while providing a clean way for external code to utilise this class.

In this article we will look at a concept fundamental to OO development � the idea of polymorphism. In this article we will discuss one method of achieving this (inheritance) and in a later one we will also look at an alternate, but complimentary method (interfaces).

 

Benefits of our class so far

The class we have been developing so far provides a self contained (if currently limited) implementation of a bank account. The internal details of how this class works are hidden from any calling code while the supplied methods provide a clean and documented way for our class to be used.

One major feature of this is that any amendments to how our Credit / Debit methods work can be made in a single place, without the need to hunt down every line of code that looks like it is attempting to modify the account�s balance. Similarly the ability to perform a transfer is also happily contained within the class itself, again re-enforcing the idea of a self contained unit.

 

Current potential problems

One benefit of a strongly typed language is that it will enforce data types when passing parameters around; however at first glance this can seem to limit the flexibility of the language.

Imagine if we wished to add a second account type to our application, e.g. a SavingsAccount � this would have certain functionality that is similar to our existing account i.e. the need to Debit, Credit and obtain the current balance, while also having some unique functionality. In addition it will need to perform a Debit in a slightly different way.

In a simplistic solution to our requirement we could simply cut & paste the BankAccount Class, do a quick rename and then modify to suit our new needs.

e.g.

Public Class SavingsAccount

#Region "Private variables"
   Private _Balance As Decimal
   Private ReadOnly _AccountNumber As Integer
   Private _InterestRate As Decimal
#End Region

#Region "Public methods"
   Public Sub AddInterest()
       Credit(_Balance * _InterestRate)
   End Sub

   Public Sub Credit(ByVal amount As Decimal)
       _Balance += amount
   End Sub

   Public Sub Credit(ByVal amount As String)
       Credit(Decimal.Parse(amount, Globalization.NumberStyles.Currency))
   End Sub

   Public Sub Debit(ByVal amount As Decimal)
       'Savings accounts suffer a 5% surcharge on all Debits
       _Balance -= amount * 1.05D
   End Sub

   Public Sub Debit(ByVal amount As String)
       Debit(Decimal.Parse(amount, Globalization.NumberStyles.Currency))
   End Sub
#End Region

#Region "Properties"
   Public ReadOnly Property Balance() As Decimal
       Get
           Return _Balance
       End Get
   End Property

   Public ReadOnly Property AccountNumber() As Integer
       Get
           Return _AccountNumber
       End Get
   End Property

   Public Property InterestRate() As Decimal
       Get
           Return _InterestRate
       End Get
       Set(ByVal Value As Decimal)
           'check for acceptable value
           If Value >= 0 And Value <= 1 Then
               _InterestRate = Value
           End If
       End Set
   End Property
#End Region

#Region "Constructors"
   Public Sub New(ByVal accountNumber As Integer)
       _AccountNumber = accountNumber
   End Sub

   Public Sub New(ByVal accountNumber As Integer, ByVal initialBalance As Decimal)
       Me.New(accountNumber)
       _Balance = initialBalance
   End Sub
#End Region

   Public Shared Sub TransferMoney(ByVal accountTo As SavingsAccount, ByVal accountFrom As SavingsAccount, ByVal amount As Decimal)
       accountFrom.Debit(amount)
       accountTo.Credit(amount)
   End Sub

End Class

Note the additional AddInterest method, the InterestRate property and private variable as well as the modified Debit and TransferMoney methods.

 

This class appears (initially anyway) to meet our needs, however under closer scrutiny a couple of issues become apparent:

1. We are duplicating code, rarely is cut & paste a good solution to a problem. If you look at the original BankAccount class there is a fundamental problem with the Credit and Debit methods � both will accept negative amounts. If we fix this problem like:

   Public Sub Credit(ByVal amount As Decimal)
�prevent negative amounts but ignore for now � error handling will come later
       If amount < 0 Then Return
       _Balance += amount
   End Sub

   Public Sub Debit(ByVal amount As Decimal)
�prevent negative amounts but ignore for now � error handling will come later
       If amount < 0 Then Return
       _Balance -= amount
   End Sub

Then we have fixed one instance of the problem, the savings account is still �broken� and will need to be amended also; although not a large problem with only 2 methods in 2 classes but what about with 5 or 6 methods cut and pasted into 10 or 20 classes? Sooner or later this is going to become a maintenance nightmare.

 

2. The strongly typed TransferMoney method is now too limited: we can currently transfer between BankAccounts or SavingsAccounts but not between both! Possible solutions to this problem include (but are not limited to)

a) Relaxing the type safety by passing in the accounts as Object and then perform validation and identification of exactly what type of object was passed within the TransferMoney method to ensure only valid objects have been passed in and then cast them to the correct variable type (C# or VB.Net)

b) Rely on late binding (VB.Net only) and hope that we really have got an object that supports the required methods.

c) Create one Transfer money function per account combination; however this quickly gets out of control as with only 2 account types we would be required to implement the following overloaded methods.

Public Shared Sub TransferMoney(ByVal accountTo As BankAccount, ByVal accountFrom As BankAccount, ByVal amount As Decimal)

Public Shared Sub TransferMoney(ByVal accountTo As SavingsAccount, ByVal accountFrom As BankAccount, ByVal amount As Decimal)

Public Shared Sub TransferMoney(ByVal accountTo As BankAccount, ByVal accountFrom As SavingsAccount, ByVal amount As Decimal)

Public Shared Sub TransferMoney(ByVal accountTo As SavingsAccount, ByVal accountFrom As SavingsAccount, ByVal amount As Decimal)

Work out how many would be required for 3 accounts? 5 accounts? 15 accounts even?

 

Both of the above solutions will require considerable amount of time and effort in terms of the coding of each implementation, testing, porting bug fixes between implementations and either creating the new versions of TransferMoney or constantly updating the non type safe version as newer classes are added. Neither is really an adequate solution to the original problem.

Oh by the way the number of Transfer money methods is a square of the number of classes so the figures are: 3 accounts equals 9 methods, 5 accounts means 25 methods and 15 is a totally unrealistic and un-maintainable 225 methods!

Posting Guidelines FAQ Post Formatting

 

Intellectuals solve problems; geniuses prevent them.

-- Albert Einstein

  • Administrators
Posted (edited)

A Solution - Polymorphism

Notice how the SavingsAccount has a lot of methods in common with a BankAccount? More importantly in this case for a method like TransferMoney to work we only need these methods � we can safely ignore the extra functionality required by the SavingsAccount for now (although we will still need to be concerned about the Debit method�s newer implementation).

 

One way to solve this problem is through a technique called Inheritance, in this case not only do the two classes have very similar requirements but also very similar implementation details; this makes inheritance a good potential solution to the problem.

 

Inheritance

Inheritance is a relationship between two classes that can best be described as Is A. In other words a SavingsAccount Is A BankAccount, it is a more specialised version admittedly but that isn�t a problem here due to the fact that at a fundamental level the concept of a SavingsAccount being a type of BankAccount holds true.

This means we can take advantage of this and drastically simplify the code involved. In VB.Net we have a new keyword Inherits while C# uses the C++ style of inheritance declaration.

To declare the SavingsAccount class now we can reduce the initial cut and paste of code to just

Public Class SavingsAccount
   Inherits BankAccount

End Class

 

public class SavingsAccount :BankAccount
{

}

 

The above code simply means that SavingsAccount inherits it�s functionality from the BankAccount class (for now treat this as meaning SavingsAccount exposes all of BankAccount�s public functionality), and more importantly can be used wherever a BankAccount is expected.

 

If we now try and compile our code we will get a couple of compiler failures, however these are not major problems. The first major issue is concerned with how inheritance and constructors work together.

 

Constructors and Inheritance

When we inherit from another class we gain nearly all of its functionality, for free, no cut and paste and no code duplication; this occurs dynamically with no additional effort beyond what we did above.

One of the few things we will not get for free is automatic inheritance of non-default constructors, these we will need to code ourselves. Note that if the BankAccount class had also implemented a default constructor (one with no parameters) then this would not be an issue.

 

If we simply cut and paste the Sub New from the bank account then this error goes away :) Admittedly a new error takes its place straight away :(

The problem here is that we are referring to the Private variables _AccountNumber and _Balance, which being declared Private are only available in the BankAccount class � at this point we can introduce another access level, that of Protected. Any variable or method declared protected will be available in the Base Class (the class we are inheriting from) and any class that inherits from it. If we amend the BankAccount class so it now reads

   Protected _Balance As Decimal
   Protected ReadOnly _AccountNumber As Integer

Then these variables will be accessible in the SavingsAccount class, however this will still not fix our problem. The new problems that arise are also a consequence of the fact that constructors have certain limitations and requirements all of their own. Also even if this had fixed the problem this isn�t necessarily the most effective way of tackling the problem as yet again we are still cutting and pasting code! An improved method is to create the constructors in the SavingsAccount class but simply get the Base class (BankAccount) to do the work for us. If we put the variable declarations back to their original form

   Private _Balance As Decimal
   Private ReadOnly _AccountNumber As Integer

We can now modify the constructors to call the original version:

   Public Sub New(ByVal accountNumber As Integer)
       MyBase.New(accountNumber)
   End Sub

   Public Sub New(ByVal accountNumber As Integer, ByVal initialBalance As Decimal)
       MyBase.New(accountNumber, initialBalance)
   End Sub

 

public SavingsAccount(int accountNumber) :base(accountNumber)
{
}

public SavingsAccount(int accountNumber, decimal initialBalance) : base(accountNumber, initialBalance)
{
}

 

Notice how in both the VB.Net and C# samples we are simply calling our Base�s implementation of these constructors, VB via the MyBase.New(�) call and in C# via the base(�) construct. By simply delegating the work this way we are again limiting the impact of code changes on the system � if we change the underlying implementation of BankAccount then we do not need to propagate these changes through out the system, we have effectively created a single point of maintenance.

 

When we then extend the SavingsAccount to include the new functionality we end up with a class like

Public Class SavingsAccount
   Inherits BankAccount

#Region "Private Variables"
   Private _InterestRate As Decimal
#End Region

#Region "Properties"
   Public Property InterestRate() As Decimal
       Get
           Return _InterestRate
       End Get
       Set(ByVal Value As Decimal)
           'check for acceptable value
           If Value >= 0 And Value <= 1 Then
               _InterestRate = Value
           End If
       End Set
   End Property

#End Region

#Region "Public methods"
   Public Sub AddInterest()
       Credit(Balance * _InterestRate)
   End Sub
#End Region

#Region "Constructors"
   Public Sub New(ByVal accountNumber As Integer)
       MyBase.New(accountNumber)
   End Sub

   Public Sub New(ByVal accountNumber As Integer, ByVal initialBalance As Decimal)
       MyBase.New(accountNumber, initialBalance)
   End Sub
#End Region

End Class

Notice how we have added the support for all the new functionality with very little effort, be aware that we still haven�t dealt with the requirement for a modified Debit method yet though � this will be the next thing we tackle.

 

Be aware that in order to access the extra functionality provided by a SavingsAccount class the variable needs to be declared as SavingsAccount, however if the variable is declared as BankAccount it can contain either a BankAccount or a SavingsAccount but will only provide access to the BankAccount functionality.

This can be a difficult concept to explain in a purely theoretical way - it is probably worth playing around with the attached solution and changing the way the variable types are declared and assigned in the form.

'default will work fine
   Dim AccA As BankAccount
   Dim AccB As SavingsAccount

   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       AccA = New BankAccount(1, 1000)
       AccB = New SavingsAccount(2, 500)
   End Sub
'you may want to try the following combinations to see which work and which don�t
   Dim AccA As BankAccount
   Dim AccB As BankAccount

   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       AccA = New SavingsAccount (1, 1000)
       AccB = New SavingsAccount(2, 500)
   End Sub
'and
   Dim AccA As SavingsAccount
   Dim AccB As SavingsAccount

   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       AccA = New BankAccount (1, 1000)
       AccB = New BankAccount (2, 500)
   End Sub

BankCS.zip

BankVB.zip

Edited by PlausiblyDamp

Posting Guidelines FAQ Post Formatting

 

Intellectuals solve problems; geniuses prevent them.

-- Albert Einstein

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...