4. Object-Oriented Programming in Visual BasicIntroduction and ContextObject-oriented programming (OOP) is a method of designing software around objects that combine state and behavior. Visual Basic, particularly Visual Basic .NET, supports OOP through classes, structures, interfaces, inheritance, polymorphism, properties, methods, events, and generics. These features allow programmers to represent business entities and system responsibilities in a form that is easier to understand, test, extend, and maintain than a purely procedural design. In a procedural program, attention is primarily directed toward sequences of instructions and the data passed between procedures. In an object-oriented program, the central question is different: which objects exist in the problem domain, what responsibilities do they have, and how should they collaborate? A banking application may contain `Account`, `Customer`, and `Transaction` objects; a university system may contain `Student`, `Course`, and `Enrollment` objects. The quality of the solution depends not only on correct syntax but also on sound allocation of responsibilities. Visual Basic's object model is based on the .NET Common Language Runtime. Consequently, classes written in Visual Basic can use framework libraries and interoperate with components written in other .NET languages. This chapter develops the principal OOP mechanisms in Visual Basic and relates them to design quality, domain modeling, and professional software development. Learning ObjectivesAfter studying this chapter, the reader should be able to: 1. Explain classes, objects, members, constructors, and object lifetime in Visual Basic. 2. Apply encapsulation, access control, inheritance, polymorphism, abstraction, and interfaces. 3. Distinguish classes from structures and shared members from instance members. 4. Use generics and composition to build reusable designs. 5. Apply SOLID principles when evaluating and refactoring object-oriented code. 6. Model a domain using appropriate relationships and responsibilities. 7. Identify design risks, ethical concerns, and maintainability problems in OOP systems. The Object Model: From Domain Concepts to CodeA class is a blueprint that defines the data and operations associated with a type. An object is a runtime instance of that class. Fields store implementation data, while properties provide controlled access to data. Methods define operations, and events notify other objects that something has occurred. ```vb Public Class BankAccount Private _balance As Decimal Public Sub New(accountNumber As String) Me.AccountNumber = accountNumber End Sub Public ReadOnly Property AccountNumber As String Public ReadOnly Property Balance As Decimal Get Return _balance End Get End Property Public Sub Deposit(amount As Decimal) If amount <= 0D Then Throw New ArgumentOutOfRangeException(NameOf(amount)) _balance += amount End Sub End Class ``` The `Private` field cannot be changed directly by external code. The constructor establishes the account number, and `Deposit` enforces a business rule. This is more robust than exposing `Balance` as a public variable because the class controls how its state changes. An object is created with `New`: ```vb Dim account As New BankAccount("A-101") account.Deposit(500D) Console.WriteLine(account.Balance) ``` `Me` refers to the current object. `Nothing` represents the absence of an object reference. Variables of a class type hold references, so two variables may refer to the same object: ```vb Dim first = account first.Deposit(100D) ' account.Balance is now 600 ``` The .NET garbage collector eventually reclaims objects that are no longer reachable. Developers should nevertheless release external resources such as files, database connections, and network handles using `IDisposable` and `Using` blocks. Memory management does not eliminate the need for resource management. Constructors and Object InvariantsA constructor is a `Sub New` procedure that runs when an object is created. Constructors may receive parameters, call another constructor through `Me.New`, or invoke a base-class constructor through `MyBase.New`. Constructor overloading supports different valid creation paths, but excessive overloads can make an API confusing. A constructor should establish the object's invariant: a condition that must remain true whenever the object is observable. For example, an employee's identifier should not be empty, and a percentage should remain between zero and one hundred. Validation should occur at the boundary where invalid data enters the object. ```vb Public Class Employee Public Sub New(id As Integer, name As String) If id <= 0 Then Throw New ArgumentOutOfRangeException(NameOf(id)) If String.IsNullOrWhiteSpace(name) Then Throw New ArgumentException("Name is required") Me.Id = id Me.Name = name.Trim() End Sub Public ReadOnly Property Id As Integer Public Property Name As String End Class ``` A class may define a `Finalize` method, but finalization is nondeterministic and should not be used for ordinary resource cleanup. Prefer `IDisposable` for deterministic cleanup. Encapsulation and Access ControlEncapsulation means combining data and operations while restricting inappropriate access to implementation details. It is not merely the use of private fields; it is the protection of valid state and the publication of a purposeful interface. Visual Basic provides several access modifiers. `Public` permits access from any referencing code. `Private` limits access to the declaring type. `Protected` allows the declaring type and derived types to access a member. `Friend` permits access within the same assembly, while `Protected Friend` combines assembly and inheritance access. `Private Protected` limits access to derived types within the same assembly. A well-designed class exposes behavior rather than indiscriminate data. A property can be read-only, write-only, or independently controlled: ```vb Public ReadOnly Property Total As Decimal Public Property Status As OrderStatus ``` An auto-implemented property is convenient, but a calculated or validated property may require a backing field. Encapsulation also includes avoiding unnecessary setters, exposing collections as read-only views, and preventing callers from placing an object in an invalid state. Shared Members and Type-Level BehaviorA `Shared` member belongs to the class itself rather than to a particular instance. It can be accessed through the type name and cannot directly use instance members. Shared members are appropriate for stateless utility operations, constants, factories, or values common to all instances. ```vb Public Class IdentifierGenerator Private Shared _nextValue As Integer = 1000 Public Shared Function NextId() As Integer _nextValue += 1 Return _nextValue End Function End Class ``` Shared mutable state can create hidden dependencies and concurrency problems. It should therefore be used carefully. A singleton or global service may be difficult to test because it couples unrelated code to one shared instance. Dependency injection is often preferable when a service has behavior or external dependencies. Inheritance and PolymorphismInheritance expresses an “is-a” relationship. A derived class inherits accessible members from a base class and can add or specialize behavior. Visual Basic uses `Inherits`: ```vb Public Class SavingsAccount Inherits BankAccount Public Sub New(number As String, rate As Decimal) MyBase.New(number) InterestRate = rate End Sub Public Property InterestRate As Decimal End Class ``` A derived class should be substitutable for its base class. If a subclass changes the meaning of inherited operations or requires callers to test its concrete type constantly, inheritance is probably inappropriate. Polymorphism allows a base-class or interface reference to refer to different concrete objects. Overriding is runtime polymorphism: ```vb Public MustInherit Class Notification Public MustOverride Sub Send(message As String) End Class Public Class EmailNotification Inherits Notification Public Overrides Sub Send(message As String) Console.WriteLine("Email: " & message) End Sub End Class ``` `Overloads` provides compile-time method selection based on different signatures. `Overrides` replaces an overridable inherited implementation. `NotOverridable` prevents further overriding, and `Shadows` hides an inherited member; shadowing should be used cautiously because it can produce different behavior depending on the variable's declared type. Abstract and Sealed ClassesA `MustInherit` class is abstract: it cannot be instantiated and is intended to provide common behavior or a conceptual contract. A `MustOverride` member has no implementation in the abstract class and must be implemented by a concrete derived class. Abstract classes are useful when related types share state or implementation. A `NotInheritable` class is sealed. It prevents inheritance and is suitable when extension would violate invariants, create security concerns, or have no meaningful interpretation. A sealed class can still implement interfaces and be composed into other objects. Abstract and sealed express opposite extension policies: one requires specialization, while the other prohibits it. Interfaces as ContractsAn interface describes capabilities without prescribing implementation. Visual Basic uses `Interface` and `Implements`: ```vb Public Interface IPaymentProcessor Function Process(amount As Decimal) As Boolean End Interface Public Class CardProcessor Implements IPaymentProcessor Public Function Process(amount As Decimal) As Boolean _ Implements IPaymentProcessor.Process Return amount > 0D End Function End Class ``` Interfaces support substitutability, loose coupling, multiple capability contracts, and testing with substitutes. A class may implement several interfaces, whereas Visual Basic class inheritance has one direct base class. An interface should be cohesive and client-focused; a large interface forces clients to depend on operations they do not need. Abstract classes and interfaces overlap but are not identical. An abstract class can provide protected state and shared implementation, while an interface primarily defines a contract. The choice depends on whether the relationship represents shared identity and implementation or merely a capability. Structures, Value Semantics, and Reference SemanticsA `Structure` is a value type. Assignment copies its value, whereas assignment of a class variable copies an object reference. Structures are appropriate for small, logically indivisible values such as coordinates, dates, or measurements. ```vb Public Structure Coordinate Public Sub New(latitude As Double, longitude As Double) Me.Latitude = latitude Me.Longitude = longitude End Sub Public ReadOnly Property Latitude As Double Public ReadOnly Property Longitude As Double End Structure ``` Large mutable structures can cause copying overhead and confusing behavior. A structure should generally be small, represent a value rather than an identity, and avoid mutable shared state. Boxing occurs when a value type is converted to `Object` or an interface reference; unboxing requires conversion back to the correct value type. Composition and AggregationComposition models a “has-a” relationship. An `Order` may contain order lines, and a `ReportService` may use a formatter. Composition is often safer than inheritance because the containing object can delegate to replaceable collaborators without acquiring an unwanted public hierarchy. ```vb Public Class ReportService Private ReadOnly _formatter As IReportFormatter Public Sub New(formatter As IReportFormatter) _formatter = formatter End Sub Public Function Create(data As Object) As String Return _formatter.Format(data) End Function End Class ``` Dependency injection supplies dependencies from outside, as in the constructor above. It improves testability and makes dependencies visible. Aggregation is a weaker whole-part relationship in which the part may exist independently; composition normally implies stronger ownership and coordinated lifetime. Generics and Reusable AbstractionsGenerics allow types and methods to operate safely over a range of data types without repeated implementations or unnecessary conversions. `List(Of T)`, `Dictionary(Of K, V)`, and `Nullable(Of T)` are common examples. ```vb Public Function FindById(Of T As {Class, IEntity})( items As IEnumerable(Of T), id As Integer) As T Return items.FirstOrDefault(Function(x) x.Id = id) End Function ``` The constraint specifies what operations are valid for `T`. Common constraints include `Class`, `Structure`, `New`, a base class, or an interface. Generics provide compile-time type checking and express reusable algorithms clearly. They should not be introduced merely to make code appear sophisticated; the abstraction must represent a genuine family of related types. SOLID as a Design Review FrameworkSOLID is a group of principles for managing dependencies and change. The Single Responsibility Principle states that a class should have one reason to change. An invoice class that calculates totals, writes files, sends email, and persists database records has several responsibilities. The Open–Closed Principle recommends that software be open for extension but closed for inappropriate modification. Polymorphic payment processors can allow a new payment method without changing a stable checkout algorithm. The Liskov Substitution Principle requires derived types to honor the expectations established by their base abstractions. A subtype should not reject valid base-class operations or weaken guarantees. The Interface Segregation Principle favors several focused interfaces over one large interface. A printer that only scans should not be forced to implement fax operations. The Dependency Inversion Principle states that high-level policy should depend on abstractions, not directly on low-level details. A service should depend on `IPaymentProcessor`, not on a particular card gateway. SOLID principles are heuristics, not rigid laws. Applying them mechanically can produce excessive interfaces, indirection, and fragmented code. The appropriate design balances flexibility with comprehensibility. Domain Modeling and Practical ApplicationDomain modeling begins by identifying important concepts, their attributes, invariants, relationships, and responsibilities. Nouns in requirements may suggest entities, but verbs often reveal services and interactions. A domain model should distinguish entities, which possess identity over time, from value objects, which are defined by their values. It should also distinguish domain rules from infrastructure concerns such as SQL, file formats, and user-interface controls. For an online order system, `Customer` and `Order` may be entities; `Money` and `Address` may be value objects. An order can contain order lines through composition, while payment processing can be represented by an interface. A repository abstraction can separate domain logic from storage technology. A professional implementation should also consider thread safety, logging, validation, exception policy, serialization, versioning, and testability. OOP is not an excuse to create a class for every noun. The model should clarify business decisions and reduce change costs. Illustrative Case Study: Refactoring a University Enrollment SystemBackgroundA university has an enrollment application that registers students for courses. The initial system contains a large `EnrollmentManager` class. It validates prerequisites, calculates fees, writes database records, sends email, and generates reports. Situation and AnalysisThe class is difficult to test because every operation requires a live database and email server. Adding a new fee policy requires changing the manager, and different course types use conditional statements throughout the code. The design violates single responsibility and dependency inversion. It also exposes mutable collections, allowing callers to alter enrollment data without validation. ApproachThe domain is divided into `Student`, `Course`, `Enrollment`, and `Fee` concepts. `Enrollment` protects its status through methods such as `Confirm` and `Withdraw`. Course-specific rules implement `IEnrollmentPolicy`, while fee calculation uses `IFeeCalculator`. A `StudentRepository` interface hides persistence details. The application service receives these abstractions through its constructor. ```vb Public Interface IEnrollmentPolicy Function CanEnroll(student As Student, course As Course) As Boolean End Interface Public Class EnrollmentService Private ReadOnly _policy As IEnrollmentPolicy Private ReadOnly _repository As IEnrollmentRepository Public Sub New(policy As IEnrollmentPolicy, repository As IEnrollmentRepository) _policy = policy _repository = repository End Sub Public Sub Enroll(student As Student, course As Course) If Not _policy.CanEnroll(student, course) Then Throw New InvalidOperationException("Enrollment is not permitted") End If _repository.Save(New Enrollment(student, course)) End Sub End Class ``` A test can now provide a fake policy and in-memory repository. The user interface is not responsible for enforcing domain rules, and a database implementation can be replaced without changing enrollment logic. Outcome and Learning PointsThe refactored system has clearer responsibilities, more isolated tests, and a simpler path for adding policy variants. The case illustrates that abstraction is valuable when it represents a real point of variation. It also demonstrates that composition and interfaces can express collaboration without constructing a deep inheritance hierarchy. Challenges, Risks, and Common MistakesA frequent mistake is exposing public fields or unrestricted setters, which permits invalid state. Another is using inheritance only to reuse a few lines of code; composition is usually more adaptable. Deep hierarchies make behavior difficult to trace, while excessive abstraction creates needless indirection. Misusing `Shared` mutable members can cause test interference and race conditions. Confusing `Overloads`, `Overrides`, and `Shadows` can produce unexpected dispatch behavior. Mutable structures may silently modify copies rather than the intended value. Constructors that perform network or database operations make object creation fragile and difficult to test. Developers should avoid catching exceptions without meaningful action, returning `Nothing` where a clear failure is required, and allowing domain rules to exist only in the interface layer. Names should express roles, methods should have focused effects, and public APIs should be kept deliberately small. Professional, Ethical, and Governance ConsiderationsObject-oriented design affects accountability and data protection. Encapsulation can reduce unauthorized modification, but `Private` is not a security boundary against a determined administrator or runtime inspection. Sensitive information should be minimized, encrypted where appropriate, and excluded from logs. Domain objects handling identity, payment, or health data should enforce authorization and audit requirements through appropriate application architecture. Developers must also consider accessibility, localization, records retention, and regulatory obligations. A class that silently discards invalid transactions may create financial or legal risk. Clear validation, traceable decisions, secure dependency configuration, and meaningful tests support professional responsibility. Current Developments and Best PracticesModern Visual Basic applications commonly use dependency injection, automated unit testing, asynchronous APIs, nullable reference analysis, immutable value objects, and layered or hexagonal architectures. Generics and interfaces remain central to reusable libraries, while analyzers and integrated development environments identify accessibility, naming, and design problems early. Useful practices include: - Keep invariants inside the object that owns them.
- Prefer composition and small interfaces over unnecessary inheritance.
- Program to abstractions at genuine variation points.
- Make dependencies explicit through constructors.
- Use read-only views for collections exposed by a class.
- Separate domain policy from persistence and user-interface code.
- Test behavior and contracts rather than private implementation details.
- Review abstractions periodically; remove those that do not reduce change or complexity.
Chapter SummaryObject-oriented programming in Visual Basic organizes software around objects that protect state and provide behavior. Classes define reference types, while structures define value-oriented types. Constructors establish invariants; access modifiers and properties support encapsulation. Inheritance and overriding enable runtime polymorphism, while abstract and sealed classes control extension. Interfaces express capabilities and support loose coupling. Shared members provide type-level behavior but require caution when state is mutable. Generics provide reusable, type-safe abstractions, and composition often offers a more flexible alternative to inheritance. SOLID principles help evaluate responsibilities, substitutability, interfaces, and dependencies. Effective domain modeling connects these mechanisms to real business concepts while considering testing, security, governance, and maintainability. Key TermsClass: A blueprint defining the members and behavior of a type. Object: A runtime instance of a class. Constructor: A `Sub New` procedure that initializes an object. Encapsulation: Protection and controlled exposure of an object's state and behavior. Property: A member that provides controlled access to a value. Inheritance: Derivation of a new class from an existing class. Polymorphism: Use of one abstraction to work with objects of different concrete types. Overriding: Replacing an overridable inherited implementation. Interface: A contract specifying operations or capabilities. Abstract class: A `MustInherit` class that cannot be instantiated directly. Sealed class: A `NotInheritable` class that cannot be subclassed. Structure: A value type whose assignment copies its value. Composition: A whole-part relationship implemented by containing collaborating objects. Shared member: A class-level member associated with the type rather than an instance. Generic: A type or method parameterized over one or more data types. Invariant: A condition that must remain valid for an object. Dependency injection: Supplying an object's collaborators from outside the object. SOLID: Five principles for managing responsibility, extension, substitution, interfaces, and dependencies. Domain model: A software representation of important concepts and rules in a problem domain. Review QuestionsShort Answer Questions1. Distinguish a class from an object in Visual Basic. 2. Why should constructors establish object invariants? 3. What is the difference between `Overrides` and `Overloads`? 4. State two differences between a class and a structure. 5. Why can unrestricted public setters weaken encapsulation? Long Answer Questions1. Explain encapsulation in Visual Basic, including access modifiers, properties, validation, and collection exposure. 2. Compare abstract classes and interfaces, and recommend situations in which each is appropriate. 3. Explain inheritance, composition, and polymorphism using a suitable domain example. 4. Discuss all five SOLID principles and show how they can guide the design of a service class. 5. Describe how generics, dependency injection, and interfaces contribute to reusable and testable software. Analytical / Application Questions1. A payment class contains card validation, database persistence, email notification, and fee calculation. Analyze its design using SOLID principles and propose a revised structure. 2. A `Rectangle` subtype of `Shape` throws an exception when its inherited `Draw` operation is called. What design principle may be violated, and how would you redesign the abstraction? 3. Design a Visual Basic object model for a library system containing members, books, loans, and notifications. Identify entities, value objects, interfaces, composition relationships, and important invariants. 22. Multiple Choice Questions with AnswersQuestion 1Which design best demonstrates encapsulation in a BankAccount class? A. Making Balance a public field that any caller can change B. Making Balance read-only and changing it only through validated Deposit and Withdraw methods C. Using a Shared balance for every account D. Allowing callers to replace the account's private fields Correct Answer: B. Making Balance read-only and changing it only through validated Deposit and Withdraw methods Question 2What is the primary purpose of a constructor in an object-oriented class? A. To establish the initial valid state of an object B. To override every method inherited from a base class C. To convert a class into a structure D. To make all members Shared Correct Answer: A. To establish the initial valid state of an object Question 3Which Visual Basic declaration prevents a class from being instantiated directly? A. NotInheritable Class B. MustInherit Class C. Shared Class D. Friend Class Correct Answer: B. MustInherit Class Question 4A service depends on IPaymentProcessor rather than CardProcessor. Which SOLID principle is most directly illustrated? A. Single Responsibility Principle B. Liskov Substitution Principle C. Dependency Inversion Principle D. Interface Segregation Principle Correct Answer: C. Dependency Inversion Principle Question 5What happens when a variable containing a class object is assigned to another variable? A. The complete object is always copied B. The second variable normally refers to the same object C. The object is converted into a Structure D. The original object is immediately finalized Correct Answer: B. The second variable normally refers to the same object Question 6Which relationship is most appropriately represented by composition? A. A ReportService using an injected IReportFormatter B. A savings account inheriting from a bank account C. A method being overloaded with another signature D. A value being boxed as Object Correct Answer: A. A ReportService using an injected IReportFormatter Question 7Why are small immutable value objects often suitable as Structures? A. They must always support inheritance B. They represent values and assignment copies their value C. They automatically provide database persistence D. They can contain only Shared members Correct Answer: B. They represent values and assignment copies their value Question 8What is the main benefit of an interface in the enrollment case study? A. It forces every class to inherit from one base class B. It hides all validation from the system C. It provides a replaceable contract for policies or repositories D. It prevents polymorphism Correct Answer: C. It provides a replaceable contract for policies or repositories Question 9Which situation most clearly violates the Single Responsibility Principle? A. A class validates its own invariant B. A formatter implements IReportFormatter C. An enrollment manager validates rules, writes database records, sends email, and generates reports D. A class has a private backing field for a property Correct Answer: C. An enrollment manager validates rules, writes database records, sends email, and generates reports Question 10What does a generic constraint such as `Of T As {Class, IEntity}` accomplish? A. It restricts T to types satisfying the specified requirements B. It makes T a Shared member C. It prevents compile-time type checking D. It converts every T into a Structure Correct Answer: A. It restricts T to types satisfying the specified requirements
|