⚡ Rocket.net – Managed WordPress Hosting

MiltonMarketing.com  Powered by Rocket.net – Managed WordPress Hosting

Bernard Aybouts - Blog - MiltonMarketing.com

Approx. read time: 50.9 min.

Post: A Comprehensive Guide to Object-Oriented Programming in VB.NET

Table of Contents

  44 Minutes Read

A Comprehensive Guide to Object-Oriented Programming in VB.NET

Introduction: The Object-Oriented Paradigm in VB.NET

Software development has evolved through several paradigms, each representing a fundamental shift in how programmers approach problem-solving. One of the most significant of these shifts was the move from procedural programming to Object-Oriented Programming (OOP). Visual Basic.NET (VB.NET) is a modern, first-class language developed by Microsoft that fully embraces and supports the object-oriented paradigm, combining the power of the.NET Framework with the productivity benefits of Visual Basic. To master VB.NET is to master OOP. This guide provides an exhaustive exploration of every core OOP concept, demonstrated through heavily documented, practical code examples in VB.NET.

From Instructions to Objects: A Paradigm Shift

To appreciate the power of Object-Oriented Programming, it is essential to first understand the paradigm it largely replaced: procedural programming.

Procedural Programming

Procedural programming, derived from structured programming, models a program as a linear, top-down sequence of instructions. The program is divided into a collection of procedures, also known as functions or subroutines, which are sets of computational steps to be carried out. In this model, the primary focus is on the procedures—the actions or verbs of the program. Data is often stored in separate variables and structures, which are passed to procedures to be operated upon. This can lead to a system where data is globally accessible, moving freely between functions.

While this approach is straightforward and efficient for small, simple programs, its limitations become apparent as applications grow in size and complexity. The lack of a strong connection between data and the functions that manipulate it can make maintenance difficult. A change to a data structure might necessitate changes in every function that touches that data. Furthermore, the prevalence of global data makes the system less secure, as it can be altered accidentally from anywhere in the program.

Object-Oriented Programming (OOP)

Object-Oriented Programming represents a fundamental change in perspective. Instead of focusing on the procedures (the verbs), OOP focuses on the “things” or objects (the nouns) that make up a system. It is a bottom-up approach that models the real world by bundling data and the functions that operate on that data into single, self-contained units called “objects”.

This shift from prioritizing functions to prioritizing data is the cornerstone of the paradigm. In OOP, data is no longer passive and passed around; it is an active component of an object, which contains its own set of behaviors (methods) for manipulation. An object, therefore, is an instance of a “class,” which serves as a blueprint defining the object’s data (properties) and behaviors (methods). VB.NET was designed from the ground up as a fully object-oriented language, providing robust support for these principles.

The Core Benefits of OOP

The paradigm shift to OOP is not merely an academic exercise; it yields significant, tangible benefits that have made it the standard for modern, large-scale software development. These advantages are not isolated features but are direct consequences of the object-centric approach to modeling programs.

  • Enhanced Security through Encapsulation: By bundling data and methods into an object, OOP allows for data hiding. The internal state of an object is protected from direct, unauthorized access by external code. This principle, known as encapsulation, prevents unintended data corruption and enhances the overall security and integrity of the program.
  • Code Reusability through Inheritance: Inheritance allows a new class to be based on an existing class, inheriting its properties and methods. This “is-a” relationship (e.g., a Manager “is an” Employee) promotes the reuse of proven, tested code, which significantly reduces development time, effort, and cost.
  • Increased Flexibility through Polymorphism: Polymorphism, meaning “many forms,” allows a single interface (like a method name) to be used for a general class of actions, with each specific class implementing the action in its own way. This simplifies code and makes the system more flexible and adaptable to change.
  • Improved Modularity and Maintenance: OOP encourages breaking down a complex problem into smaller, self-contained, and manageable objects. This modularity makes troubleshooting far easier; if an object malfunctions, the problem is localized to its defining class. This also streamlines team collaboration, as developers can work on different objects simultaneously with minimal interference.
  • Effective Problem Solving for Complexity: For large, complex applications, OOP is a more intuitive and effective approach than procedural programming. It allows developers to model complex, real-world systems by breaking them down into their constituent parts (objects), writing a class for each part, and then assembling them to create the final application.

Ultimately, the transition to OOP is a change in the developer’s mental model. Procedural programming thinks in terms of verbs and actions, while OOP thinks in terms of nouns and entities. This noun-centric modeling of real-world “things” naturally leads to the concept of encapsulation, which provides security. The hierarchical nature of real-world objects leads to inheritance and code reuse. The ability of different objects to respond to the same command in unique ways leads to polymorphism. This cascade of benefits, all stemming from the core principle of object-centric design, is what makes OOP a powerful and indispensable paradigm for professional software development.

Section 1: The Foundation – Classes and Objects

At the heart of Object-Oriented Programming lie two fundamental concepts: the class and the object. Understanding the relationship between these two is the first and most critical step in mastering OOP. They are the essential building blocks upon which all other principles are constructed.

The Class: A Blueprint for Objects

A class is a template, a blueprint, or a set of specifications that defines a type of object. A class itself is not an object; rather, it is the code that describes what an object of that type will be. It defines the structure and behavior—the properties and methods—that all objects created from it will share. In VB.NET, a class is defined using the Class and End Class keywords.

A powerful and widely used analogy, provided by Microsoft’s own documentation, is that of a cookie cutter and cookies. The cookie cutter is the class. It defines the characteristics of each cookie, such as its shape and size. The class is the blueprint used to create the objects.

The Object: An Instance of a Class

An object is a usable, in-memory instance of a class. If the class is the cookie cutter, the objects are the actual cookies created from it. Once you have defined a class, you can create as many objects from that class as you need. Each object is a distinct entity, and while they all share the structure and behaviors defined by the class, each one maintains its own unique state (its own data).

The act of creating an object from a class is called instantiation. In VB.NET, this is typically accomplished by using the Dim statement to declare a variable of the class type, combined with the New operator to create a new instance and assign it to the variable.

‘ This line declares a variable named ‘myCar’ and instantiates a new object
‘ from the ‘Car’ class. ‘myCar’ is now an object.
Dim myCar As New Car()

Anatomy of a Class (Class Members)

A class definition is composed of several types of members that define its data and behavior.

  • Fields: Fields are variables declared within a class that hold the object’s internal data or state. To properly enforce encapsulation, it is a critical best practice to declare fields as Private. This prevents code outside the class from directly accessing and potentially corrupting the object’s data. For example: Private _customerName As String. The underscore prefix is a common, though not required, naming convention for private fields that back public properties.
  • Properties: Properties are a more advanced and controlled mechanism for exposing an object’s data. They act as public “gates” to the private fields, using special procedures called Get and Set accessors. The Get accessor returns the value of the underlying private field, while the Set accessor assigns a new value. The primary advantage of properties over public fields is that they allow for the inclusion of validation logic, ensuring the object’s data remains in a valid state. VB.NET also supports auto-implemented properties, which provide a more concise syntax for when no special validation logic is needed.
  • Methods: Methods define the actions or behaviors that an object can perform. They are the functions and procedures of the class. In VB.NET, a method that does not return a value is defined using the Sub keyword, while a method that does return a value is defined using the Function keyword.

The strict separation between Private fields and Public properties is not merely a stylistic convention; it is the primary mechanism for achieving encapsulation at the member level. While using public fields might seem simpler for beginners, it breaks the “black box” principle of OOP and sacrifices the security, validation, and maintainability that the paradigm is designed to provide. An object’s state must be protected to ensure its integrity. For example, a Book object should not have a negative page count. If the PageCount were a public field, any part of the program could erroneously set it to a negative value. By using a private field (_pageCount) and a public property, the Set accessor acts as a gatekeeper, allowing the programmer to enforce rules, such as If value > 0 Then _pageCount = value. This pattern is the direct implementation of data protection and is fundamental to robust OOP design.

