Lesson 25 · Object-Oriented Programming

Object-Oriented Programming

Master the four pillars of OOP in VB 2026 — Classes, Objects, Encapsulation, Inheritance, Polymorphism, and Interfaces — building a real-world employee payroll class hierarchy from scratch.

Key Takeaway: A Class is a blueprint; an Object is a live instance created with New. Use Properties (not public fields) to expose data — they let you add validation without breaking callers. Inheritance (Inherits) lets a child class reuse parent code; mark methods Overridable in the parent and Overrides in the child for Polymorphism. An Interface is a contract: any class that Implements it must provide all listed methods — enabling you to write code that works on any type that fulfils the contract, regardless of its inheritance chain.
Class / Object
Blueprint / Instance
Class defines structure; New creates a live object from that blueprint.
Property
Get / Set
Controlled access to internal fields. Add validation in Set without changing callers.
Constructor
Sub New
Runs when object is created. Use to initialise required fields. Can be overloaded.
Encapsulation
Private / Public
Hide internal details. Expose only what callers need via Properties and Methods.
Inheritance
Inherits
Child class gains all parent members. Use MyBase.New() to call parent constructor.
Polymorphism
Overridable / Overrides
Same method name, different behaviour per class. Resolved at runtime.
Interface
Interface / Implements
A contract of method signatures. Any class can implement it regardless of hierarchy.
MustInherit / MustOverride
Abstract
Abstract class / method — cannot instantiate directly; children must override.

25.1 Classes, Objects, and Properties

A class bundles data (fields stored privately) and behaviour (methods) into one unit. You create objects from a class with New. Properties replace raw public fields: the Get accessor returns the value, the Set accessor validates before storing it.

Employee.vb — Visual Basic 2026
Public Class Employee

    ' --- Private backing fields (hidden from callers) ---
    Private _name   As String
    Private _salary As Decimal
    Private _id     As Integer

    ' --- Constructor: required data supplied at creation ---
    Public Sub New(id As Integer, name As String, salary As Decimal)
        Me.Id     = id
        Me.Name   = name
        Me.Salary = salary
    End Sub

    ' --- Properties: controlled access with validation ---
    Public Property Id As Integer
        Get
            Return _id
        End Get
        Set(value As Integer)
            If value < 1 Then Throw New ArgumentOutOfRangeException("Id must be positive.")
            _id = value
        End Set
    End Property

    Public Property Name As String
        Get
            Return _name
        End Get
        Set(value As String)
            If String.IsNullOrWhiteSpace(value) Then Throw New ArgumentException("Name cannot be blank.")
            _name = value.Trim()
        End Set
    End Property

    Public Property Salary As Decimal
        Get
            Return _salary
        End Get
        Set(value As Decimal)
            If value < 0 Then Throw New ArgumentOutOfRangeException("Salary cannot be negative.")
            _salary = value
        End Set
    End Property

    ' --- Read-only auto-property (no backing field needed) ---
    Public ReadOnly Property HireDate As DateTime = DateTime.Today

    ' --- Methods ---
    Public Overridable Function CalculatePay() As Decimal
        Return _salary    ' base: monthly salary
    End Function

    Public Overridable Function GetDescription() As String
        Return $"Employee #{_id}: {_name}, Salary RM {_salary:N2}"
    End Function

    Public Overrides Function ToString() As String
        Return GetDescription()
    End Function

End Class

' --- Creating and using objects ---
Dim emp As New Employee(1, "Alice Tan", 5000)
Console.WriteLine(emp.Name)         ' Alice Tan
Console.WriteLine(emp.CalculatePay())' 5000
emp.Salary = 5500                   ' triggers Set validator
Console.WriteLine(emp)              ' calls ToString → GetDescription
Properties vs Public Fields

Always use Properties, never raw Public fields. Properties look identical to callers (emp.Salary = 5500) but let you add validation, logging, or change notifications inside Set at any time — without breaking any code that uses the class. Switching a public field to a property later is a breaking change; using a property from day one is not.

Try It — Simulation 25.1: Employee Object Builder

Create an Employee object by filling in the fields. The simulation shows the Sub New constructor call, validates each Property's Set, and calls GetDescription() — just as VB would at runtime.

Employee Object Builder
ID:
Name:
Salary (RM):

25.2 Inheritance — Inherits and MyBase

Inheritance lets a child class reuse all the code of its parent, then extend or specialise it. Use Inherits to declare the parent. Call MyBase.New() in the child's constructor to initialise the parent portion. Children automatically have all parent Properties and Methods.

