Object-Oriented Programming

Chapter 1
Programming Paradigms and OOP Foundations

Bachelors Program in AI & ML
Object-Oriented Programming   •   Bachelor

Programming Paradigms and OOP Foundations

Introduction

A programming paradigm is a general style of organising computation. It influences how programmers represent problems, decompose systems, express algorithms, manage change, and reason about correctness. No single paradigm is universally superior. Procedural, functional, event-driven, and object-oriented programming offer different abstractions and are often combined in contemporary software.

Object-oriented programming (OOP) is especially important in large software systems because it groups data and the operations that govern that data into conceptual units. These units can model business entities, user-interface components, devices, simulations, or services. OOP is not merely a programming-language feature; it is a way of analysing a problem, assigning responsibilities, and controlling complexity.

This chapter introduces the principal programming paradigms and develops the conceptual vocabulary needed for later work in an Object-Oriented Programming course. Examples use language-independent pseudocode so that the ideas apply to Java, C++, C#, Python, and similar languages.

Learning Objectives

By the end of this chapter, a student should be able to:

1. Explain procedural, functional, event-driven, and object-oriented paradigms.

2. Distinguish classes, objects, state, behaviour, identity, methods, and messages.

3. Describe association, aggregation, composition, inheritance, and dependency.

4. Analyse a problem and select an appropriate programming paradigm.

5. Apply basic OOP principles to a realistic software-design scenario.

6. Recognise the benefits, limitations, and professional implications of OOP.

Programming Paradigms

Procedural programming

Procedural programming organises a solution as a sequence of procedures or functions. A procedure receives inputs, performs operations, and may modify data held in variables or data structures. The programmer typically decomposes a task from the top level into smaller steps.

For example, a payroll program might follow this structure:

```text

read employee records

for each employee:

calculate gross pay

calculate deductions

calculate net pay

print payroll report

```

The central abstraction is the procedure. Data may be shared among procedures or passed as parameters. Procedural programming is often clear for numerical algorithms, scripts, and tasks with a naturally linear flow. It also maps well to the operation of a computer, making it efficient and comparatively easy to implement.

However, systems can become difficult to maintain when many procedures directly manipulate shared data. A change to a data structure may require modifications across numerous functions. The design may also reflect the order of operations rather than the concepts in the problem domain.

Functional programming

Functional programming treats computation primarily as the evaluation and composition of functions. Functions are ideally pure: given the same input, they produce the same output and do not alter external state. Data is commonly treated as immutable, meaning that an apparent change produces a new value rather than modifying an existing one.

A functional formulation of a payroll calculation might transform a collection of employees through a sequence of operations:

```text

employees

-> calculate gross pay for each employee

-> calculate deductions for each result

-> calculate net pay

-> produce report

```

Important functional concepts include higher-order functions, which accept or return other functions; recursion; function composition; and expressions rather than commands. Functional programming can make reasoning, testing, and concurrent execution easier because pure functions have fewer hidden effects. It is influential in languages such as Haskell, Lisp, F#, and Scala, and functional features are also common in Java, Python, JavaScript, and C#.

Pure functional programming can be less natural when a problem inherently involves changing entities, such as a bank account or a game character. In practice, many systems use a hybrid approach: immutable transformations for data processing and controlled stateful components where interaction requires them.

Event-driven programming

Event-driven programming organises a system around events and handlers. An event is a significant occurrence, such as a mouse click, sensor reading, network message, timer expiration, or user-submitted form. An event loop or runtime detects events and dispatches them to appropriate handlers.

```text

when SubmitButtonClicked:

validate form

save order

display confirmation

```

The exact order of execution is not always visible in the source code. Instead, the framework determines when a handler runs. This paradigm is fundamental to graphical user interfaces, web applications, mobile applications, embedded systems, and distributed services.

Its strengths include responsiveness, modular event handling, and a natural fit for asynchronous environments. Its challenges include indirect control flow, event-ordering errors, race conditions, and difficulty tracing a complete execution path. Event-driven systems frequently use OOP: buttons, windows, messages, and controllers may be objects with state and methods.

Object-oriented programming