Code Example: The Book Class

The following code provides a heavily documented, step-by-step example of defining a simple Book class, which serves as a practical introduction to classes, objects, and their members.

‘ =============================================================================
‘ CLASS DEFINITION: The Blueprint
‘ A class is a blueprint. Here, we define the blueprint for any object that
‘ represents a “Book”. This class encapsulates all the data and behaviors
‘ related to a book into a single, reusable unit.
‘ =============================================================================
Public Class Book

‘ =========================================================================
‘ 1. FIELDS: Private variables to hold the object’s internal data.

‘ These fields represent the state of a single Book object. By declaring
‘ them as ‘Private’, we enforce encapsulation. This means they cannot be
‘ accessed or modified directly from outside the Book class. This protects
‘ the data from accidental corruption and ensures that data is only changed
‘ through controlled means (properties).
‘ The underscore prefix (_title) is a common naming convention for private
‘ fields that are “backing” a public property.
‘ =========================================================================
Private _title As String
Private _author As String
Private _pageCount As Integer

‘ =========================================================================
‘ 2. PROPERTIES: Public “gates” that control access to the private fields.

‘ Properties provide a controlled way to get (read) and set (write) the
‘ values of the private fields. This is a core part of encapsulation.
‘ =========================================================================

‘ The ‘Title’ property controls access to the ‘_title’ field.
Public Property Title As String
‘ The GET accessor is executed when code reads the value of the property.
‘ Example: Dim currentTitle As String = myBook.Title
Get
Return _title
End Get
‘ The SET accessor is executed when code assigns a value to the property.
‘ The new value is passed in through the implicit ‘value’ parameter.
‘ Example: myBook.Title = “New Title”
Set(ByVal value As String)
‘ Here, we can add validation logic. A book title should not be
‘ empty or just whitespace. If an invalid value is provided, we
‘ can assign a default value or raise an error. This protects
‘ the object’s state.
If String.IsNullOrWhiteSpace(value) Then
_title = “Untitled”
Else
_title = value
End If
End Set
End Property

‘ The ‘Author’ property controls access to the ‘_author’ field.
Public Property Author As String
Get
Return _author
End Get
Set(ByVal value As String)
If String.IsNullOrWhiteSpace(value) Then
_author = “Unknown Author”
Else
_author = value
End If
End Set
End Property

‘ The ‘PageCount’ property controls access to the ‘_pageCount’ field.
Public Property PageCount As Integer
Get
Return _pageCount
End Get
Set(ByVal value As Integer)
‘ Validation logic is crucial here. A book cannot have a negative
‘ number of pages. This property ensures the object’s state
‘ remains valid.
If value > 0 Then
_pageCount = value
Else
_pageCount = 0 ‘ Assign a sensible default for invalid input.
End If
End Set
End Property

‘ =========================================================================
‘ 3. METHODS: Actions the object can perform.

‘ Methods define the behavior of an object. This is a ‘Sub’ because it
‘ performs an action (displaying information) but does not return a value.
‘ =========================================================================
Public Sub DisplayBookInfo()
Console.WriteLine(“————————-“)
Console.WriteLine($”Title: {Title}”)     ‘ Reads the Title property
Console.WriteLine($”Author: {Author}”)   ‘ Reads the Author property
Console.WriteLine($”Pages: {PageCount}”) ‘ Reads the PageCount property
Console.WriteLine(“————————-“)
End Sub
End Class

‘ =============================================================================
‘ MODULE: The Application Entry Point
‘ A Module is a container for code that is not part of a specific class instance.
‘ The ‘Main’ Sub is the starting point of a console application.
‘ =============================================================================
Module Program
Sub Main()
‘ =====================================================================
‘ INSTANTIATION: Creating an actual object from the Book class blueprint.

‘ The ‘New’ keyword creates an instance of the Book class in memory.
‘ ‘vbNetBook’ is now an object, a specific, tangible “thing” based on
‘ the Book template.
‘ =====================================================================
Console.WriteLine(“Creating the first book object…”)
Dim vbNetBook As New Book()

‘ =====================================================================
‘ USING THE OBJECT: Accessing the object’s members using dot notation.

‘ We interact with the object through its public interface (properties
‘ and methods). We are not touching the private fields directly.
‘ These lines call the ‘Set’ part of each property.
‘ =====================================================================
vbNetBook.Title = “Mastering VB.NET OOP”
vbNetBook.Author = “Technical Expert”
vbNetBook.PageCount = 500

‘ Now, let’s try to set an invalid page count. The property’s validation
‘ logic will protect the object.
Console.WriteLine(“Attempting to set page count to -100…”)
vbNetBook.PageCount = -100 ‘ This will be handled by the Set accessor.

‘ Calling the object’s method to perform an action.
‘ This method will use the object’s current state to display its info.
vbNetBook.DisplayBookInfo() ‘ Will show PageCount as 500, not -100.

‘ Let’s set a valid page count again.
vbNetBook.PageCount = 450
vbNetBook.DisplayBookInfo()

‘ =====================================================================
‘ CREATING ANOTHER INSTANCE
‘ Each object is independent and has its own state.
‘ =====================================================================
Console.WriteLine(vbCrLf & “Creating a second, independent book object…”)
Dim classicNovel As New Book()
classicNovel.Title = “Pride and Prejudice”
classicNovel.Author = “Jane Austen”
classicNovel.PageCount = 279

‘ Setting the title to an empty string to test validation.
classicNovel.Title = “”
classicNovel.DisplayBookInfo() ‘ Will show Title as “Untitled”.

Console.WriteLine(vbCrLf & “Press any key to exit.”)
Console.ReadKey()
End Sub
End Module

Section 2: The First Pillar – Encapsulation

Encapsulation is one of the four foundational pillars of Object-Oriented Programming. It is the mechanism of bundling the data (fields and properties) and the code that operates on the data (methods) into a single, self-contained unit: the class. More importantly, encapsulation involves hiding the complex internal implementation of an object and exposing only a controlled, public interface.

The “Black Box” Principle

Think of a well-encapsulated object as a “black box.” A user of the object knows what it does through its public interface (its public methods and properties), but they do not need to know—and should not be able to access—how it does it. This principle provides several key benefits:

  1. Data Protection: It protects an object’s internal state from accidental or malicious modification by external code. This ensures data integrity, as the object itself is responsible for maintaining a valid state.
  2. Reduced Complexity: By hiding implementation details, the object becomes simpler to use. A developer using a BankAccount object only needs to know about Deposit and Withdraw methods, not the intricate details of how transactions are logged or balances are stored.
  3. Enhanced Maintainability: Encapsulation is the key enabler of modularity and maintainability. Because the internal logic is hidden, a developer can completely refactor or change how a class works internally without breaking any of the external code that uses it, provided the public interface remains unchanged. For example, a BankAccount’s balance could initially be stored in a simple variable. Later, this could be changed to a database lookup. As long as the public Deposit method and Balance property signatures do not change, the client code requires no modification. This decoupling of use from implementation is critical for building large, evolving software systems.

Implementation via Access Modifiers

The primary tool for implementing encapsulation in VB.NET is the use of access modifiers. These are keywords that specify the level of accessibility—or visibility—of a class and its members. Choosing the correct access modifier is a crucial design decision.

Modifier Accessibility Description
Public From any other code in the same project or any project that references it. Unrestricted access. Used to define the class’s public interface that other parts of the application will interact with.
Private Only from within the same class. The highest level of restriction and the default for encapsulation. Used for internal fields and private helper methods that should not be exposed.
Protected From within the same class and from any derived (child) classes. Essential for inheritance. It allows a child class to access and use the implementation details of its parent class while still hiding them from the outside world.
Friend From within the same assembly (the current project, e.g., a .exe or .dll). Useful for creating helper classes or members that need to be shared within a project but should not be part of the public API exposed to other projects.
Protected Friend A combination of Protected and Friend. The member is accessible from within the same assembly AND from derived classes (even if they are in a different assembly). Provides the union of Protected and Friend access, offering maximum flexibility within controlled boundaries.