Inheritance.vb — Visual Basic 2026
' --- FullTimeEmployee inherits Employee ---
Public Class FullTimeEmployee
    Inherits Employee          ' gets Id, Name, Salary, HireDate, CalculatePay, etc.

    Public Property Department As String
    Public Property Bonus      As Decimal

    Public Sub New(id As Integer, name As String, salary As Decimal,
                   dept As String, bonus As Decimal)
        MyBase.New(id, name, salary)   ' initialise parent Employee
        Me.Department = dept
        Me.Bonus      = bonus
    End Sub

    Public Overrides Function CalculatePay() As Decimal
        Return MyBase.CalculatePay() + Bonus   ' salary + bonus
    End Function

    Public Overrides Function GetDescription() As String
        Return MyBase.GetDescription() & $" [{Department}, Bonus RM {Bonus:N2}]"
    End Function
End Class

' --- PartTimeEmployee inherits Employee ---
Public Class PartTimeEmployee
    Inherits Employee

    Public Property HoursWorked As Decimal
    Public Property HourlyRate  As Decimal

    Public Sub New(id As Integer, name As String,
                   hours As Decimal, rate As Decimal)
        MyBase.New(id, name, hours * rate)   ' salary = hours × rate
        Me.HoursWorked = hours
        Me.HourlyRate  = rate
    End Sub

    Public Overrides Function CalculatePay() As Decimal
        Return HoursWorked * HourlyRate
    End Function

    Public Overrides Function GetDescription() As String
        Return MyBase.GetDescription() &
               $" [{HoursWorked}h @ RM{HourlyRate}/h]"
    End Function
End Class

' --- Contractor (no Salary — uses flat project fee) ---
Public Class Contractor
    Inherits Employee

    Public Property ProjectFee  As Decimal
    Public Property Agency      As String

    Public Sub New(id As Integer, name As String,
                   fee As Decimal, agency As String)
        MyBase.New(id, name, 0)   ' salary = 0; pay comes from ProjectFee
        Me.ProjectFee = fee
        Me.Agency     = agency
    End Sub

    Public Overrides Function CalculatePay() As Decimal
        Return ProjectFee
    End Function

    Public Overrides Function GetDescription() As String
        Return $"Contractor #{Id}: {Name}, Agency: {Agency}, Fee RM {ProjectFee:N2}"
    End Function
End Class
Employee (parent / base class) ├── Id, Name, Salary, HireDate ← Properties ├── CalculatePay() → Decimal ← Overridable └── GetDescription() → String ← Overridable │ ├── FullTimeEmployee Inherits Employee │ ├── Department, Bonus ← new Properties │ ├── CalculatePay() ← Overrides: salary + bonus │ └── GetDescription() ← Overrides: adds dept/bonus │ ├── PartTimeEmployee Inherits Employee │ ├── HoursWorked, HourlyRate ← new Properties │ └── CalculatePay() ← Overrides: hours × rate │ └── Contractor Inherits Employee ├── ProjectFee, Agency ← new Properties └── CalculatePay() ← Overrides: project fee
Try It — Simulation 25.2: Employee Hierarchy Builder

Create different employee types and add them to a list. Watch how each class's Sub New calls MyBase.New(), and how the inherited vs overridden properties appear on each object.

Employee Hierarchy Builder
Type:
Name:
Salary (RM):
Bonus (RM):

25.3 Polymorphism — Overridable and Overrides

Polymorphism means "many forms". You can hold a FullTimeEmployee or Contractor in a variable typed as Employee, and when you call .CalculatePay(), VB calls the correct version for the actual object type — resolved at runtime. This lets you write generic code that works for any employee type without needing to know which one it is.

Polymorphism.vb — Visual Basic 2026
' --- Polymorphic list: any Employee subtype ---
Dim staff As New List(Of Employee)
staff.Add(New FullTimeEmployee(1, "Alice", 5000, "IT", 500))
staff.Add(New PartTimeEmployee(2, "Bob",   80,   25))
staff.Add(New Contractor(3,        "Carol", 8000, "TechStaff"))

' --- Polymorphic loop: same call, different result per type ---
For Each emp In staff
    Console.WriteLine($"{emp.Name}: RM {emp.CalculatePay():N2}")
    ' Alice: RM 5,500.00  ← FullTimeEmployee.CalculatePay (salary + bonus)
    ' Bob  : RM 2,000.00  ← PartTimeEmployee.CalculatePay (hours × rate)
    ' Carol: RM 8,000.00  ← Contractor.CalculatePay (project fee)
Next

' --- Total payroll using polymorphism ---
Dim totalPayroll = staff.Sum(Function(e) e.CalculatePay())
Console.WriteLine($"Total payroll: RM {totalPayroll:N2}")

