Programming Fundamentals

Chapter 7
Object-Oriented Programming Fundamentals

Certificate in Java-Python-Programming Languages
Programming Fundamentals   •   Certificate

Object-Oriented Programming Fundamentals

Introduction

Object-oriented programming (OOP) is a method of designing software around objects that combine data with the operations that use that data. Instead of treating a program only as a sequence of procedures, OOP models a problem as a collection of interacting entities. A student-management system, for example, may contain Student, Course, and Enrollment objects. Each object has state, such as a student’s name, and behavior, such as enrolling in a course.

OOP is important because it supports abstraction, reuse, maintainability, and controlled complexity. Java is designed as a strongly object-oriented language, whereas Python supports OOP while also supporting procedural and functional styles. The underlying concepts are similar, but their syntax, type systems, visibility mechanisms, and object models differ.

Learning Objectives

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

  • distinguish classes from objects;
  • define attributes, methods, and constructors;
  • apply encapsulation and understand access control;
  • explain inheritance, polymorphism, interfaces, and composition;
  • model a small problem using collaborating classes;
  • compare Java and Python implementations;
  • identify design limitations and select suitable relationships between classes.

1. Classes and Objects

A class is a blueprint or specification for objects. It describes the data an object may contain and the operations it may perform. An object is a concrete instance of a class created during program execution.

For example, Student is a class, while a particular student named Amina is an object of that class. Multiple objects may be created from one class, and each object normally has its own attribute values.

Three ideas are central:

  • State: the values stored in an object’s attributes.
  • Behavior: the operations provided by the object’s methods.
  • Identity: the fact that an object is a distinct entity, even if another object has identical values.

In both Java and Python, objects are commonly created with a constructor-like operation. Java uses the new keyword for ordinary object construction. Python calls the class itself to create an object.

A simple comparison

class Student {
    String name;
    int age;

    void introduce() {
        System.out.println("My name is " + name);
    }
}

Student s = new Student();
s.name = "Amina";
s.age = 20;
s.introduce();
class Student:
    def introduce(self):
        print("My name is", self.name)

s = Student()
s.name = "Amina"
s.age = 20
s.introduce()

These examples work, but they expose the attributes directly. A more robust design controls how state is created and modified.

2. Attributes, Methods, and Constructors

An attribute stores information associated with an object. In Java, attributes are usually declared in the class body with a type. In Python, instance attributes are commonly created in __init__, the initialization method.

A method is a function defined within a class. It normally operates on the object that receives the method call. In Java, the current object is referred to implicitly through this; in Python, it is explicitly named, conventionally as self.

A constructor initializes a newly created object. Java constructors have the same name as the class and no return type. Python uses __init__; technically, object allocation is performed before __init__, but beginners commonly refer to __init__ as the constructor or initializer.

class BankAccount {
    private String owner;
    private double balance;

    public BankAccount(String owner, double openingBalance) {
        this.owner = owner;
        this.balance = openingBalance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    public double getBalance() {
        return balance;
    }
}
class BankAccount:
    def __init__(self, owner, opening_balance):
        self._owner = owner
        self._balance = opening_balance

    def deposit(self, amount):
        if amount > 0:
            self._balance += amount

    def get_balance(self):
        return self._balance

The constructor establishes an initial valid state. Validation in methods prevents invalid operations, such as depositing a negative amount.

Constructor overloading and default arguments

Java supports constructor overloading: several constructors may have the same name but different parameter lists. Python does not support traditional overloading by signature. A Python constructor can instead use default values or alternative class methods.

public BankAccount(String owner) {
    this(owner, 0.0);
}
class BankAccount:
    def __init__(self, owner, opening_balance=0.0):
        self._owner = owner
        self._balance = opening_balance

3. Encapsulation and Access Control

Encapsulation means combining data and behavior in one unit while restricting uncontrolled access to the internal representation. It protects an object’s invariants—conditions that should remain true, such as “an account balance cannot be reduced by an invalid transaction.”

Encapsulation is not simply the use of getters and setters. Good encapsulation means that a class exposes meaningful operations and hides implementation details. For example, withdraw(amount) is usually better than allowing any external code to assign directly to balance.

Java provides explicit access modifiers:

ModifierGeneral accessibility
publicAccessible from any appropriate class
privateAccessible only within the declaring class
protectedAccessible within the package and subclasses, subject to Java rules
no modifierPackage-private accessibility

Python does not enforce access modifiers in the same way. Naming conventions communicate intended access:

  • name suggests public use;
  • _name suggests internal or protected-by-convention use;
  • __name triggers name mangling, which reduces accidental access but is not absolute security.

Python properties can provide controlled access:

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Temperature cannot be below absolute zero")
        self._celsius = value

The class now validates a physical constraint whenever the value is assigned. The constraint is not a programming formula; it is a domain rule expressed in degrees Celsius.

4. Abstraction

Abstraction means representing the essential features of something while omitting unnecessary implementation detail. A Printer class might provide print_document(document) without exposing how paper, memory, and hardware drivers are controlled.

Abstraction operates at several levels:

  1. A method hides a sequence of instructions.
  2. A class hides its internal data representation.
  3. An interface hides implementation details behind a capability or contract.
  4. A module or package hides a group of cooperating classes.

Abstraction helps programmers reason about a system using meaningful concepts rather than every low-level operation.

5. Inheritance

Inheritance allows a new class, called a subclass, to acquire or extend features of an existing superclass. It expresses an “is-a” relationship. A Car may be a Vehicle; a SavingsAccount may be an Account.

class Vehicle {
    public void move() {
        System.out.println("Vehicle is moving");
    }
}

class Bicycle extends Vehicle {
    @Override
    public void move() {
        System.out.println("Bicycle is being pedaled");
    }
}
class Vehicle:
    def move(self):
        print("Vehicle is moving")

class Bicycle(Vehicle):
    def move(self):
        print("Bicycle is being pedaled")

Inheritance can reduce duplication, but it also creates coupling. A subclass depends on assumptions made by its superclass. Therefore, inheritance should be used when the subtype genuinely satisfies the behavioral expectations of the supertype, not merely because two classes share some code.

Method overriding

When a subclass supplies a new implementation of an inherited method, it overrides that method. Java’s @Override annotation helps the compiler detect errors. Python relies on matching method names and compatible behavior.

Java supports single class inheritance: a class extends one superclass. Python supports multiple inheritance, although it should be used carefully because method-resolution order and cooperative initialization can become complex.

6. Polymorphism

Polymorphism means that one interface or general operation can work with objects of different concrete types. A variable declared as a general type may refer to an object of a specialized type. The method that executes can depend on the object’s runtime class; this is called dynamic dispatch.

class Animal {
    public void speak() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("Woof");
    }
}

class Cat extends Animal {
    @Override
    public void speak() {
        System.out.println("Meow");
    }
}

Animal[] animals = { new Dog(), new Cat() };
for (Animal animal : animals) {
    animal.speak();
}
class Dog:
    def speak(self):
        return "Woof"

class Cat:
    def speak(self):
        return "Meow"

animals = [Dog(), Cat()]
for animal in animals:
    print(animal.speak())

The Python example illustrates duck typing: an object is usable if it provides the required behavior, regardless of an explicit shared superclass. Java normally requires a declared relationship such as inheritance or interface implementation, although newer Java features and generic abstractions can express flexible designs.

Polymorphism reduces conditional logic. Instead of writing code that asks whether an object is a dog or cat, the program sends speak() to each object and lets the object provide its implementation.

7. Interfaces and Abstract Classes

An interface defines a contract: a set of operations that a class promises to provide. It is useful when unrelated classes share a capability, such as being printable, payable, or sortable.

interface Payable {
    double calculatePay();
}

class Employee implements Payable {
    private double salary;

    public Employee(double salary) {
        this.salary = salary;
    }

    @Override
    public double calculatePay() {
        return salary;
    }
}

A Java class can implement multiple interfaces, even though it can extend only one class. Interfaces therefore support flexible polymorphism.