Code Example: The Secure BankAccount Class

This example will starkly contrast a poorly designed class that uses public fields with a properly encapsulated BankAccount class. The well-designed class will use private fields, public properties, and methods to control access and enforce business rules, such as preventing an overdrawn balance.

First, consider the insecure approach without proper encapsulation:

‘ WARNING: This is an example of POOR design. Do not use this approach.
Public Class InsecureBankAccount
‘ Public fields can be accessed and modified by anyone, from anywhere.
‘ There is no control, no validation, and no security.
Public AccountHolder As String
Public Balance As Decimal
End Class

Module InsecureExample
Sub DemonstrateProblem()
Dim insecureAccount As New InsecureBankAccount()
insecureAccount.AccountHolder = “John Doe”
insecureAccount.Balance = 100.0D

‘ THE PROBLEM: Direct modification is possible, leading to invalid data.
‘ Another part of the application could do this, corrupting the object’s state.
insecureAccount.Balance = -5000.0D ‘ A negative balance might be invalid.
insecureAccount.Balance = 999999999.0D ‘ Or a fraudulent amount.

‘ There is no protection for the object’s data integrity.
Console.WriteLine($”Insecure balance: {insecureAccount.Balance:C}”)
End Sub
End Module

Now, observe the secure, encapsulated approach, which is the correct way to design the class:

‘ =============================================================================
‘ ENCAPSULATED CLASS: The Correct Approach
‘ This class properly encapsulates its data, protecting its integrity.
‘ =============================================================================
Public Class BankAccount

‘ =========================================================================
‘ 1. PRIVATE FIELDS: The internal state is hidden and protected.

‘ Only code within the BankAccount class can directly access these fields.
‘ =========================================================================
Private _accountHolder As String
Private _balance As Decimal
Private _accountNumber As String

‘ =========================================================================
‘ 2. PUBLIC READ-ONLY PROPERTIES: Controlled exposure of data.

‘ We expose data that is safe to read but should not be changed from the
‘ outside. A ‘ReadOnly’ property only has a ‘Get’ accessor.
‘ =========================================================================
Public ReadOnly Property AccountHolder As String
Get
Return _accountHolder
End Get
End Property

Public ReadOnly Property Balance As Decimal
Get
Return _balance
End Get
End Property

Public ReadOnly Property AccountNumber As String
Get
Return _accountNumber
End Get
End Property

‘ =========================================================================
‘ 3. CONSTRUCTOR: Initializes the object to a valid state.

‘ The constructor is the only place where initial values for the read-only
‘ properties are set.
‘ =========================================================================
Public Sub New(accountNumber As String, accountHolder As String, initialBalance As Decimal)
Me._accountNumber = accountNumber
Me._accountHolder = accountHolder
‘ Ensure the initial balance is not negative.
If initialBalance >= 0 Then
Me._balance = initialBalance
Else
Me._balance = 0
End If
End Sub

‘ =========================================================================
‘ 4. PUBLIC METHODS: The controlled interface for actions.

‘ These methods define the ONLY ways to change the account’s balance.
‘ They contain business logic to ensure all changes are valid.
‘ =========================================================================

”’

”’ Adds a specified amount to the account balance.
”’

”’The positive amount to deposit. Public Sub Deposit(amount As Decimal)
‘ Business Rule: Cannot deposit a negative or zero amount.
If amount > 0 Then
_balance += amount
Console.WriteLine($”Deposit successful. New balance: {_balance:C}”)
Else
Console.WriteLine(“Deposit amount must be positive.”)
End If
End Sub

”’

”’ Withdraws a specified amount from the account balance, if funds are sufficient.
”’

”’The positive amount to withdraw. Public Sub Withdraw(amount As Decimal)
‘ Business Rule: Cannot withdraw a negative or zero amount.
If amount <= 0 Then Console.WriteLine(“Withdrawal amount must be positive.”) Return ‘ Exit the method End If ‘ Business Rule: Cannot withdraw more than the current balance. If amount > _balance Then
Console.WriteLine(“Withdrawal failed. Insufficient funds.”)
Else
_balance -= amount
Console.WriteLine($”Withdrawal successful. New balance: {_balance:C}”)
End If
End Sub
End Class

‘ =============================================================================
‘ MODULE: Demonstrating Secure Usage
‘ =============================================================================
Module SecureExample
Sub Main()
‘ Create a new, valid BankAccount object.
Dim myAccount As New BankAccount(“ACCT-001”, “Jane Smith”, 500.0D)

‘ We can READ the balance through the public property.
Console.WriteLine($”Initial balance for {myAccount.AccountHolder} is {myAccount.Balance:C}”)

‘ We CANNOT WRITE to the balance directly. The following line would cause a compile error.
‘ myAccount.Balance = 10000.0D  <– COMPILE ERROR: Property ‘Balance’ is ‘ReadOnly’.

‘ All interactions must go through the public methods.
Console.WriteLine()
myAccount.Deposit(200.0D)    ‘ OK
myAccount.Withdraw(150.0D)   ‘ OK
myAccount.Withdraw(600.0D)   ‘ Fails due to insufficient funds rule.
myAccount.Deposit(-50.0D)    ‘ Fails due to positive amount rule.

Console.WriteLine()
Console.WriteLine($”Final balance: {myAccount.Balance:C}”)

Console.ReadKey()
End Sub
End Module

In this secure example, the BankAccount class is in complete control of its own state. The private _balance field can only be changed by the Deposit and Withdraw methods, which contain the necessary validation and business logic. This is the power and purpose of encapsulation.

Section 3: The Second Pillar – Inheritance

Inheritance is a fundamental OOP concept that allows a new class to be based on an existing class. This mechanism forms the basis of creating class hierarchies, enabling elegant and efficient code reuse. It models the “is-a” relationships that are common in real-world domains, for example, a Manager is an Employee, or a Car is a Vehicle.

The “Is-A” Relationship and Code Reusability

When a class, known as the derived class or child class, inherits from another class, known as the base class or parent class, it automatically acquires all of the Public and Protected members (fields, properties, and methods) of the base class. This is the core of code reusability in OOP. Instead of copying and pasting code or rewriting common functionality, you define it once in a base class, and all derived classes receive it automatically. This not only saves significant development time but also makes maintenance easier. A bug fix or an enhancement made in the base class is instantly available to all derived classes.

This approach is profoundly different from the design limitations of languages that lack multiple inheritance, like C++’s “diamond problem.” VB.NET and the.NET Framework deliberately enforce single inheritance for classes to avoid such complexity. This design choice is not a limitation but a feature that promotes cleaner, more predictable architectures. To achieve the goals often associated with multiple inheritance—that is, to allow a class to take on multiple behaviors or contracts—the framework provides a more robust and flexible mechanism: interfaces. A class can only inherit the implementation from one parent, but it can promise to fulfill the contracts of multiple interfaces. For instance, a FlyingCar class presents a design challenge for single inheritance: should it inherit from Car or Airplane? A better design is to have it inherit from a base Vehicle class (to get common properties like Color and Weight) and also implement both an IDrivable interface and an IFlyable interface. This composition-over-inheritance approach, encouraged by the single-inheritance model, leads to more flexible and decoupled designs.

Implementation in VB.NET

