Object-Oriented Programming FundamentalsIntroductionObject-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 ObjectivesBy 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 ObjectsA 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 comparisonclass 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 ConstructorsAn 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._balanceThe constructor establishes an initial valid state. Validation in methods prevents invalid operations, such as depositing a negative amount. Constructor overloading and default argumentsJava 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_balance3. Encapsulation and Access ControlEncapsulation 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: | Modifier | General accessibility |
|---|
public | Accessible from any appropriate class | private | Accessible only within the declaring class | protected | Accessible within the package and subclasses, subject to Java rules | | no modifier | Package-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 = valueThe 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. AbstractionAbstraction 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: - A method hides a sequence of instructions.
- A class hides its internal data representation.
- An interface hides implementation details behind a capability or contract.
- 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. InheritanceInheritance 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 overridingWhen 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. PolymorphismPolymorphism 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 ClassesAn 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):
passPython 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 AggregationComposition 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 IdentityA 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 += 1Shared 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 ApplicationBackgroundA 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. AnalysisPotential 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 solutionclass 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 designThe 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| Concept | Java | Python |
|---|
| Type declaration | Usually explicit and statically checked | Usually inferred and dynamically checked at runtime | | Object creation | new ClassName(...) | ClassName(...) | | Current object | this | self by convention and explicit parameter | | Initialization | Class-named constructor | __init__ initializer | | Access control | Enforced modifiers such as private | Mainly naming conventions and properties | | Inheritance | One superclass; multiple interfaces | Multiple inheritance supported | | Interfaces | Explicit interface construct | Protocols, abstract base classes, or conventions | | Method overloading | Supported by distinct parameter lists | Usually default arguments or other techniques | | Polymorphism | Declared types and dynamic dispatch | Duck typing and inheritance | | Memory management | Garbage collection | Reference 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- Give each class a clear responsibility.
- Keep attributes private or protected by convention and expose meaningful operations.
- Validate inputs at boundaries and preserve object invariants.
- Prefer composition when a relationship is “has-a” or behavior may vary independently.
- Use inheritance only when the subtype truly satisfies the supertype’s contract.
- Program against interfaces or stable abstractions when multiple implementations are expected.
- Keep methods short enough to understand and test.
- Avoid excessive getters and setters that merely expose internal data.
- Use descriptive names and consistent conventions.
- Write unit tests for normal cases, boundary cases, and invalid operations.
- Document public behavior, assumptions, exceptions, and side effects.
- Avoid shared mutable class state unless its sharing is intentional.
13. Challenges, Limitations, and Ethical ConsiderationsOOP 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 RelevanceModern 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 SummaryObject-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 TermsClass: 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 QuestionsShort Answer Questions- What is the difference between a class and an object?
- Why are constructors important in object-oriented design?
- Define encapsulation and give one practical benefit.
- What is method overriding?
- Distinguish composition from inheritance.
Long Answer Questions- Explain how abstraction, encapsulation, and polymorphism work together in a maintainable program.
- Compare Java and Python approaches to access control, interfaces, and type checking.
- Explain why inheritance can create coupling and identify circumstances in which it is appropriate.
- Describe the role of constructors, validation, and invariants in designing a reliable class.
- Explain how the library model separates responsibilities among
Book, Member, and Library.
Analytical / Application Questions- Design classes for an online payment system supporting credit-card and bank-transfer payments. Identify where interfaces, polymorphism, and encapsulation should be used.
- 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. - 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 AnswersQuestion 1Which 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 2What 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 3Which Java keyword indicates that a class inherits from another class? A. implements B. inherits C. extends D. derives Correct Answer: C. extends Question 4Which relationship is normally represented by a car containing an engine? A. Composition B. Recursion C. Compilation D. Overloading Correct Answer: A. Composition Question 5What 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 6In Python, which method is commonly used to initialize an instance? A. __start__ B. __create__ C. __init__ D. constructor Correct Answer: C. __init__ Question 7What 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 8Why 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 9Which 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 10What 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
|