OOP organises software around objects that combine state and behaviour. An object represents an entity with a distinct identity. Its state is represented by data, while its behaviour is represented by operations that may inspect or change that state. Objects collaborate by sending messages, commonly implemented as method calls.

For example, a `BankAccount` object may hold an account number and balance. It may provide `deposit`, `withdraw`, and `currentBalance` methods. Code using the object need not know how the balance is stored; it requests an operation through the object's public interface.

OOP supports several related design ideas:

  • Encapsulation: combining data and operations while controlling access to internal representation.
  • Abstraction: exposing essential features while hiding unnecessary implementation detail.
  • Inheritance: defining a class in terms of an existing class, when a genuine substitutable relationship exists.
  • Polymorphism: allowing one interface to represent different concrete implementations.

These ideas are valuable, but they do not automatically produce good software. Poorly designed class hierarchies, excessive indirection, and objects with unclear responsibilities can make a system harder to understand than a procedural solution.

The Core Vocabulary of OOP

Class and object

A class is a specification or blueprint describing possible state and behaviour. An object is a runtime instance of a class. A class can be compared with an architectural plan, whereas an object is a constructed building; the analogy is limited because objects can also change state and collaborate dynamically.

```text

class Lamp:

state: on or off

method switchOn()

method switchOff()

```

Creating two lamps produces two objects of the same class. They share the class-defined behaviour but have separate states. Thus, turning one lamp on does not normally turn the other on.

A class may define attributes, constructors, methods, visibility, invariants, and relationships. An attribute is a named element of state. A method is an operation defined by a class. A constructor establishes a valid initial state when an object is created.

State

An object's state is the collection of values of its attributes at a particular time. If a `Student` object has an identifier, name, and enrolled-course list, those values form part of its state. State may be simple, such as a Boolean `isOpen`, or structured, such as a collection of line items in an order.

A well-designed class protects state from invalid transitions. For instance, a bank account should not permit a withdrawal that violates its business rules unless overdrafts are explicitly supported. Such rules are often called invariants: conditions that should remain true for every valid object state.

Behaviour and methods

Behaviour describes what an object can do. A method may calculate a result, change state, communicate with another object, or perform a controlled combination of these activities. Methods should generally have clear responsibilities and meaningful names.

A method's interface includes its name, parameters, return type where applicable, visibility, and documented effects. Its implementation is the internal code. Separating interface from implementation allows an implementation to change without requiring all clients to change.

Identity

Identity distinguishes one object from another, even when their attribute values are equal. Two customer objects may have the same name but represent different customers. Identity is commonly maintained by object references or unique identifiers.

Identity matters when objects have a continuing lifecycle, can be modified, or participate in relationships. In contrast, a value such as the number 5 is often treated by value: two occurrences of 5 are interchangeable for the relevant operation.

Messages and collaboration

A message is a request for an object to perform an operation. In many languages, a message is expressed as a method invocation:

```text

account.withdraw(100)

```

The sender need not know the internal algorithm used by `withdraw`. This supports information hiding, reduces coupling, and allows different objects to respond through compatible interfaces.

A collaboration is a pattern of messages exchanged among objects to achieve a result. In a web shop, an order may ask an inventory service to reserve products, a payment object to authorise a transaction, and a notification service to send confirmation.

Object Relationships

Association

An association is a general structural relationship between classes. A `Student` may be associated with a `Course`, and a doctor may be associated with patients. Associations may be one-to-one, one-to-many, or many-to-many, and may be navigable in one or both directions.

Aggregation

Aggregation represents a whole–part relationship in which the parts can exist independently of the whole. A `Department` may aggregate `Professor` objects: if the department is dissolved, professors can still exist and be assigned elsewhere. Aggregation is often represented by a collection reference, but its exact semantics depend on the design and language.

Composition

Composition is a stronger whole–part relationship. The part's lifecycle is normally controlled by the whole. For example, an `Order` may be composed of `OrderLine` objects created specifically for that order. If the order is permanently removed, its lines generally have no independent meaning.

Composition is a design decision, not simply a notation symbol. Shared ownership, persistence, and lifecycle rules should be considered explicitly.