VB.NET provides a clear and explicit set of keywords to manage inheritance.

  • Inherits Keyword: This keyword is used in the declaration of the derived class to specify its base class. It must be the first line after the class declaration.
    ‘ Manager is the derived class, Employee is the base class.
    Public Class Manager
    Inherits Employee
    ‘… class members
    End Class
  • MyBase Keyword: Within a derived class, the MyBase keyword provides a reference to the immediate base class. It is used to call members of the base class, which is particularly important in two scenarios :
  1. Calling Base Class Constructors: A derived class constructor must call a constructor of its base class. If the base class has a parameterized constructor, MyBase.New(…) must be the first line in the derived class’s constructor.
  2. Extending Overridden Methods: When overriding a base class method, you might want to execute the base class’s logic in addition to the new logic. MyBase.MethodName() allows you to do this.
  • Controlling Inheritance:
  • NotInheritable: This modifier, equivalent to sealed in C# and final in Java, prevents a class from being used as a base class. It marks the end of an inheritance chain. String is a common example of a NotInheritable class.
  • MustInherit: This modifier, equivalent to abstract in other languages, specifies that a class is incomplete and is intended only to be a base for other classes. A MustInherit class cannot be instantiated directly with the New keyword. It serves as a blueprint that may contain both implemented and unimplemented members. This concept is central to Abstraction and will be explored further in Section 5.

Code Example: The Employee and Manager Hierarchy

The following practical example demonstrates inheritance by modeling a simple employee hierarchy. We will create a base Employee class with common properties and methods, and then a Manager class that derives from it, inheriting the common features and adding its own specific ones.

‘ =============================================================================
‘ BASE CLASS: Employee
‘ This class defines the common properties and behaviors for ALL employees.
‘ =============================================================================
Public Class Employee
‘ Protected fields are accessible within this class AND in any derived class.
‘ This is a better choice than Private if child classes need direct access.
Protected _employeeID As Integer
Protected _name As String
Protected _baseSalary As Decimal

‘ Public properties to expose the data in a controlled way.
Public Property EmployeeID As Integer
Get
Return _employeeID
End Get
Set(value As Integer)
_employeeID = value
End Set
End Property

Public Property Name As String
Get
Return _name
End Get
Set(value As String)
_name = value
End Set
End Property

Public Property BaseSalary As Decimal
Get
Return _baseSalary
End Get
Set(value As Decimal)
If value >= 0 Then
_baseSalary = value
End If
End Set
End Property

‘ CONSTRUCTOR for the base class.
Public Sub New(id As Integer, name As String, salary As Decimal)
Me.EmployeeID = id
Me.Name = name
Me.BaseSalary = salary
Console.WriteLine($”Employee object created for {Me.Name}.”)
End Sub

‘ A common method for all employees.
‘ It is marked ‘Overridable’ so derived classes can provide their own
‘ specific implementation if needed (see Polymorphism, Section 4).
Public Overridable Function CalculateAnnualPay() As Decimal
‘ Default calculation is just the base salary times 12.
Return BaseSalary * 12
End Function

‘ Another common method.
Public Sub DisplayInfo()
Console.WriteLine(“——————–“)
Console.WriteLine($”ID: {EmployeeID}”)
Console.WriteLine($”Name: {Name}”)
Console.WriteLine($”Base Monthly Salary: {BaseSalary:C}”)
End Sub
End Class

‘ =============================================================================
‘ DERIVED CLASS: Manager
‘ A Manager ‘is-an’ Employee. It inherits from the Employee class.
‘ =============================================================================
Public Class Manager
Inherits Employee ‘ This is the inheritance relationship.

‘ A Manager has an additional property that a regular Employee does not.
Private _annualBonus As Decimal

Public Property AnnualBonus As Decimal
Get
Return _annualBonus
End Get
Set(value As Decimal)
If value >= 0 Then
_annualBonus = value
End If
End Set
End Property

‘ CONSTRUCTOR for the derived class.
‘ It takes all the parameters needed for itself and its base class.
Public Sub New(id As Integer, name As String, salary As Decimal, bonus As Decimal)
‘ The FIRST line MUST be a call to the base class constructor using ‘MyBase.New’.
‘ This ensures the base ‘Employee’ part of the ‘Manager’ object is initialized correctly.
MyBase.New(id, name, salary)

‘ Now, initialize the members specific to the Manager class.
Me.AnnualBonus = bonus
Console.WriteLine($” -> And promoted to Manager with a bonus of {Me.AnnualBonus:C}.”)
End Sub

‘ We will discuss this in the Polymorphism section, but for now, this shows
‘ how a derived class can provide its own version of a base class method.
Public Overrides Function CalculateAnnualPay() As Decimal
‘ A manager’s annual pay is their base pay PLUS their bonus.
‘ We can call the base class’s version of the function to reuse its logic.
Dim basePay As Decimal = MyBase.CalculateAnnualPay()
Return basePay + AnnualBonus
End Function
End Class

‘ =============================================================================
‘ MODULE: Demonstrating Inheritance in Action
‘ =============================================================================
Module InheritanceExample
Sub Main()
‘ Create an instance of the base class.
Dim emp1 As New Employee(101, “Alice”, 4000.0D)
emp1.DisplayInfo()
Console.WriteLine($”Alice’s Annual Pay: {emp1.CalculateAnnualPay():C}”)
Console.WriteLine()

‘ Create an instance of the derived class.
Dim mgr1 As New Manager(201, “Bob”, 7000.0D, 10000.0D)

‘ The ‘Manager’ object can use methods and properties defined in the
‘ ‘Employee’ class because it inherited them.
mgr1.DisplayInfo() ‘ This method was inherited directly from Employee.

‘ The Manager class has its own specific property.
Console.WriteLine($”Bob’s Annual Bonus: {mgr1.AnnualBonus:C}”)

‘ And it has its own version of the CalculateAnnualPay method.
Console.WriteLine($”Bob’s Annual Pay: {mgr1.CalculateAnnualPay():C}”)

Console.ReadKey()
End Sub
End Module

This example clearly shows how the Manager class reuses the EmployeeID, Name, BaseSalary, and DisplayInfo members from the Employee class without having to redefine them. It simply extends the functionality by adding an AnnualBonus and providing a specialized version of CalculateAnnualPay. This is the essence of inheritance: building upon existing code to create more specialized types.

Section 4: The Third Pillar – Polymorphism

Polymorphism, a term derived from Greek meaning “many forms,” is arguably the most powerful concept in Object-Oriented Programming. It is the ability of objects of different classes to respond to the same message (a method call) in different, type-specific ways. Polymorphism allows you to write code that is more generic, flexible, and extensible.

In VB.NET, polymorphism manifests in two distinct forms: compile-time polymorphism (achieved through method overloading) and run-time polymorphism (achieved through method overriding). While some sources use the term broadly, adopting this clear distinction is crucial for understanding its practical application.

Compile-Time Polymorphism (Method Overloading)

Also known as static binding or early binding, method overloading allows a class to have multiple methods with the same name, as long as each method has a unique signature. The method signature is defined by the number, type, and order of its parameters. The compiler determines which version of the method to call at compile-time based on the arguments provided in the method call.

  • Overloads Keyword: To implement method overloading in VB.NET, you must use the Overloads keyword in the declaration of each overloaded method. This explicitly tells the compiler that you intend to have multiple versions of the same method.

Code Example: The Overloaded Calculator

The following example demonstrates a Calculator class with multiple Add methods, each handling different data types.

‘ =============================================================================
‘ CLASS: Calculator
‘ This class demonstrates compile-time polymorphism through method overloading.
‘ =============================================================================
Public Class Calculator
‘ Method 1: Adds two integers.
Public Overloads Function Add(a As Integer, b As Integer) As Integer
Console.WriteLine(“-> Called Add(Integer, Integer)”)
Return a + b
End Function

‘ Method 2: Adds two doubles. Same name, different parameter types.
Public Overloads Function Add(a As Double, b As Double) As Double
Console.WriteLine(“-> Called Add(Double, Double)”)
Return a + b
End Function

‘ Method 3: Adds three integers. Same name, different number of parameters.
Public Overloads Function Add(a As Integer, b As Integer, c As Integer) As Integer
Console.WriteLine(“-> Called Add(Integer, Integer, Integer)”)
Return a + b + c
End Function