' --- TypeOf / Is to detect actual type when needed ---
For Each emp In staff
    If TypeOf emp Is FullTimeEmployee Then
        Dim fte = DirectCast(emp, FullTimeEmployee)
        Console.WriteLine($"{fte.Name} is in {fte.Department}")
    End If
Next

' --- Pattern matching with TryCast (safe downcast) ---
For Each emp In staff
    Dim pt = TryCast(emp, PartTimeEmployee)
    If pt IsNot Nothing Then
        Console.WriteLine($"{pt.Name}: {pt.HoursWorked}h @ RM{pt.HourlyRate}/h")
    End If
Next

' --- MustInherit / MustOverride: abstract base ---
Public MustInherit Class Shape
    Public MustOverride Function Area() As Double
    Public MustOverride Function Perimeter() As Double
    Public Overridable Function Describe() As String
        Return $"{GetType().Name}: Area={Area():F2}, Perimeter={Perimeter():F2}"
    End Function
End Class
' Dim s As New Shape()  ← COMPILE ERROR — cannot instantiate MustInherit
Try It — Simulation 25.3: Polymorphic Payroll

A pre-loaded staff list with all three employee types. Click Run Payroll to see each employee's CalculatePay() resolve to the right overridden version. Click an employee to inspect their actual runtime type.

Polymorphic Payroll — Runtime Type Resolution

25.4 Interfaces — Contracts for Any Class

An Interface declares what a class can do without saying how. Any class that Implements an interface must provide all declared members. The power: you can write code against the interface type, and it will work with any implementing class — even ones from completely different inheritance trees.

Interfaces.vb — Visual Basic 2026
' --- Define interfaces ---
Public Interface IPayable
    Function CalculatePay() As Decimal
    ReadOnly Property Name As String
End Interface

Public Interface IPrintable
    Function GetDescription() As String
    Sub PrintReport()
End Interface

Public Interface IExportable
    Function ToCsv() As String
    Function ToJson() As String
End Interface

' --- Implement multiple interfaces in one class ---
Public Class FullTimeEmployee
    Inherits Employee
    Implements IPayable, IPrintable, IExportable

    ' IPayable — already satisfied by inherited Salary + override
    Public Overrides Function CalculatePay() As Decimal Implements IPayable.CalculatePay
        Return Salary + Bonus
    End Function

    ' IPrintable
    Public Overrides Function GetDescription() As String Implements IPrintable.GetDescription
        Return $"FTE #{Id}: {Name} ({Department}) — RM {CalculatePay():N2}/mo"
    End Function

    Public Sub PrintReport() Implements IPrintable.PrintReport
        Console.WriteLine(GetDescription())
    End Sub

    ' IExportable
    Public Function ToCsv() As String Implements IExportable.ToCsv
        Return $"{Id},{Name},{Department},{Salary},{Bonus}"
    End Function

    Public Function ToJson() As String Implements IExportable.ToJson
        Return $"{{""id"":{Id},""name"":""{Name}"",""dept"":""{Department}"",""pay"":{CalculatePay()}}}"
    End Function
End Class

' --- Write code against the interface, not the concrete type ---
Private Sub ProcessPayroll(employees As IEnumerable(Of IPayable))
    Dim total = 0D
    For Each emp In employees
        total += emp.CalculatePay()
        Console.WriteLine($"Paid: {emp.Name} — RM {emp.CalculatePay():N2}")
    Next
    Console.WriteLine($"Total: RM {total:N2}")
End Sub

' ProcessPayroll works for FullTimeEmployee, PartTimeEmployee, Contractor,
' Vendor, Freelancer — any class that Implements IPayable.
Try It — Simulation 25.4: Interface Explorer

Select an employee and an interface. See whether they implement it, and — if they do — call its methods and inspect the output. Demonstrates writing generic code against IPayable, IPrintable, and IExportable.

Interface Explorer — Implements Demo
Employee type:
Interface to test:

25.5 Encapsulation and Access Modifiers

Encapsulation hides implementation details. Only expose what callers genuinely need. The wrong access level is one of the most common sources of bugs in large codebases — internal state changed by unexpected callers leads to hard-to-trace errors.