An abstract class is a partially implemented base class. It may contain shared attributes and implemented methods as well as abstract methods that subclasses must complete.

abstract class Shape {
    public abstract double area();

    public void describe() {
        System.out.println("This is a shape");
    }
}

Python’s abc module supports abstract base classes:

from abc import ABC, abstractmethod

class Payable(ABC):
    @abstractmethod
    def calculate_pay(self):
        pass

Python also commonly uses protocols or informal interfaces: documentation states which methods an object must provide. The choice depends on whether explicit enforcement, shared implementation, or flexible structural behavior is most valuable.

8. Composition and Aggregation

Composition builds complex objects from other objects. It expresses a “has-a” relationship. A Car has an Engine; a Library has a collection of Book objects.

class Engine:
    def start(self):
        return "Engine started"

class Car:
    def __init__(self):
        self.engine = Engine()

    def start(self):
        return self.engine.start()

In this example, Car creates and controls its engine. This is strong composition: the engine is treated as a part of the car. In aggregation, the contained object may exist independently and may be shared. A university aggregates students; a student can exist even if a particular university object is removed.

Composition is often preferable to inheritance because it allows behavior to be changed by replacing collaborators. This principle is sometimes summarized as “favor composition over inheritance,” but it is not an absolute rule. Inheritance remains suitable for stable, genuine subtype relationships.

9. Class Attributes, Static Members, and Identity

A Java static field belongs to the class rather than to each instance. Python class attributes are shared through the class unless an instance creates an attribute with the same name.

class Counter {
    private static int count = 0;

    public Counter() {
        count++;
    }

    public static int getCount() {
        return count;
    }
}
class Counter:
    count = 0

    def __init__(self):
        Counter.count += 1

Shared state should be used carefully. It can introduce hidden dependencies and make testing difficult. Instance state is usually preferable when values belong to individual objects.

Object identity and equality are also distinct. Two objects can contain equal data while being different objects. Java commonly requires overriding equals() and hashCode() together when value equality is intended. Python classes can define __eq__ and __hash__, but mutable objects should not be used as hash keys if changes could affect their hash-relevant state.

10. Modeling a Problem: A Library Application

Background

A small library wants software that records books, members, and loans. A book can be available or loaned. A member may borrow a limited number of books. The system should prevent invalid actions.

Analysis

Potential classes are:

  • Book: title, author, availability;
  • Member: name and current loans;
  • LoanService: coordinates borrowing and returning.

A book should not independently decide which member may borrow it; that policy belongs to a coordinating service or domain layer. The model uses composition because a service contains or receives references to books and members rather than inheriting from them.

Illustrative Python solution

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self._borrowed = False

    @property
    def available(self):
        return not self._borrowed

    def mark_borrowed(self):
        if not self.available:
            raise ValueError("Book is already borrowed")
        self._borrowed = True

    def mark_returned(self):
        self._borrowed = False


class Member:
    MAX_LOANS = 3

    def __init__(self, name):
        self.name = name
        self._loans = []

    @property
    def loans(self):
        return tuple(self._loans)

    def add_loan(self, book):
        if len(self._loans) >= self.MAX_LOANS:
            raise ValueError("Loan limit reached")
        self._loans.append(book)

    def remove_loan(self, book):
        self._loans.remove(book)


class Library:
    def borrow(self, member, book):
        if not book.available:
            raise ValueError("Book is unavailable")
        member.add_loan(book)
        book.mark_borrowed()

    def return_book(self, member, book):
        member.remove_loan(book)
        book.mark_returned()

The loans property returns a tuple rather than the internal list. This prevents callers from directly modifying the member’s internal collection. The Library class coordinates a transaction involving two objects.

Analysis of the design

The design demonstrates:

  • classes and objects through Book, Member, and Library;
  • encapsulation through _borrowed, _loans, and controlled methods;
  • composition because Library collaborates with domain objects;
  • invariants such as availability and the loan limit;
  • separation of responsibilities, since each class has a focused role.

A production system would also need persistent storage, authentication, transaction handling, concurrency controls, logging, and tests. The example is a domain model, not a complete commercial library system.

11. Java–Python Comparison