‘ Method 4: Concatenates two strings.
‘ This shows that the operation itself can be different, as long as the
‘ signature is unique.
Public Overloads Function Add(a As String, b As String) As String
Console.WriteLine(“-> Called Add(String, String)”)
Return a & ” ” & b
End Function
End Class

‘ =============================================================================
‘ MODULE: Demonstrating Overloading
‘ =============================================================================
Module OverloadingExample
Sub Main()
Dim calc As New Calculator()

‘ The compiler selects the correct ‘Add’ method based on the arguments.
‘ This decision is made at COMPILE-TIME.

Console.WriteLine(“Calling Add with two integers (5, 10)…”)
Dim intResult As Integer = calc.Add(5, 10)
Console.WriteLine($”Result: {intResult}” & vbCrLf)

Console.WriteLine(“Calling Add with two doubles (3.5, 7.2)…”)
Dim dblResult As Double = calc.Add(3.5, 7.2)
Console.WriteLine($”Result: {dblResult}” & vbCrLf)

Console.WriteLine(“Calling Add with three integers (1, 2, 3)…”)
Dim tripleIntResult As Integer = calc.Add(1, 2, 3)
Console.WriteLine($”Result: {tripleIntResult}” & vbCrLf)

Console.WriteLine(“Calling Add with two strings (‘Hello’, ‘World’)…”)
Dim strResult As String = calc.Add(“Hello”, “World”)
Console.WriteLine($”Result: {strResult}” & vbCrLf)

Console.ReadKey()
End Sub
End Module

Run-Time Polymorphism (Method Overriding)

Also known as dynamic binding or late binding, method overriding is the more profound form of polymorphism and is intrinsically linked to inheritance. It allows a derived class to provide a specific implementation for a method that is already defined in its base class. The crucial difference is that the decision of which method to execute is made at run-time, based on the actual type of the object, not the type of the variable that references it.

This is the cornerstone of building extensible and decoupled systems. It allows you to write code that operates on a base type (e.g., a Shape) without needing to know about any specific derived types (Circle, Square) that might exist or be created in the future. If a new Triangle class is added later, existing code that works with Shape objects will automatically work with Triangle objects without any modification. This adheres to the “Open/Closed Principle” of software design: a system should be open for extension (adding new shapes) but closed for modification (not changing existing, stable code).

  • Keyword Pair: Method overriding requires a partnership between the base and derived classes, using a specific pair of keywords.
  • Overridable: Used in the base class to mark a method as being available for overriding by derived classes.
  • Overrides: Used in the derived class to declare that it is providing its own implementation for an Overridable method from the base class.
  • Distinction from Shadows: It is important not to confuse Overrides with the Shadows keyword. Shadows re-declares and hides a base class member with a new one. It does not replace the base implementation in a polymorphic way. If you call a shadowed method through a base class reference, the base class’s version will be executed, not the derived class’s version. Shadows breaks polymorphism, whereas Overrides enables it.

Code Example: The Polymorphic Shape Hierarchy

This example demonstrates run-time polymorphism. We define a base Shape class and several derived classes. A single loop can then process a list containing different types of shapes, with each shape correctly calculating its own area.

‘ =============================================================================
‘ BASE CLASS: Shape
‘ Defines the common interface for all shapes.
‘ =============================================================================
Public Class Shape
‘ A property common to all shapes.
Public Property Name As String

‘ CONSTRUCTOR
Public Sub New(name As String)
Me.Name = name
End Sub

‘ THE POLYMORPHIC METHOD
‘ This function is marked as ‘Overridable’, which means that any class
‘ inheriting from Shape is allowed to provide its own specific version
‘ of this function. This is the contract for run-time polymorphism.
Public Overridable Function GetArea() As Double
‘ A generic shape has no area.
Console.WriteLine(“Warning: GetArea() called on a generic Shape.”)
Return 0
End Function
End Class

‘ =============================================================================
‘ DERIVED CLASS: Circle
‘ =============================================================================
Public Class Circle
Inherits Shape

Public Property Radius As Double

‘ Constructor calls the base constructor.
Public Sub New(radius As Double)
MyBase.New(“Circle”) ‘ Pass the name up to the Shape class.
Me.Radius = radius
End Sub

‘ METHOD OVERRIDING
‘ The ‘Overrides’ keyword replaces the base class’s GetArea() implementation
‘ with a specific calculation for a circle.
Public Overrides Function GetArea() As Double
Return Math.PI * (Radius ^ 2)
End Function
End Class

‘ =============================================================================
‘ DERIVED CLASS: Rectangle
‘ =============================================================================
Public Class Rectangle
Inherits Shape

Public Property Width As Double
Public Property Height As Double

‘ Constructor
Public Sub New(width As Double, height As Double)
MyBase.New(“Rectangle”)
Me.Width = width
Me.Height = height
End Sub

‘ METHOD OVERRIDING
‘ This provides the specific area calculation for a rectangle.
Public Overrides Function GetArea() As Double
Return Width * Height
End Function
End Class

‘ =============================================================================
‘ MODULE: Demonstrating Run-Time Polymorphism
‘ =============================================================================
Module PolymorphismExample
Sub Main()
‘ Create a list that can hold Shape objects.
‘ Because of inheritance, this list can hold any object that ‘is-a’ Shape,
‘ including Circle and Rectangle objects.
Dim shapes As New List(Of Shape)

‘ Add different types of shapes to the same list.
shapes.Add(New Circle(5.0))       ‘ Radius 5
shapes.Add(New Rectangle(4.0, 6.0)) ‘ Width 4, Height 6
shapes.Add(New Rectangle(10.0, 10.0)) ‘ A square
shapes.Add(New Circle(1.5))       ‘ Radius 1.5

Console.WriteLine(“Calculating areas of all shapes in the list…”)
Console.WriteLine(“———————————————“)

‘ This loop demonstrates the power of polymorphism.
For Each currentShape In shapes
‘ The ‘currentShape’ variable is of type ‘Shape’.
‘ However, when ‘currentShape.GetArea()’ is called, the.NET runtime
‘ checks the ACTUAL type of the object in memory (Circle or Rectangle)
‘ and calls the appropriate OVERRIDDEN method. This is late binding.
‘ The code in this loop does not need to know or care what the
‘ specific shape types are.
Dim area As Double = currentShape.GetArea()
Console.WriteLine($”Area of {currentShape.Name} is {area:F2}”)
End For

‘ If we add a new ‘Triangle’ class that inherits from Shape and overrides
‘ GetArea(), this loop would work with it WITHOUT ANY CHANGES.

Console.ReadKey()
End Sub
End Module

Section 5: The Fourth Pillar – Abstraction

Abstraction is the principle of simplifying complex reality by modeling classes appropriate to the problem, exposing only the essential features of an object while hiding the unnecessary implementation details. It is about defining a contract of what an object can do, without specifying how it does it.

While closely related to encapsulation, abstraction operates at a higher level. Encapsulation is about data hiding—bundling data and methods and protecting the data with access modifiers. Abstraction is about complexity hiding—providing a simplified view of an object that conceals the intricate machinery within. For example, when you drive a car, you interact with an abstraction: a steering wheel, pedals, and a gearshift. You don’t need to know about the internal combustion engine, the transmission, or the electronics. Abstraction provides you with the essential controls and hides the rest.

In VB.NET, abstraction is primarily achieved through two powerful mechanisms: abstract classes and interfaces.

Mechanism 1: Abstract Classes (MustInherit)

An abstract class is a special type of class that cannot be instantiated on its own. It is declared using the MustInherit keyword and is designed exclusively to serve as a base class (a blueprint) for other classes to inherit from.

An abstract class is a hybrid: it can contain both fully implemented (concrete) members and abstract members that have no implementation.

  • Abstract Methods (MustOverride): A method within an abstract class can be declared with the MustOverride keyword. This signifies that the method has no implementation (no code body) in the abstract class. Any concrete (non-abstract) class that inherits from this abstract class is then forced to provide its own implementation for that method using the Overrides keyword. This enforces a contract on all child classes, guaranteeing that they will have a specific capability.