ModifierVisible toTypical Use
PublicAny code anywhereProperties, methods that form the public API
PrivateThis class onlyBacking fields, helper methods
ProtectedThis class + subclassesMembers subclasses need but callers shouldn't see
FriendSame assembly (project)Internal helpers shared across classes in one project
Protected FriendSame assembly + subclassesSubclass helpers that the assembly shares
Private ProtectedSame assembly + subclassesTightest: only subclasses within same assembly
Encapsulation.vb — Visual Basic 2026
Public Class BankAccount

    Private _balance     As Decimal          ' hidden — no direct access
    Private _transactions As New List(Of String)

    Public ReadOnly Property Balance As Decimal
        Get
            Return _balance                   ' read-only — no Set
        End Get
    End Property

    Public Sub Deposit(amount As Decimal)
        If amount <= 0 Then Throw New ArgumentException("Amount must be positive.")
        _balance += amount
        RecordTransaction($"DEPOSIT  RM {amount:N2}")
    End Sub

    Public Sub Withdraw(amount As Decimal)
        If amount <= 0 Then Throw New ArgumentException("Amount must be positive.")
        If amount > _balance Then Throw New InvalidOperationException("Insufficient funds.")
        _balance -= amount
        RecordTransaction($"WITHDRAW RM {amount:N2}")
    End Sub

    Private Sub RecordTransaction(entry As String)   ' Private helper
        _transactions.Add($"{DateTime.Now:HH:mm:ss}  {entry}  Balance: RM {_balance:N2}")
    End Sub

    Public Function GetStatement() As String()
        Return _transactions.ToArray()
    End Function

End Class

' Correct usage — goes through Public methods:
Dim acc As New BankAccount()
acc.Deposit(1000)
acc.Withdraw(250)
Console.WriteLine(acc.Balance)   ' 750 (ReadOnly — can read, can't set)
' acc._balance = 99999  ← COMPILE ERROR: _balance is Private
Try It — Simulation 25.5: Bank Account (Encapsulation)

The balance is a Private field — you can only change it through Deposit and Withdraw. Every operation is validated and logged. Try withdrawing more than the balance to see the InvalidOperationException.

BankAccount — Encapsulation Demo
acc.Balance (ReadOnly)
RM 0.00
Account #ACC-001
Amount (RM):
Statement (GetStatement()):
(no transactions yet)

25.6 GitHub Copilot — OOP Patterns

GitHub Copilot — Product Catalog with OOP
You typed: ' Build a product catalog system. MustInherit class Product with Name, Price, SKU. Subclasses: PhysicalProduct (weight, dimensions, ShippingCost()), DigitalProduct (downloadUrl, FileSizeMb, no shipping). Interface IDiscountable with ApplyDiscount(percent). List(Of Product) with polymorphic TotalRevenue().'
' OOP product catalog — abstract base, subclasses, interface.
Public Interface IDiscountable
    Function ApplyDiscount(percent As Decimal) As Decimal
End Interface

Public MustInherit Class Product
    Public Property Name  As String
    Public Property Price As Decimal
    Public Property SKU   As String

    Public Sub New(name As String, price As Decimal, sku As String)
        Me.Name = name : Me.Price = price : Me.SKU = sku
    End Sub

    Public MustOverride Function ShippingCost() As Decimal
    Public MustOverride Function GetInfo() As String

    Public Overridable Function TotalCost() As Decimal
        Return Price + ShippingCost()
    End Function
End Class

Public Class PhysicalProduct
    Inherits Product
    Implements IDiscountable

    Public Property WeightKg    As Decimal
    Public Property Dimensions  As String

    Public Sub New(name As String, price As Decimal, sku As String,
                   weight As Decimal, dims As String)
        MyBase.New(name, price, sku)
        Me.WeightKg = weight : Me.Dimensions = dims
    End Sub

    Public Overrides Function ShippingCost() As Decimal
        Return WeightKg * 3.5D   ' RM 3.50 per kg
    End Function

    Public Overrides Function GetInfo() As String
        Return $"{Name} [{SKU}] — RM {Price:N2} + RM {ShippingCost():N2} shipping"
    End Function

    Public Function ApplyDiscount(percent As Decimal) As Decimal
            Implements IDiscountable.ApplyDiscount
        Price -= Price * (percent / 100)
        Return Price
    End Function
End Class

Public Class DigitalProduct
    Inherits Product

    Public Property DownloadUrl As String
    Public Property FileSizeMb  As Decimal

    Public Sub New(name As String, price As Decimal, sku As String,
                   url As String, size As Decimal)
        MyBase.New(name, price, sku)
        Me.DownloadUrl = url : Me.FileSizeMb = size
    End Sub

    Public Overrides Function ShippingCost() As Decimal
        Return 0   ' no physical shipping
    End Function

    Public Overrides Function GetInfo() As String
        Return $"{Name} [{SKU}] — RM {Price:N2} (digital, {FileSizeMb}MB)"
    End Function