ConceptJavaPython
Type declarationUsually explicit and statically checkedUsually inferred and dynamically checked at runtime
Object creationnew ClassName(...)ClassName(...)
Current objectthisself by convention and explicit parameter
InitializationClass-named constructor__init__ initializer
Access controlEnforced modifiers such as privateMainly naming conventions and properties
InheritanceOne superclass; multiple interfacesMultiple inheritance supported
InterfacesExplicit interface constructProtocols, abstract base classes, or conventions
Method overloadingSupported by distinct parameter listsUsually default arguments or other techniques
PolymorphismDeclared types and dynamic dispatchDuck typing and inheritance
Memory managementGarbage collectionReference counting plus garbage collection mechanisms

Neither language is universally superior. Java’s static checking and explicit contracts can detect many errors before execution and support large-team development. Python generally enables concise experimentation and flexible integration, but requires disciplined testing, documentation, and runtime validation.

12. Best Practices

  1. Give each class a clear responsibility.
  2. Keep attributes private or protected by convention and expose meaningful operations.
  3. Validate inputs at boundaries and preserve object invariants.
  4. Prefer composition when a relationship is “has-a” or behavior may vary independently.
  5. Use inheritance only when the subtype truly satisfies the supertype’s contract.
  6. Program against interfaces or stable abstractions when multiple implementations are expected.
  7. Keep methods short enough to understand and test.
  8. Avoid excessive getters and setters that merely expose internal data.
  9. Use descriptive names and consistent conventions.
  10. Write unit tests for normal cases, boundary cases, and invalid operations.
  11. Document public behavior, assumptions, exceptions, and side effects.
  12. Avoid shared mutable class state unless its sharing is intentional.

13. Challenges, Limitations, and Ethical Considerations

OOP can improve structure, but it does not automatically produce good software. Excessive inheritance can create fragile hierarchies, while excessive abstraction can make simple programs difficult to understand. Objects may also be designed around technical structures rather than the real problem domain.

Encapsulation must not be treated as a substitute for security. Sensitive data still requires authentication, authorization, encryption, secure storage, and careful logging. A User class with a private password field is not secure if passwords are stored in plain text.

Programs that model people or decisions should consider privacy, fairness, accessibility, and accountability. For example, a class representing a customer should collect only necessary data, restrict access, and avoid embedding discriminatory assumptions in methods. AI-generated class designs and code should be reviewed for security vulnerabilities, licensing concerns, incorrect assumptions, and biased data models.

Contemporary Relevance

Modern systems frequently combine OOP with other paradigms. Web frameworks use objects for requests, responses, services, and database records, while functional techniques may be used for transformations and concurrency. Dependency injection, design patterns, automated testing, type checking, and containerized deployment all build upon the ability to separate responsibilities and define clear contracts.

Python type hints, static analyzers, dataclasses, and protocols provide some benefits associated with explicit type systems without removing Python’s dynamic nature. Java continues to develop concise syntax, records, sealed classes, pattern matching, and stronger tools for expressing data-oriented and restricted class hierarchies. These developments do not replace the fundamental principles of modeling, responsibility, abstraction, and controlled collaboration.

Chapter Summary

Object-oriented programming models software as interacting objects with state and behavior. Classes define the structure and operations of objects, while constructors establish initial valid state. Encapsulation protects internal representation and preserves invariants; abstraction exposes essential behavior without unnecessary detail. Inheritance enables specialization, but it should represent a genuine subtype relationship. Polymorphism permits common code to work with different implementations. Interfaces and abstract classes define contracts, while composition builds larger objects from collaborating parts and is often more flexible than inheritance.

Java and Python support these ideas differently. Java provides explicit static typing, access modifiers, interfaces, and compile-time checking. Python uses dynamic typing, conventions, properties, duck typing, and flexible class construction. Effective design in either language requires clear responsibilities, validation, testing, and attention to security and privacy. The library example demonstrates how these concepts combine to model a realistic domain while controlling invalid state.

Key Terms

Class: A blueprint defining the data and behavior of objects.

Object: A concrete instance of a class.