Mechanism 2: Interfaces (Interface and Implements)

An interface is a pure contract. It is a reference type that contains only the signatures of members—methods, properties, events, and indexers. An interface contains absolutely no implementation code.

A class can choose to implement one or more interfaces using the Implements keyword. When a class implements an interface, it makes a promise to the compiler that it will provide a concrete implementation for every single member defined in that interface. This is how VB.NET achieves the benefits of multiple inheritance; a class can inherit the implementation of one base class while also inheriting the contracts of multiple interfaces.

Valuable Table: Abstract Class vs. Interface

The choice between using an abstract class and an interface is a fundamental architectural decision in OOP. They are not interchangeable, and understanding their differences is key to designing robust and flexible systems. This table provides a clear comparison to guide that decision.

Feature Abstract Class (MustInherit) Interface (Interface)
Implementation Can contain a mix of implemented (concrete) members and abstract (MustOverride) members. Cannot contain any implementation code. It only defines member signatures (the contract).
Inheritance A class can inherit from only ONE abstract (or concrete) class. This is single inheritance. A class can implement MULTIPLE interfaces.
Constructors Can have constructors. These are called by derived class constructors using MyBase.New() to initialize the base part of the object. Cannot have constructors, as they cannot be instantiated.
Fields Can declare and use fields (member variables) to store state. Cannot contain fields.
Access Modifiers Members can have any access modifier (Public, Protected, Private, etc.). All members are implicitly Public, as an interface defines a public contract.
Purpose / “When to Use” Use for an “is-a” relationship. Best when creating a base for a family of closely related classes that share significant common code and functionality. Example: Dog and Cat inheriting from an abstract Animal class. Use for a “can-do” relationship. Best when defining a capability or contract that can be implemented by a wide range of otherwise unrelated classes. Example: FileLogger, DatabaseLogger, and EmailNotifier all implementing an ILoggable interface.

The decision framework is this: if you are modeling a family of objects that share not just a contract but also a substantial amount of implementation code, use an abstract class to avoid code duplication. If you are defining a capability that can be applied to many different kinds of objects, which may have completely different implementations of that capability, use an interface.

Code Example: IDrivable and Vehicle

This example demonstrates how abstract classes and interfaces can work together to create a flexible and well-designed system. We will model various things that can be driven.

‘ =============================================================================
‘ INTERFACE: IDrivable
‘ This defines a “can-do” contract. Anything that implements this interface
‘ promises that it has the capability to be driven. It is a pure contract
‘ with no implementation.
‘ =============================================================================
Public Interface IDrivable
‘ Any class that implements IDrivable MUST provide these members.
Sub StartEngine()
Sub StopEngine()
Sub Accelerate(amount As Integer)
Sub Brake(amount As Integer)
ReadOnly Property CurrentSpeed As Integer
End Interface

‘ =============================================================================
‘ ABSTRACT CLASS: Vehicle
‘ This defines an “is-a” relationship for a family of related objects.
‘ It is ‘MustInherit’, so you cannot create a ‘Vehicle’ object directly.
‘ It provides some shared, concrete functionality for all vehicles.
‘ =============================================================================
Public MustInherit Class Vehicle
‘ A concrete property shared by all vehicles.
Public Property Color As String

‘ A constructor to be used by derived classes.
Public Sub New(color As String)
Me.Color = color
End Sub

‘ A concrete method shared by all vehicles.
Public Sub HonkHorn()
Console.WriteLine(“Beep! Beep!”)
End Sub

‘ An abstract method. It defines a behavior that all vehicles must have,
‘ but each type of vehicle will implement it differently. There is no
‘ code body here.
Public MustOverride Sub DisplayDetails()
End Class

‘ =============================================================================
‘ CONCRETE CLASS: Car
‘ A Car ‘is-a’ Vehicle and it ‘can-do’ driving (it is IDrivable).
‘ It inherits from ONE class and implements ONE interface.
‘ =============================================================================
Public Class Car
Inherits Vehicle
Implements IDrivable

‘ Private field for the interface implementation.
Private _currentSpeed As Integer = 0

‘ Properties specific to a Car.
Public Property Make As String
Public Property Model As String

‘ Constructor for the Car class.
Public Sub New(make As String, model As String, color As String)
‘ Call the base class (Vehicle) constructor first.
MyBase.New(color)
Me.Make = make
Me.Model = model
End Sub

‘ =========================================================================
‘ IMPLEMENTATION of the Vehicle’s ABSTRACT method
‘ =========================================================================
Public Overrides Sub DisplayDetails()
Console.WriteLine($”This is a {Color} {Make} {Model}.”)
End Sub

‘ =========================================================================
‘ IMPLEMENTATION of the IDrivable INTERFACE members
‘ The ‘Implements’ keyword links the method to the interface member.
‘ =========================================================================
Public Sub StartEngine() Implements IDrivable.StartEngine
Console.WriteLine(“Car engine started. Vroom!”)
End Sub

Public Sub StopEngine() Implements IDrivable.StopEngine
Console.WriteLine(“Car engine stopped.”)
_currentSpeed = 0
End Sub

Public Sub Accelerate(amount As Integer) Implements IDrivable.Accelerate
_currentSpeed += amount
Console.WriteLine($”Car accelerating. Current speed: {_currentSpeed} km/h”)
End Sub

Public Sub Brake(amount As Integer) Implements IDrivable.Brake
_currentSpeed -= amount
If _currentSpeed < 0 Then _currentSpeed = 0
Console.WriteLine($”Car braking. Current speed: {_currentSpeed} km/h”)
End Sub

Public ReadOnly Property CurrentSpeed As Integer Implements IDrivable.CurrentSpeed
Get
Return _currentSpeed
End Get
End Property
End Class

‘ =============================================================================
‘ MODULE: Demonstrating Abstraction
‘ =============================================================================
Module AbstractionExample
Sub Main()
‘ Create an instance of our concrete class.
Dim myCar As New Car(“Toyota”, “Camry”, “Blue”)

‘ Use methods from the base Vehicle class.
myCar.HonkHorn()

‘ Use the overridden method.
myCar.DisplayDetails()

Console.WriteLine()

‘ Now, let’s treat the car purely as something that is drivable.
‘ We can create a variable of the INTERFACE type.
Dim drivableObject As IDrivable = myCar

‘ We can now only access the members defined in the IDrivable interface.
‘ The following line would cause a compile error because HonkHorn() is
‘ not part of the IDrivable contract.
‘ drivableObject.HonkHorn()  <– COMPILE ERROR

Console.WriteLine(“Interacting with the car through the IDrivable interface…”)
drivableObject.StartEngine()
drivableObject.Accelerate(50)
Console.WriteLine($”Speed check: {drivableObject.CurrentSpeed} km/h”)
drivableObject.Brake(20)
drivableObject.StopEngine()

Console.ReadKey()
End Sub
End Module

Section 6: Advanced Class Concepts

Beyond the four primary pillars of OOP, several other features of classes in VB.NET are essential for building robust, real-world applications. These include constructors, which manage the birth of an object, and shared members, which provide class-level functionality independent of any single instance.

Constructors (Sub New): The Birth of an Object

