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.
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.
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.
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
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.
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.
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.
' --- 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
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.
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.
' --- 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
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.
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.
' --- 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.
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.
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.
| Modifier | Visible to | Typical Use |
|---|---|---|
| Public | Any code anywhere | Properties, methods that form the public API |
| Private | This class only | Backing fields, helper methods |
| Protected | This class + subclasses | Members subclasses need but callers shouldn't see |
| Friend | Same assembly (project) | Internal helpers shared across classes in one project |
| Protected Friend | Same assembly + subclasses | Subclass helpers that the assembly shares |
| Private Protected | Same assembly + subclasses | Tightest: only subclasses within same assembly |
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
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.
25.6 GitHub Copilot — OOP Patterns
' 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())
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. UseMyBase.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 methodsOverridable; useOverridesin the child. CallMyBase.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. UseTypeOf … IsorTryCastto inspect or downcast safely. - Interfaces (
Interface … End Interface) define a contract. Any class thatImplementsit 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 throughPublicProperties and methods. UseReadOnly Propertyto prevent callers from setting values directly.
Exercises
Exercise 25.1 — Vehicle Fleet
- Create
MustInherit Class VehiclewithMake,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
IServiceablewithSub ScheduleService(date As DateTime)andFunction DaysUntilService() As Integer - Polymorphic fleet report: loop
List(Of Vehicle), callFuelCost(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 ShapewithMustOverride Function Area() As Double,MustOverride Function Perimeter() As Double, concreteOverridable Function Describe() As String- Subclasses:
Circle,Rectangle,Triangle,RegularPolygon(sides, sideLength) - Interface
IDrawablewithSub 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 LibraryItemwithTitle,Author,ISBN,Overridable Function GetSummary() As String- Subclasses:
Book(pages, genre),Magazine(issue number, frequency),DVD(runtime minutes, rating) - Interface
IBorrowablewithSub CheckOut(member As String),Sub Return(),ReadOnly Property IsAvailable As Boolean - Build a
LibraryCatalogclass withAdd,Search(query),GetAvailable()using LINQ on aList(Of LibraryItem) - Copilot challenge: "Implement IExportable.ToCsv() and ToJson() on all three item types and export the whole catalog"
Related Resources
← Lesson 24
Error Handling — Try/Catch and custom exceptions.
Lesson 26 →
Graphics Introduction — drawing lines and shapes.
MS Docs — VB Classes
Complete guide to classes and objects in VB .NET.
MS Docs — Interfaces
Interface declaration, implementation, and usage patterns.
Featured Books
Visual Basic 2022 Made Easy
OOP fundamentals including classes, inheritance, and interfaces with complete working programs.
View on Amazon →
VB Programming With Code Examples
Real-world OOP patterns including payroll systems, inventory classes, and data modeling.
View on Amazon →