Administrators PlausiblyDamp Posted October 30, 2004 Administrators Posted October 30, 2004 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! Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Administrators PlausiblyDamp Posted October 30, 2004 Author Administrators Posted October 30, 2004 (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.zipBankVB.zip Edited April 18, 2005 by PlausiblyDamp Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.