Object-Oriented Programming FoundationsIntroductionObject-oriented programming (OOP) is a way of designing and implementing software by organizing it around objects: entities that combine data with the operations that manipulate that data. OOP is not merely a programming-language feature. It is a modelling approach that helps developers represent domains such as banking, education, commerce, logistics, and social media in software. Java and Python are both widely used object-oriented languages, although they express object-oriented ideas differently. Java emphasizes explicit types, class declarations, access control, and compile-time structure. Python provides dynamic typing, concise syntax, and considerable flexibility. In both languages, effective OOP requires more than knowing how to write a class. Developers must decide what belongs together, which responsibilities an object should have, how objects collaborate, and how design choices affect maintainability. This chapter establishes the conceptual vocabulary required for later study of classes, inheritance, interfaces, polymorphism, design patterns, testing, and software architecture. Learning ObjectivesBy the end of this chapter, you should be able to: - distinguish major programming paradigms;
- define object, class, attribute, method, identity, state, and behavior;
- explain abstraction, encapsulation, and message passing;
- relate objects to real-world and computational models;
- compare object-oriented decomposition with procedural decomposition;
- implement a small object-oriented model in Java or Python;
- evaluate the benefits, costs, and limitations of OOP;
- identify design practices that improve maintainability and testability.
Programming Paradigms and Computational ModelsA programming paradigm is a general style of structuring programs. Paradigms influence how programmers represent problems, express computation, and organize change. No paradigm is universally superior; the appropriate choice depends on the problem, team, tools, and operational constraints. Imperative programmingImperative programming describes computation as a sequence of commands that change program state. Variables, assignment, loops, and conditional statements are central. A typical imperative solution might read a list of prices, update a running total, and print the result. Imperative programming is close to the operational behaviour of a computer and can be efficient and direct. However, when a program becomes large, unrelated operations may share mutable data. This can make it difficult to determine which part of the program caused an incorrect state. Procedural programmingProcedural programming organizes imperative instructions into procedures or functions. A procedure performs an operation on supplied data, often returning a result. Procedural decomposition asks: “What sequence of steps solves this problem?” For example, a procedural shopping application may contain functions such as calculate_total, apply_discount, and print_receipt. Data structures are commonly passed between these functions. This approach is often clear for algorithms and data-processing pipelines. Functional programmingFunctional programming treats computation primarily as the evaluation and composition of functions. It emphasizes immutability, expressions, higher-order functions, and reduction of side effects. Java and Python both support functional techniques, although neither is exclusively functional. Functional approaches can make transformations predictable and easier to test. OOP, by contrast, is particularly useful when a system contains entities with persistent identity, changing state, and collaborations over time. Modern software frequently combines functional and object-oriented techniques. Declarative programmingDeclarative programming describes what result is wanted rather than specifying every step used to produce it. SQL queries and configuration languages are common examples. A database query states the required rows and conditions while the database engine determines an execution strategy. Object-oriented programmingObject-oriented programming organizes software around interacting objects. An object has: - identity — what makes it a distinct entity;
- state — the information currently associated with it;
- behavior — the operations it can perform.
OOP decomposition asks: “What entities and responsibilities exist in this domain, and how should they collaborate?” This question is complementary to algorithmic decomposition rather than a complete replacement for it. From Domain Concepts to Software ObjectsSuppose a university system manages course enrolment. Important domain concepts may include Student, Course, Instructor, Enrolment, and Payment. A naïve design could represent these as unrelated records and write global functions that manipulate them. An object-oriented design instead assigns data and responsibility to suitable objects. For example, an Enrolment object might know its student, course, date, and status. It might also provide operations such as confirm() and cancel(). The object does not merely store information; it helps protect the rules governing that information. This does not mean that every noun in a requirements document must become a class. Some nouns are attributes, some are events, some are roles, and some are irrelevant to the software. Object identification requires analysis of responsibility, lifecycle, and collaboration. Essential OOP TerminologyObjectAn object is a runtime entity that has identity, state, and behavior. In Java, objects are normally instances of classes. In Python, virtually everything—including functions and classes—is an object. ClassA class is a definition or blueprint describing the structure and behavior that instances may possess. It is not necessarily an object itself, although in Python classes are objects too. A class may specify fields, properties, constructors, and methods. InstanceAn instance is a concrete object created from a class. Two instances of the same class may have different state while sharing the class’s behavior. Attribute or fieldAn attribute is a named piece of data associated with an object. In Java, an instance variable is commonly called a field. Python attributes can be created dynamically unless a design restricts that flexibility. MethodA method is behavior defined by a class and invoked in the context of an object or class. An instance method can usually access the receiving object’s state. ConstructorA constructor establishes an object’s initial valid state. Java constructors have the same name as the class and no return type. Python commonly uses the __init__ method to initialize an already-created instance. InterfaceAn interface is a contract describing operations that a type promises to provide. Java has an explicit interface construct. Python commonly expresses interfaces through protocols, abstract base classes, documentation, and testing conventions. EncapsulationEncapsulation combines data and related operations while controlling how the data can be accessed or changed. It is not simply the act of placing variables inside a class. Its purpose is to protect invariants and reduce unnecessary dependencies. AbstractionAbstraction represents essential aspects of a concept while omitting irrelevant implementation detail. A Stack abstraction may expose push, pop, and isEmpty without revealing whether storage uses an array or linked nodes. InheritanceInheritance allows a class to derive or specialize behavior from another class. It can express an “is-a” relationship, but it also creates coupling between parent and child classes. Composition is often a safer alternative when reuse, rather than true substitutability, is the main goal. PolymorphismPolymorphism means that one interface can be used with objects of different concrete types, with each type providing appropriate behavior. A program can call pay() on a PaymentMethod reference while the actual object may be a card payment, bank transfer, or digital wallet. ResponsibilityA responsibility is an obligation assigned to a class or object. Responsibilities include knowing information, validating rules, creating objects, coordinating work, or performing domain operations. Identity, State, and BehaviorThese three properties provide a practical test for deciding whether a domain concept is naturally modelled as an object. IdentityIdentity distinguishes one object from another, even when their current data is equal. Two customer objects might both have the name “Amina Khan,” yet represent different accounts. In Java, object identity can be observed with reference comparison, while .equals() is conventionally used for logical equality when properly implemented. In Python, is tests identity and == tests equality according to the object’s equality behavior. Identity is especially important for entities with a lifecycle. A bank account remains the same account after its balance changes. By contrast, a temperature reading may be treated as a value whose identity is unimportant. StateState is the set of values associated with an object at a particular time. A bank account’s state might include account number, owner, balance, and status. State changes when valid operations occur. A strong design defines legal states and controls transitions between them. For example, an order may move from NEW to PAID to SHIPPED; a shipped order should not be silently returned to NEW. Such rules are often called invariants or business constraints. BehaviorBehavior is what an object can do. Behavior may calculate a result, change state, communicate with another object, or reject an invalid request. Good object design places behavior near the data and rules it uses, rather than exposing raw data and requiring unrelated callers to reproduce the rules. Consider a BankAccount. A method withdraw(amount) can check that the amount is positive and that the account permits the withdrawal. If callers directly modify balance, these checks may be bypassed. Abstraction and EncapsulationAbstraction operates at the level of what a client needs to know. Encapsulation operates at the level of how implementation details are protected. They are related but distinct. A Printer abstraction might offer print(document). The client need not know whether the printer uses a network queue, USB connection, or simulated output. Encapsulation ensures that connection details and queue management remain inside appropriate components. Levels of abstractionSoftware commonly contains several levels: - domain level: concepts such as Invoice or Reservation;
- application level: use cases such as booking a room;
- infrastructure level: databases, networks, files, and external APIs;
- language/runtime level: objects, memory, exceptions, and threads.
A well-designed system hides lower-level details behind stable abstractions. This allows a database implementation to change without forcing every business rule to change. Information hidingInformation hiding is the deliberate concealment of design decisions likely to change. A class should expose a small, meaningful public interface and keep volatile implementation details private or protected by methods. Java provides access modifiers such as private, package-private, protected, and public. Python relies more on conventions, name mangling for double-underscore attributes, properties, and disciplined interfaces. Python’s flexibility does not remove the need for encapsulation; it makes design responsibility more visible. Message Passing and CollaborationObjects collaborate by sending messages. In contemporary languages, a message is commonly represented by a method call, but the conceptual idea is broader: one object requests a service from another object without necessarily knowing its internal implementation. For example, a CheckoutService may send a reserve() request to an Inventory object and a charge() request to a PaymentGateway. Each collaborator owns a responsibility. The checkout service coordinates the workflow but should not manipulate inventory records or payment internals directly. Message passing has several consequences: - the sender depends on a contract rather than implementation details;
- the receiver decides how to perform the requested operation;
- responsibilities can be distributed across collaborating objects;
- communication boundaries become useful locations for testing and replacement.
Excessive message chains can be a warning sign. A statement such as order.getCustomer().getAddress().getCountry() exposes structural knowledge about several objects and can increase coupling. A higher-level method, such as order.shippingCountry(), may provide a more stable abstraction. A Small Worked ExampleThe following Python example models a bank account. The class protects the rule that withdrawals must be positive and cannot exceed the available balance. class BankAccount:
def __init__(self, account_number, opening_balance=0.0):
if opening_balance < 0:
raise ValueError("Opening balance cannot be negative")
self.account_number = account_number
self._balance = opening_balance
@property
def balance(self):
return self._balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self._balance += amount
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal must be positive")
if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amountHere, account_number and _balance are attributes, while deposit and withdraw are methods. The object’s identity is associated with its account number. Its state includes the current balance. Its behavior includes accepting deposits and authorized withdrawals. The _balance convention communicates that callers should not modify the field directly. The balance property exposes observation without exposing uncontrolled mutation. In a production financial system, monetary values should normally use a decimal representation rather than binary floating-point values, and transactions would require persistence, authorization, audit records, concurrency control, and regulatory safeguards. The same idea in Java might be expressed as follows: public final class BankAccount {
private final String accountNumber;
private double balance;
public BankAccount(String accountNumber, double openingBalance) {
if (openingBalance < 0) {
throw new IllegalArgumentException("Opening balance cannot be negative");
}
this.accountNumber = accountNumber;
this.balance = openingBalance;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit must be positive");
}
balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0 || amount > balance) {
throw new IllegalArgumentException("Invalid withdrawal");
}
balance -= amount;
}
}This example is intentionally small. It demonstrates encapsulation and invariant enforcement, but it is not a complete banking implementation. Object-Oriented DecompositionDecomposition divides a complex system into smaller parts. In object-oriented decomposition, the parts are usually classes or components with responsibilities and collaboration relationships. A useful design process is: - identify the system’s goals and use cases;
- identify important domain concepts and external actors;
- assign responsibilities to candidate objects;
- define public contracts and collaborations;
- decide which data must be hidden;
- test scenarios and revise the model;
- separate domain logic from infrastructure concerns.
Several design principles support this process. High cohesionA class has high cohesion when its responsibilities strongly belong together. A ReportGenerator that validates payments, sends email, updates inventory, and formats charts has low cohesion. Splitting these concerns usually improves comprehension and testing. Low couplingCoupling measures how strongly one module depends on others. Some coupling is unavoidable, but unnecessary knowledge of concrete classes, data layouts, and call sequences makes change expensive. Single Responsibility PrincipleThe Single Responsibility Principle states that a class should have one reason to change. It does not mean that a class may contain only one method. A class can have several closely related operations while still representing one coherent responsibility. Favor composition over inheritanceComposition builds objects from other objects. A Car may contain an Engine and a Transmission; it is not necessarily a specialized kind of engine. Composition allows behavior to be replaced or configured and generally avoids the fragile dependencies associated with deep inheritance hierarchies. Illustrative Case: An Online Food-Ordering SystemBackgroundA restaurant platform receives customer orders, checks item availability, calculates totals, accepts payment, and requests delivery. The initial implementation uses a large controller containing database calls, pricing rules, payment logic, and notification code. ProblemThe controller is difficult to test. Adding a new payment method requires editing several conditional branches. Discount rules are duplicated, and changes to database schemas affect business logic. AnalysisCandidate objects include Order, MenuItem, Customer, PaymentMethod, PricingPolicy, Inventory, and NotificationService. Not every object must be a database table. For example, PricingPolicy may be a domain service or strategy object because it represents a rule rather than a persistent entity. The Order should maintain its items and status transitions. PricingPolicy should calculate prices without requiring the controller to know every discount rule. PaymentMethod should provide a payment contract implemented by card and wallet payment types. Infrastructure services should be accessed through interfaces or clearly defined boundaries. Approach and solutionA possible flow is: - the application receives an order request;
- an
Order object validates item quantities; Inventory confirms availability;PricingPolicy calculates the payable amount;- a
PaymentMethod processes payment; - the order changes status to
PAID only after successful payment; - a notification component informs the customer.
The application service coordinates this flow, but each object protects its own rules. A fake payment method can be substituted during testing. A new discount policy can be introduced without rewriting the order entity. OutcomeThe redesigned system has clearer boundaries, more focused tests, and lower change impact. It does not eliminate complexity: payment failures, duplicate requests, partial refunds, inventory races, and external-service outages still require careful engineering. Learning points- A domain noun is a candidate for modelling, not an automatic class.
- Responsibilities should follow knowledge and ownership of rules.
- Interfaces and composition support substitution.
- Object-oriented design must address failure, persistence, security, and concurrency—not only class diagrams.
Benefits of OOPManageabilityEncapsulation limits the number of places that must understand an implementation detail. This can reduce the impact of change. ReuseClasses, components, and interfaces can be reused across applications. Reuse is most effective when the abstraction is genuinely stable; forcing unrelated cases into one hierarchy often creates complexity. ExtensibilityPolymorphism allows new implementations to be added behind an existing contract. For example, a reporting system can support PDF and HTML exporters through a common exporter interface. Domain alignmentObjects can correspond to meaningful domain concepts, making requirements discussions easier between developers and domain experts. TestabilitySmall, cohesive objects with explicit dependencies are easier to unit-test. Dependencies can be replaced by stubs, fakes, or mocks where appropriate. Limitations and RisksOOP is not automatically modular or maintainable. Poorly designed object systems can produce the following problems. Excessive complexityA simple data transformation may become harder to understand when spread across numerous classes, factories, interfaces, and wrappers. The abstraction cost may exceed its benefit. Deep inheritance hierarchiesChanges in a base class can unexpectedly affect many subclasses. Multiple inheritance, where available, can introduce method-resolution and conceptual difficulties. Hidden mutable stateObjects that change themselves through many methods can create order-dependent bugs. Mutable shared state is particularly risky in concurrent systems. Over-encapsulationIf every trivial value requires a class and every field requires multiple layers of indirection, developers may spend more effort navigating the design than solving the problem. Impedance mismatchObject models do not map perfectly to relational databases, message queues, or external APIs. Persistence mapping, serialization, lazy loading, and transaction boundaries require explicit decisions. Performance and memory costsObjects may require metadata, allocations, and method dispatch. For ordinary business applications this is often acceptable, but high-performance systems may use data-oriented designs, arrays, value types, or specialized memory layouts. False modellingA class structure can give the appearance of a domain model while failing to represent actual business rules. Names and diagrams do not guarantee correctness. Professional, Ethical, and Security ConsiderationsObject-oriented design affects more than code organization. In applications handling personal, financial, health, or educational data, classes and interfaces can enforce authorization boundaries and audit responsibilities. However, encapsulation is not a substitute for security controls. Developers should apply least privilege, validate untrusted input, avoid exposing sensitive data through debugging or serialization, and record security-relevant events. Domain objects should not silently bypass consent, retention, accessibility, or privacy requirements. For regulated systems, traceability and clear responsibility boundaries support auditing, but compliance must be addressed through the complete system and organizational process. AI-assisted programming tools can generate classes and tests, but generated code must be reviewed for incorrect assumptions, insecure defaults, licensing concerns, and inappropriate data exposure. AI can accelerate implementation; it does not replace domain analysis, testing, or professional accountability. Best Practices for Beginning OOP Design- Start from use cases and business rules rather than from a list of classes.
- Give each class a clear purpose and a small public interface.
- Keep invariants close to the data they constrain.
- Prefer immutable value objects when identity and mutation are unnecessary.
- Prefer composition when reuse does not represent a genuine subtype relationship.
- Depend on abstractions at volatile boundaries such as payment, storage, and messaging.
- Avoid global mutable state and hidden dependencies.
- Write tests for both normal behavior and invalid state transitions.
- Use language conventions consistently, including naming and access-control practices.
- Refactor when responsibilities, coupling, or duplication become difficult to manage.
Chapter SummaryProgramming paradigms provide different ways to organize computation. Imperative and procedural programming emphasize commands and procedures; functional programming emphasizes expressions and controlled side effects; declarative programming specifies desired results; and object-oriented programming organizes software around collaborating objects. An object combines identity, state, and behavior. A class describes common structure and behavior, while an instance is a concrete runtime object. Abstraction focuses on essential services, and encapsulation protects implementation details and invariants. Objects collaborate through message passing, commonly represented by method calls. Object-oriented decomposition can improve cohesion, reduce coupling, support reuse, and align software with domain concepts. Its effectiveness depends on disciplined responsibility assignment and appropriate use of composition, interfaces, and polymorphism. OOP also has limitations, including excessive indirection, inheritance fragility, mutable-state bugs, persistence mismatch, and possible performance costs. Sound design therefore uses OOP selectively and evaluates it against the problem rather than treating it as a universal solution. Key TermsObject: A runtime entity with identity, state, and behavior. Class: A definition describing the structure and behavior of instances. Instance: A concrete object created from a class. Attribute: Data associated with an object. Method: Behavior defined by a class. Constructor: An operation that establishes initial object state. Identity: The property that distinguishes one object from another. State: The values associated with an object at a particular time. Behavior: The operations an object can perform. Abstraction: Representation of essential features while omitting irrelevant detail. Encapsulation: Combination and protection of data and related operations. Information hiding: Concealment of implementation decisions behind a stable interface. Message passing: Collaboration in which one object requests a service from another. Inheritance: Mechanism for deriving a class from another class. Polymorphism: Use of one contract with objects having different implementations. Composition: Construction of an object from collaborating component objects. Cohesion: Degree to which a module’s responsibilities belong together. Coupling: Degree of dependency between modules. Invariant: A rule that must remain true for a valid object or system state. Interface: A contract specifying operations that an implementation provides. Review QuestionsShort Answer Questions- What are the three defining properties of an object?
- Distinguish abstraction from encapsulation.
- What is the difference between a class and an instance?
- Why is message passing useful in object-oriented design?
- Give one benefit and one limitation of inheritance.
Long Answer Questions- Compare procedural decomposition and object-oriented decomposition using an online shopping example.
- Explain how identity, state, and behavior apply to a bank account.
- Discuss cohesion, coupling, and the Single Responsibility Principle.
- Explain why composition is often preferred to inheritance.
- Evaluate the benefits and limitations of OOP for a large enterprise application.
Analytical / Application Questions- Design a preliminary object model for a university enrolment system. Identify candidate objects, their responsibilities, and at least three collaborations.
- A class exposes public fields for an order’s status, total, and payment result. Analyze the risks and propose an encapsulated redesign.
- An application has one large controller containing payment, inventory, discount, and notification logic. Propose a decomposition and justify the responsibilities assigned to each object.
Multiple Choice Questions with AnswersQuestion 1Which combination best describes an object in object-oriented programming? A. A variable, a loop, and a conditional B. Identity, state, and behavior C. A database table, a query, and an index D. A compiler, a linker, and a loader Correct Answer: B. Identity, state, and behavior Question 2What is the primary purpose of encapsulation? A. To ensure every class inherits from another class B. To make all attributes globally accessible C. To protect implementation details and control state changes D. To eliminate the need for methods Correct Answer: C. To protect implementation details and control state changes Question 3In Python, which operator conventionally tests whether two references denote the same object? A. is B. equals C. same D. === Correct Answer: A. is Question 4Which statement best describes abstraction? A. Exposing every internal implementation detail B. Representing essential features while omitting irrelevant detail C. Copying all fields from a parent class D. Storing data only in a database Correct Answer: B. Representing essential features while omitting irrelevant detail Question 5A class has high cohesion when: A. It depends on many unrelated classes B. Its responsibilities are closely related C. It contains no methods D. All of its fields are public Correct Answer: B. Its responsibilities are closely related Question 6Which relationship is most naturally represented by composition? A. A Car has an Engine B. A Circle is a Database C. A Student is a Payment Gateway D. A Receipt is a Compiler Correct Answer: A. A Car has an Engine Question 7What does polymorphism allow? A. One interface to work with objects having different implementations B. All objects to have identical state C. A program to operate without any data D. Every class to avoid method definitions Correct Answer: A. One interface to work with objects having different implementations Question 8Which design choice best protects a bank account’s balance invariant? A. Allowing any caller to assign the balance directly B. Making withdrawal a controlled method that validates requests C. Removing the account identity D. Representing every transaction as a global variable Correct Answer: B. Making withdrawal a controlled method that validates requests Question 9Why can deep inheritance hierarchies be risky? A. They prevent objects from having behavior B. Changes in a base class may unexpectedly affect many subclasses C. They make all code functional D. They eliminate coupling completely Correct Answer: B. Changes in a base class may unexpectedly affect many subclasses Question 10Which approach is most appropriate when an implementation detail is likely to change? A. Expose it through every public method B. Hide it behind a stable abstraction or interface C. Duplicate it in every caller D. Store it in global mutable state Correct Answer: B. Hide it behind a stable abstraction or interface
|