Attribute: Data associated with an object or class.

Method: A function defined within a class.

Constructor: An operation that initializes a new object.

State: The values currently stored by an object.

Behavior: The operations an object can perform.

Identity: The distinct existence of an object.

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

Abstraction: Representing essential features while hiding unnecessary detail.

Inheritance: Deriving a new class from an existing class.

Superclass: A class from which another class inherits.

Subclass: A class that inherits from another class.

Polymorphism: Using one general interface with objects of different concrete types.

Interface: A contract specifying operations a class provides.

Abstract class: A partially implemented class intended to be extended.

Composition: Building an object from other objects.

Aggregation: A weaker whole-part relationship in which parts may exist independently.

Invariant: A condition that should remain true for a valid object.

Duck typing: Polymorphism based on available behavior rather than declared type.

Review Questions

Short Answer Questions

  1. What is the difference between a class and an object?
  2. Why are constructors important in object-oriented design?
  3. Define encapsulation and give one practical benefit.
  4. What is method overriding?
  5. Distinguish composition from inheritance.

Long Answer Questions

  1. Explain how abstraction, encapsulation, and polymorphism work together in a maintainable program.
  2. Compare Java and Python approaches to access control, interfaces, and type checking.
  3. Explain why inheritance can create coupling and identify circumstances in which it is appropriate.
  4. Describe the role of constructors, validation, and invariants in designing a reliable class.
  5. Explain how the library model separates responsibilities among Book, Member, and Library.

Analytical / Application Questions

  1. Design classes for an online payment system supporting credit-card and bank-transfer payments. Identify where interfaces, polymorphism, and encapsulation should be used.
  2. A developer makes Car inherit from Engine because both contain a start() method. Analyze whether this is a suitable relationship and propose a better design.
  3. Extend the library model so that different members have different loan limits. Explain whether inheritance, composition, an interface, or a combination would best support the requirement.

Multiple Choice Questions with Answers

Question 1

Which statement best describes an object?

A. A concrete instance of a class

B. A compiler directive

C. A package containing source files

D. A method parameter only

Correct Answer: A. A concrete instance of a class

Question 2

What is the primary purpose of encapsulation?

A. To make every attribute globally accessible

B. To control access to internal representation and preserve valid state

C. To eliminate all methods

D. To force every class to use inheritance

Correct Answer: B. To control access to internal representation and preserve valid state

Question 3

Which Java keyword indicates that a class inherits from another class?

A. implements

B. inherits

C. extends

D. derives

Correct Answer: C. extends

Question 4

Which relationship is normally represented by a car containing an engine?

A. Composition

B. Recursion

C. Compilation

D. Overloading

Correct Answer: A. Composition

Question 5

What does polymorphism allow?

A. Only one object to exist in a program

B. A common operation to work with objects of different concrete types

C. All attributes to become private automatically

D. A method to have no parameters

Correct Answer: B. A common operation to work with objects of different concrete types

Question 6

In Python, which method is commonly used to initialize an instance?

A. __start__

B. __create__

C. __init__

D. constructor

Correct Answer: C. __init__

Question 7

What does a Java interface primarily define?

A. A contract of operations that implementing classes provide

B. A database table

C. A private constructor only

D. A fixed memory address

Correct Answer: A. A contract of operations that implementing classes provide

Question 8

Why should shared mutable class state be used carefully?

A. It always improves security

B. It can create hidden dependencies and complicate testing

C. It prevents object creation

D. It disables polymorphism

Correct Answer: B. It can create hidden dependencies and complicate testing

Question 9

Which design choice best represents a genuine inheritance relationship?

A. A Library has Books

B. A Bicycle is a Vehicle

C. A Member has Loans

D. A Car has an Engine

Correct Answer: B. A Bicycle is a Vehicle

Question 10

What is duck typing most closely associated with?

A. Using an object based on the behavior it provides

B. Requiring every class to extend Object explicitly

C. Preventing method overriding

D. Storing all attributes as constants

Correct Answer: A. Using an object based on the behavior it provides

Programming Fundamentals
Chapter 7 of 14