A constructor is a special method within a class that is executed automatically whenever a new object of that class is created (instantiated). In VB.NET, the constructor is always named New and is defined as a Sub, as it never returns a value. Its primary responsibility is to initialize the object, ensuring it starts in a valid and predictable state.

  • Default (Parameterless) Constructor: This is a constructor that takes no parameters. If you do not explicitly define any constructors in your class, the VB.NET compiler provides a hidden, public, parameterless constructor for you. This allows you to create an object with Dim myObj As New MyClass().
    Public Sub New()
    ‘ This is a default constructor.
    ‘ Initialize default values here.
    Console.WriteLine(“Object created with default constructor.”)
    End Sub
  • Parameterized Constructor: This is the most common and useful type of constructor. It accepts one or more parameters, allowing you to pass in initial values when the object is created. This ensures that the object is born with the data it needs to be valid from the very beginning.
    Public Sub New(ByVal name As String)
    ‘ This is a parameterized constructor.
    Me._name = name ‘ Initialize the object’s state.
    Console.WriteLine($”Object created with name: {name}”)
    End Sub
  • Overloaded Constructors: A class can have multiple constructors, each with a different signature (i.e., a different list of parameters). This provides flexibility in how an object can be created. For example, a Book object could be created with just a title, or with a title and an author.
    Public Class Book
    Private _title As String
    Private _author As String    ‘ Overload 1: Takes only a title.
    Public Sub New(title As String)
    Me.New(title, “Unknown Author”) ‘ Calls the other constructor.
    End Sub’ Overload 2: Takes a title and an author.
    Public Sub New(title As String, author As String)
    Me._title = title
    Me._author = author
    End Sub
    End Class

Shared Members (Shared): Class-Level Data and Methods

In contrast to instance members, which belong to a specific object, a shared member belongs to the class itself. In other languages, this concept is known as static. There is only one copy of a shared member in memory, regardless of how many instances of the class are created (or even if no instances are created at all).

Shared members are a bridge between procedural and object-oriented programming. They allow for functionality that is not tied to the state of a particular object, but they keep this functionality neatly organized and scoped within a class, avoiding the pitfalls of true global variables and functions. A crucial rule to understand is that a shared method cannot access non-shared (instance) members (e.g., Me.Name), because a shared method is not associated with any specific Me instance. The call would be ambiguous. Conversely, an instance method can access shared members, because there is only one copy of the shared member, making the reference unambiguous.

  • Shared Fields and Properties: These are used for data that is common to all instances of a class. Examples include application-wide constants, configuration settings, or a counter that tracks how many objects of a class have been created.
  • Shared Methods: These are utility functions that do not depend on the state of a specific object. They are called using the class name directly, not an instance variable. The Math.Sqrt() function is a perfect example; you call Math.Sqrt(25) without ever creating an instance of the Math class.
  • Shared Constructor (Shared Sub New): This is a special, parameterless constructor that is guaranteed by the.NET runtime to be executed only once, before any instances of the class are created and before any other shared members are accessed. Its sole purpose is to perform any complex initialization required for shared fields.

Code Example: Logger Class with Shared Members

This example shows a Logger class that uses shared members to provide a single, application-wide logging mechanism.

‘ =============================================================================
‘ CLASS: Logger
‘ This class uses shared members to provide a centralized logging service
‘ without needing to create an instance of the Logger.
‘ =============================================================================
Public Class Logger
‘ =========================================================================
‘ SHARED FIELD: This field belongs to the Logger class itself, not to
‘ any one instance. There will only ever be ONE copy of this list.
‘ =========================================================================
Private Shared _logMessages As New List(Of String)

‘ =========================================================================
‘ SHARED CONSTRUCTOR: Initializes shared members.
‘ This runs automatically, only ONCE, before the Logger class is first used.
‘ It is always parameterless and implicitly private.
‘ =========================================================================
Shared Sub New()
‘ Initialize our shared state.
_logMessages.Add(“Log Initialized at ” & DateTime.Now.ToString())
End Sub

‘ =========================================================================
‘ SHARED METHOD: A utility function accessible via the class name.
‘ Notice it is ‘Shared’. It cannot access any non-shared (instance) members.
‘ =========================================================================
Public Shared Sub WriteLog(message As String)
Dim logEntry As String = $”{DateTime.Now:HH:mm:ss} – {message}”
_logMessages.Add(logEntry)
End Sub

‘ =========================================================================
‘ SHARED METHOD: Another utility function.
‘ =========================================================================
Public Shared Sub DisplayAllLogs()
Console.WriteLine(“— Application Log —“)
For Each msg In _logMessages
Console.WriteLine(msg)
Next
Console.WriteLine(“———————–“)
End Sub
End Class

‘ =============================================================================
‘ MODULE: Demonstrating Shared Member Usage
‘ =============================================================================
Module SharedMembersExample
Sub Main()
‘ We do NOT create an instance of the Logger class like this:
‘ Dim myLogger As New Logger()

‘ Instead, we call the SHARED methods directly on the CLASS NAME.
Logger.WriteLog(“Application starting…”)

‘ Do some work…
Console.WriteLine(“Performing some application tasks.”)
Logger.WriteLog(“Task A completed.”)

‘ Do more work…
Console.WriteLine(“Performing more tasks.”)
Logger.WriteLog(“Task B encountered a minor issue.”)

‘ Display the logs at the end.
Logger.DisplayAllLogs()

Console.ReadKey()
End Sub
End Module

Section 7: Synthesis – A Practical OOP Project in VB.NET

Theory is essential, but true understanding comes from application. This final section synthesizes all the preceding OOP concepts—Encapsulation, Inheritance, Polymorphism, and Abstraction, along with constructors and shared members—into a single, cohesive, and practical project. We will build a simple console-based banking application that models accounts and transactions, demonstrating how these principles work together to create a system that is robust, scalable, and easy to maintain.

This project will showcase the power of programming to an interface, not an implementation. The central Bank class will be designed to work with any object that fulfills the IAccount contract. It will not know or care about the specific details of a CheckingAccount or a SavingsAccount. This decoupling is the ultimate payoff of a well-designed OOP system, as it allows new account types (e.g., a LoanAccount) to be added in the future with zero changes to the existing Bank class.

Project Goal

To model a simple banking system with different account types. The system must allow for creating accounts, depositing and withdrawing funds, and displaying account summaries. The design must be extensible to allow for new account types in the future with minimal code changes.

System Components (Classes and Interfaces)

1. The IAccount Interface (Abstraction)

This interface defines the public contract—the essential capabilities—that any account type in our system must have. It is pure abstraction.

”’

”’ Defines the essential contract for any bank account.
”’ Any class that implements this interface promises to provide these members.
”’ This is an example of ABSTRACTION (“can-do” relationship).
”’

Public Interface IAccount
ReadOnly Property AccountNumber As String
ReadOnly Property AccountHolder As String
ReadOnly Property Balance As Decimal
Sub Deposit(amount As Decimal)
Function Withdraw(amount As Decimal) As Boolean
Sub DisplaySummary()
End Interface

2. The Account Abstract Base Class (Inheritance, Abstraction, Encapsulation)

This MustInherit class serves as the foundation for all specific account types. It provides the shared implementation and data that are common to all accounts.

”’

”’ An ABSTRACT base class for all account types. It provides shared functionality.
”’ It ‘Implements IAccount’ to fulfill the contract, but can leave some parts
”’ for derived classes to complete. This is INHERITANCE and ABSTRACTION.
”’

Public MustInherit Class Account
Implements IAccount

‘ ENCAPSULATION: Internal state is protected. Child classes can access them.
Protected _accountNumber As String
Protected _accountHolder As String
Protected _balance As Decimal

‘ SHARED MEMBER: Belongs to the class, used to generate unique account numbers.
Private Shared _nextAccountNumber As Integer = 1001

‘ CONSTRUCTOR: Protected so only derived classes can call it.
Public Sub New(accountHolder As String, initialBalance As Decimal)
Me._accountNumber = “ACCT-” & _nextAccountNumber.ToString()
_nextAccountNumber += 1 ‘ Increment for the next account
Me._accountHolder = accountHolder
If initialBalance >= 0 Then
Me._balance = initialBalance
End If
End Sub

‘ INTERFACE IMPLEMENTATION (Concrete)
Public ReadOnly Property AccountNumber As String Implements IAccount.AccountNumber
Get
Return _accountNumber
End Get
End Property

Public ReadOnly Property AccountHolder As String Implements IAccount.AccountHolder
Get
Return _accountHolder
End Get
End Property