Inheritance and generalisation

Inheritance allows a subclass to reuse or specialise features of a superclass. For example, `SavingsAccount` may inherit common account behaviour and add interest-related behaviour. The relationship is often described as “is a,” but that phrase alone is insufficient. A subclass should be substitutable wherever the superclass is expected; this is the central idea behind the Liskov Substitution Principle.

Inheritance can reduce duplication, but deep or unstable hierarchies create strong coupling. Composition—building an object from other objects—is frequently more flexible than inheritance. A payment processor, for example, may contain a payment strategy rather than inherit from a different processor for every payment method.

Dependency

A dependency occurs when one class temporarily uses another, such as a report generator using a printer or a service receiving a repository as a parameter. Dependencies are weaker than ownership relationships but still influence maintainability. Interfaces and dependency injection can reduce coupling and improve testing.

A Conceptual Model of OOP Design

OOP design can be viewed as a process of assigning responsibilities. The designer asks:

1. What concepts, events, or resources are important in the domain?

2. Which objects should know particular facts?

3. Which object should perform each operation?

4. What information must be hidden to protect invariants?

5. What relationships and lifecycles exist?

6. Which parts are likely to vary?

A useful design separates what a client requests from how the request is implemented. This is abstraction. It also aims for high cohesion—each class has closely related responsibilities—and low coupling—classes depend on one another as little as practical.

The principles commonly summarised as SOLID provide guidance, although they are not mechanical laws. The Single Responsibility Principle encourages a class to have one coherent reason to change. The Open–Closed Principle encourages extension without repeated modification of stable code. The Liskov Substitution Principle concerns safe substitutability of subtypes. The Interface Segregation Principle discourages forcing clients to depend on unused operations. The Dependency Inversion Principle encourages dependence on abstractions rather than unstable concrete details.

These principles must be balanced against simplicity. Introducing an interface or hierarchy without a genuine design need can increase complexity rather than reduce it.

Worked Example: A Library Lending System

Consider a university library that lends physical and electronic resources.

Analysis

Important concepts include `Member`, `Resource`, `Loan`, `Librarian`, and `Notification`. A resource has a title and availability state. A member can borrow resources subject to borrowing rules. A loan records the resource, borrower, checkout date, and due date.

The resource types may vary. A printed book can be returned physically, whereas an electronic resource may expire automatically. Rather than placing every operation in one large `Library` class, responsibilities can be distributed:

```text

Member.borrow(resource)

Resource.isAvailable()

Loan.isOverdue(currentDate)

NotificationService.sendReminder(member, loan)

```

The `Library` may coordinate the process, but it should not necessarily own every rule. `Loan` is responsible for whether it is overdue; a resource is responsible for relevant availability behaviour; and a notification service handles delivery.

Design choices

`Member` and `Resource` have independent identities and may exist without a current loan, so a `Loan` is associated with them rather than composing them. A loan, however, may be composed of its loan details if those details have no meaning outside the loan. Different resource types can implement a common `Resource` interface, enabling polymorphic availability checks.

The design should also protect state. Clients should not directly assign `resource.available = true` after a loan. Instead, controlled methods such as `checkout()` and `returnItem()` validate transitions. This prevents inconsistent states, such as one resource being simultaneously loaned to two members.

Outcome and learning points

The resulting design makes rules visible, distributes responsibilities, and allows new resource types to be added with limited changes. Nevertheless, the design must address real constraints: concurrent checkout requests, persistence failures, privacy of borrowing records, accessibility, and auditability. OOP provides a structure for these concerns; it does not solve them automatically.

When Is OOP Appropriate?

OOP is particularly appropriate when:

  • the domain contains entities with identity and changing state;
  • multiple components must collaborate through stable interfaces;
  • the system is expected to evolve over time;
  • variations of a concept require interchangeable implementations;
  • encapsulation can protect important rules or resources;
  • a team benefits from explicit boundaries and responsibilities.

Examples include enterprise information systems, simulation software, games, desktop and mobile interfaces, embedded controllers, and many web back ends.