End Class

' Polymorphic total:
Dim catalog As New List(Of Product)
Dim revenue = catalog.Sum(Function(p) p.TotalCost())
Copilot Chat Prompts for This Lesson

Try these in the Copilot Chat panel while working with OOP:

  • "Add an IComparable(Of Employee) implementation to sort employees by CalculatePay() descending in a List.Sort() call"
  • "Create an EmployeeRepository class that stores a List(Of Employee) and exposes FindById(id), FindByDepartment(dept), and GetTotalPayroll() using LINQ"
  • "Write a generic Factory(Of T) class with a Create() function that uses Activator.CreateInstance to instantiate any class with a parameterless constructor"
  • "Implement the Observer pattern: IObserver interface with Update(event As String), Subject class with Subscribe/Unsubscribe/Notify, used to notify UI labels when employee salary changes"

Lesson Summary

  • A Class is a blueprint with data (Private fields) and behaviour (methods). Create objects with New. Always use Properties (Get/Set) instead of public fields to enable future validation without breaking callers.
  • Sub New (constructor) runs when an object is created. Use MyBase.New() in a child class to initialise the parent portion. Constructors can be overloaded with different parameter lists.
  • Inheritance (Inherits) lets a child class reuse all parent code. Mark parent methods Overridable; use Overrides in the child. Call MyBase.MethodName() to include the parent's implementation.
  • Polymorphism: store any subclass in a List(Of Employee) and call .CalculatePay() — VB calls the right overridden version at runtime. Use TypeOf … Is or TryCast to inspect or downcast safely.
  • Interfaces (Interface … End Interface) define a contract. Any class that Implements it must provide all members. Write methods against the interface type to accept any implementing class.
  • MustInherit = abstract class (cannot be instantiated). MustOverride = abstract method (child must override). Forces a consistent API across all subclasses.
  • Encapsulation: keep fields Private, expose state only through Public Properties and methods. Use ReadOnly Property to prevent callers from setting values directly.

Exercises

Exercise 25.1 — Vehicle Fleet

  • Create MustInherit Class Vehicle with Make, Model, Year, MustOverride Function FuelCost(km As Double) As Decimal
  • Subclasses: PetrolCar (litres per 100km, fuel price/litre), ElectricCar (kWh per 100km, electricity price/kWh), Truck (load tonnage multiplies base consumption)
  • Interface IServiceable with Sub ScheduleService(date As DateTime) and Function DaysUntilService() As Integer
  • Polymorphic fleet report: loop List(Of Vehicle), call FuelCost(500) for a 500 km journey, show total fleet cost
  • Copilot challenge: "Add IComparable(Of Vehicle) sorted by FuelCost(500) ascending and display a sorted fleet report"

Exercise 25.2 — Shape Calculator

  • MustInherit Class Shape with MustOverride Function Area() As Double, MustOverride Function Perimeter() As Double, concrete Overridable Function Describe() As String
  • Subclasses: Circle, Rectangle, Triangle, RegularPolygon(sides, sideLength)
  • Interface IDrawable with Sub DrawToCanvas(g As Graphics) implemented in each shape
  • Polymorphic area totaller using List(Of Shape).Sum(Function(s) s.Area())
  • Copilot challenge: "Add an IResizable interface with Sub Scale(factor As Double) that adjusts all dimensions proportionally"

Exercise 25.3 — Library Catalog

  • Class LibraryItem with Title, Author, ISBN, Overridable Function GetSummary() As String
  • Subclasses: Book (pages, genre), Magazine (issue number, frequency), DVD (runtime minutes, rating)
  • Interface IBorrowable with Sub CheckOut(member As String), Sub Return(), ReadOnly Property IsAvailable As Boolean
  • Build a LibraryCatalog class with Add, Search(query), GetAvailable() using LINQ on a List(Of LibraryItem)
  • Copilot challenge: "Implement IExportable.ToCsv() and ToJson() on all three item types and export the whole catalog"

Next: Lesson 26 — Introduction to Graphics

Start drawing in VB 2026 — the Graphics object, Paint event, pens, brushes, and your first lines and shapes on a PictureBox or Form surface.

Continue »

Related Resources


Featured Books

Visual Basic 2022 Made Easy

Visual Basic 2022 Made Easy

by Dr. Liew Voon Kiong

OOP fundamentals including classes, inheritance, and interfaces with complete working programs.

View on Amazon →
VB Programming With Code Examples

VB Programming With Code Examples

by Dr. Liew Voon Kiong

Real-world OOP patterns including payroll systems, inventory classes, and data modeling.

View on Amazon →