Public ReadOnly Property Balance As Decimal Implements IAccount.Balance
Get
Return _balance
End Get
End Property

‘ Common implementation shared by all account types.
Public Sub Deposit(amount As Decimal) Implements IAccount.Deposit
If amount > 0 Then
_balance += amount
Console.WriteLine($”Deposited {amount:C}. New Balance: {_balance:C}”)
Else
Console.WriteLine(“Deposit amount must be positive.”)
End If
End Sub

‘ POLYMORPHISM & ABSTRACTION: This method is Overridable. Derived classes
‘ can provide their own withdrawal rules.
Public Overridable Function Withdraw(amount As Decimal) As Boolean Implements IAccount.Withdraw
If amount <= 0 Then Console.WriteLine(“Withdrawal amount must be positive.”) Return False End If If amount > _balance Then
Console.WriteLine(“Withdrawal failed. Insufficient funds.”)
Return False
Else
_balance -= amount
Console.WriteLine($”Withdrew {amount:C}. New Balance: {_balance:C}”)
Return True
End If
End Function

‘ Common display logic.
Public Overridable Sub DisplaySummary() Implements IAccount.DisplaySummary
Console.WriteLine($”Account #: {AccountNumber}”)
Console.WriteLine($”Holder: {AccountHolder}”)
Console.WriteLine($”Balance: {Balance:C}”)
End Sub
End Class

3. The CheckingAccount and SavingsAccount Derived Classes (Inheritance, Polymorphism)

These are the concrete implementations of different account types. They inherit the common logic from Account and provide their own specializations.

”’

”’ A concrete CheckingAccount class. It INHERITS from Account.
”’

Public Class CheckingAccount
Inherits Account

Private Const OVERDRAFT_FEE As Decimal = 35.0D

‘ Constructor calls the base constructor.
Public Sub New(accountHolder As String, initialBalance As Decimal)
MyBase.New(accountHolder, initialBalance)
End Sub

‘ POLYMORPHISM: Overriding the Withdraw method to add specific logic for overdrafts.
Public Overrides Function Withdraw(amount As Decimal) As Boolean
If amount <= 0 Then Console.WriteLine(“Withdrawal amount must be positive.”) Return False End If ‘ A checking account allows overdraft but applies a fee. If amount > _balance Then
Console.WriteLine(“Insufficient funds. Applying overdraft fee.”)
_balance -= OVERDRAFT_FEE
Console.WriteLine($”Overdraft fee of {OVERDRAFT_FEE:C} applied. New Balance: {_balance:C}”)
Return False
Else
‘ If funds are sufficient, use the base class’s logic.
Return MyBase.Withdraw(amount)
End If
End Function
End Class

”’

”’ A concrete SavingsAccount class. It INHERITS from Account.
”’

Public Class SavingsAccount
Inherits Account

Private _interestRate As Decimal

‘ Constructor
Public Sub New(accountHolder As String, initialBalance As Decimal, interestRate As Decimal)
MyBase.New(accountHolder, initialBalance)
Me._interestRate = interestRate
End Sub

‘ A method specific to SavingsAccount
Public Sub ApplyInterest()
Dim interestEarned As Decimal = _balance * _interestRate
_balance += interestEarned
Console.WriteLine($”Interest of {interestEarned:C} applied. New Balance: {_balance:C}”)
End Sub

‘ POLYMORPHISM: Override DisplaySummary to show extra info.
Public Overrides Sub DisplaySummary()
MyBase.DisplaySummary() ‘ Reuse the base display logic
Console.WriteLine($”Interest Rate: {_interestRate:P}”)
End Sub
End Class

4. The Bank Class (Composition and Polymorphism)

This class manages a collection of accounts. It demonstrates composition (a bank “has-a” list of accounts) and the power of programming to an interface.

”’

”’ Manages a collection of bank accounts. It works with the IAccount interface,
”’ making it decoupled from the concrete account types.
”’

Public Class Bank
‘ COMPOSITION: The Bank “has-a” list of accounts.
‘ PROGRAMMING TO AN INTERFACE: The list holds IAccount, not a specific type.
‘ This allows the bank to manage ANY object that implements IAccount.
Private _accounts As New List(Of IAccount)

Public Sub AddAccount(account As IAccount)
_accounts.Add(account)
Console.WriteLine($”New account {account.AccountNumber} for opened.”)
End Sub

Public Function FindAccount(accountNumber As String) As IAccount
Return _accounts.Find(Function(acc) acc.AccountNumber = accountNumber)
End Function

Public Sub DisplayAllAccountSummaries()
Console.WriteLine(vbCrLf & “===== Bank-Wide Account Summary =====”)
For Each acc In _accounts
‘ POLYMORPHISM in action. The correct DisplaySummary for each
‘ account type is called automatically.
acc.DisplaySummary()
Console.WriteLine(“——————–“)
Next
Console.WriteLine(“===================================” & vbCrLf)
End Sub
End Class

5. The Module Program (Main Entry Point)

This is the main application driver that puts all the pieces together.

Module BankingSystem
Sub Main()
‘ 1. Create the main Bank object.
Dim myBank As New Bank()
Console.WriteLine(“Welcome to the OOP Bank!”)
Console.WriteLine(“————————” & vbCrLf)

‘ 2. Create and add different types of accounts.
‘    The Bank’s AddAccount method accepts any object that is an IAccount.
Dim checking As New CheckingAccount(“John Doe”, 1000.0D)
myBank.AddAccount(checking)

Dim savings As New SavingsAccount(“Jane Smith”, 5000.0D, 0.02D) ‘ 2% interest
myBank.AddAccount(savings)
Console.WriteLine()

‘ 3. Display initial summaries.
myBank.DisplayAllAccountSummaries()

‘ 4. Perform transactions using the specific account objects.
Console.WriteLine(“>>> Performing transactions for John Doe…”)
checking.Deposit(200.0D)
checking.Withdraw(50.0D)
checking.Withdraw(1200.0D) ‘ This will trigger the overdraft logic.
Console.WriteLine()

Console.WriteLine(“>>> Performing transactions for Jane Smith…”)
savings.Withdraw(1000.0D)
‘ The following line would cause a compile error because the base Account
‘ class does not have an ApplyInterest method. We need the specific type.
‘ savings.ApplyInterest()
‘ To call a method specific to a derived class, we must use that type.
Dim janesSavingsAccount As SavingsAccount = CType(myBank.FindAccount(savings.AccountNumber), SavingsAccount)
If janesSavingsAccount IsNot Nothing Then
janesSavingsAccount.ApplyInterest()
End If
Console.WriteLine()

‘ 5. Display final summaries.
myBank.DisplayAllAccountSummaries()

Console.WriteLine(“Bank operations complete. Press any key to exit.”)
Console.ReadKey()
End Sub
End Module

This final project illustrates that the principles of OOP are not just theoretical constructs. They are practical tools that, when used together, lead to software that is logical, secure, reusable, and, most importantly, adaptable to future change.

About the Author: Bernard Aybout (Virii8)

Avatar of Bernard Aybout (Virii8)
I am a dedicated technology enthusiast with over 45 years of life experience, passionate about computers, AI, emerging technologies, and their real-world impact. As the founder of my personal blog, MiltonMarketing.com, I explore how AI, health tech, engineering, finance, and other advanced fields leverage innovation—not as a replacement for human expertise, but as a tool to enhance it. My focus is on bridging the gap between cutting-edge technology and practical applications, ensuring ethical, responsible, and transformative use across industries. MiltonMarketing.com is more than just a tech blog—it's a growing platform for expert insights. We welcome qualified writers and industry professionals from IT, AI, healthcare, engineering, HVAC, automotive, finance, and beyond to contribute their knowledge. If you have expertise to share in how AI and technology shape industries while complementing human skills, join us in driving meaningful conversations about the future of innovation. 🚀