A procedural or functional approach may be preferable for a short command-line utility, a numerical transformation pipeline, a compiler pass, or a highly parallel data-processing task. Event-driven design is naturally appropriate for user interfaces and asynchronous services. Modern applications often combine paradigms: an object-oriented web service may use functional collection operations and event-driven messaging.

Paradigm selection should therefore be based on problem structure, expected change, team expertise, performance requirements, testability, ecosystem, and operational constraints—not on fashion.

Practical and Professional Considerations

Testing and maintainability

Encapsulation can make unit testing easier when behaviour is exposed through focused interfaces. Dependency injection allows a class to receive a database or message broker abstraction, which can be replaced with a test double. Tests should verify observable behaviour and important invariants rather than depend excessively on private implementation details.

Performance

Objects may introduce memory overhead, indirection, and allocation costs. In most business applications, clarity and maintainability outweigh these costs. In real-time, embedded, or high-performance systems, object lifetimes, memory layout, dynamic dispatch, and garbage collection must be evaluated empirically rather than assumed to be costly or free.

Security and privacy

Encapsulation is not a complete security mechanism. Access control, authentication, authorisation, encryption, secure configuration, logging, and threat modelling remain necessary. Classes handling personal, financial, or health data should minimise collection, restrict access, and avoid exposing sensitive values through logs or error messages.

AI and machine-learning relevance

AI and ML systems also benefit from OOP. A training pipeline may contain objects representing datasets, models, optimisers, evaluators, and experiment configurations. Interfaces can allow several model implementations to be evaluated consistently. However, abstraction must not hide important scientific assumptions. Reproducibility, data provenance, fairness evaluation, model versioning, and monitoring require explicit records and governance beyond class structure.

Challenges and Limitations of OOP

OOP can encourage developers to model every concept as a mutable object, even when simple values or functions would be clearer. Excessive inheritance produces fragile hierarchies. Anemic domain models—classes containing only data with all logic elsewhere—may provide little benefit from encapsulation. Excessive getters and setters can expose representation and weaken invariants.

Object relationships can also create cyclic dependencies, memory-management problems, and difficult persistence mappings. Distributed objects are not equivalent to local objects: network latency, partial failure, serialization, and security boundaries must be considered. Finally, a well-designed object model can still fail if requirements are misunderstood or if operational concerns are ignored.

Best practice is to begin with clear responsibilities and simple abstractions. Prefer composition when it expresses the design more accurately, keep interfaces small, validate state at boundaries, document lifecycle assumptions, and refactor in response to evidence. Use diagrams, code review, automated tests, and profiling where appropriate.

Chapter Summary

Programming paradigms provide different ways to organise computation. Procedural programming emphasises procedures and control flow; functional programming emphasises expressions, pure functions, and immutable data; event-driven programming responds to events through handlers; and OOP organises software around collaborating objects.

An object has identity, state, and behaviour. A class specifies the structure and operations of objects, while methods implement behaviour and messages express requests. Encapsulation protects state, abstraction hides unnecessary detail, inheritance supports carefully justified generalisation, and polymorphism enables interchangeable implementations. Association, aggregation, composition, inheritance, and dependency describe different relationships among objects.

OOP is valuable for systems containing stateful entities, evolving requirements, and complex collaborations, but it is not universally best. Effective design depends on cohesion, coupling, clear responsibilities, protected invariants, appropriate use of composition, and consideration of performance, testing, security, privacy, and governance. In professional and AI/ML contexts, OOP is commonly combined with functional and event-driven techniques.

Key Terms

Paradigm: A general style or model for organising computation.

Procedural programming: A paradigm based on procedures, functions, and explicit sequences of operations.

Functional programming: A paradigm emphasising function evaluation, composition, and reduced side effects.

Event-driven programming: A paradigm in which event occurrences trigger handlers.

Object: A runtime entity with identity, state, and behaviour.

Class: A specification from which objects are created.

State: The values that describe an object at a particular time.

Behaviour: The operations an object can perform.

Identity: The property that distinguishes one object from another.

Method: An operation defined by a class.

Message: A request for an object to perform an operation.

Encapsulation: Combining data and operations while controlling access to representation.

Abstraction: Representing essential features while hiding unnecessary detail.

Polymorphism: Using a common interface for objects with different implementations.

Inheritance: Defining a class as a specialisation of another class.

Association: A general relationship between classes.

Aggregation: A whole–part relationship whose parts can exist independently.

Composition: A strong whole–part relationship involving controlled lifecycle.

Cohesion: The degree to which a class's responsibilities belong together.

Coupling: The degree of dependence between software components.

Review Questions

Short Answer Questions

1. What is a programming paradigm, and why can a system use more than one paradigm?

2. Distinguish a class from an object.

3. Define state, behaviour, and identity.

4. What is the difference between aggregation and composition?

5. Why can composition be preferable to inheritance?

Long Answer Questions

1. Compare procedural, functional, event-driven, and object-oriented programming in terms of abstraction, state management, and suitable applications.

2. Explain encapsulation, abstraction, inheritance, and polymorphism, using a coherent example.

3. Describe how responsibilities could be distributed in an online shopping system.

4. Discuss the benefits and limitations of using OOP in large software projects.

5. Explain why paradigm selection should depend on problem structure rather than programming-language fashion.

Analytical / Application Questions

1. Design a preliminary object model for a hospital appointment system. Identify likely classes, responsibilities, relationships, and at least two invariants.

2. A data-processing task reads millions of records, transforms each record independently, and aggregates results. Which paradigm or combination of paradigms would you choose, and why?

3. A proposed design uses a deep inheritance hierarchy for different payment methods. Analyse the risks and recommend an alternative design where appropriate.

Multiple Choice Questions with Answers

Question 1

Which paradigm primarily organises computation as a sequence of procedures or functions?

A. Procedural programming

B. Functional programming

C. Event-driven programming

D. Object-oriented programming

Correct Answer: A. Procedural programming

Question 2

Which statement best defines an object?

A. A source-code comment describing a class

B. A runtime entity with identity, state, and behaviour

C. A collection of unrelated global variables

D. A procedure that cannot maintain state

Correct Answer: B. A runtime entity with identity, state, and behaviour

Question 3

What is the main purpose of encapsulation?

A. To guarantee that every class uses inheritance

B. To eliminate all object relationships

C. To control access to internal representation and protect state

D. To make every attribute publicly writable

Correct Answer: C. To control access to internal representation and protect state

Question 4

Two objects have identical attribute values but represent different customers. What distinguishes them?

A. Their identity

B. Their syntax

C. Their compiler

D. Their event loop

Correct Answer: A. Their identity

Question 5

Which relationship most strongly implies that a part's lifecycle is controlled by its whole?

A. Dependency

B. Association

C. Aggregation

D. Composition

Correct Answer: D. Composition

Question 6

A graphical user interface responds to button clicks by invoking handlers. Which paradigm is most directly involved?

A. Procedural only

B. Event-driven

C. Functional only

D. Declarative database programming

Correct Answer: B. Event-driven

Question 7

What does polymorphism allow?

A. Different implementations to be used through a common interface

B. Every class to have exactly one object

C. All state to be globally accessible

D. A procedure to avoid parameters

Correct Answer: A. Different implementations to be used through a common interface

Question 8

Which situation most strongly supports choosing OOP?

A. A one-line arithmetic calculation

B. A stateless transformation of a fixed list

C. A system containing interacting entities with changing state and evolving requirements

D. A task that must contain no abstractions

Correct Answer: C. A system containing interacting entities with changing state and evolving requirements

Question 9

Why can excessive inheritance be harmful?

A. It always prevents compilation

B. It creates strong coupling and fragile hierarchies

C. It makes objects impossible to identify

D. It removes the need for testing

Correct Answer: B. It creates strong coupling and fragile hierarchies

Question 10

In a library system, why should a resource's availability usually be changed through controlled methods rather than direct public assignment?

A. To protect validity rules and prevent inconsistent state

B. To ensure that resources have no identity

C. To eliminate all relationships with members

D. To force the use of functional programming

Correct Answer: A. To protect validity rules and prevent inconsistent state

Object-Oriented Programming
Chapter 1 of 15