Object-Oriented Programming: Classes, Objects, and Inheritance
Object-oriented programming (OOP) organizes software around data structures rather than the logic that processes them — a shift that reshaped how large codebases are designed, maintained, and extended. This page covers the three foundational mechanisms — classes, objects, and inheritance — along with the four classical pillars of OOP, the design tensions that make the paradigm genuinely contested, and a comparison of how OOP is implemented across major languages. The goal is a working reference, not a tutorial: precise enough to resolve confusion, structured enough to serve as a mental scaffold.
- Definition and scope
- Core mechanics or structure
- Causal relationships or drivers
- Classification boundaries
- Tradeoffs and tensions
- Common misconceptions
- Checklist or steps (non-advisory framing)
- Reference table or matrix
Definition and scope
A class is a blueprint — a named template that specifies what data a thing holds (its attributes) and what actions it can perform (its methods). An object is a specific instance of that blueprint, allocated in memory with its own attribute values. The class BankAccount might define attributes balance and owner_name and methods deposit() and withdraw(); a specific account belonging to a specific person is the object.
OOP is one of the four major programming paradigms — alongside procedural, functional, and logic programming — and it dominates enterprise software, game development, and mobile application development. The IEEE Computer Society recognizes OOP as a core knowledge area in its Software Engineering Body of Knowledge (SWEBOK), which classifies it under programming fundamentals alongside data structures and algorithms.
Scope matters here. OOP is not a single language feature; it is a design philosophy implemented differently across languages. Python, Java, C++, Ruby, Swift, and Kotlin are all described as object-oriented, yet they differ substantially in how strictly they enforce OOP rules, whether multiple inheritance is permitted, and whether everything in the language is truly an object.
Core mechanics or structure
The four classical pillars of OOP appear in essentially every formal treatment of the subject, including the MIT OpenCourseWare 6.009 course materials on fundamentals of programming:
Encapsulation bundles data and the methods that operate on it inside a single class boundary. Access modifiers — public, private, and protected in Java and C++ — control which parts of a program can read or modify an attribute directly. The practical effect: internal implementation details can change without breaking code that depends on the class, as long as the public interface stays consistent.
Abstraction hides complexity behind a simplified interface. A FileWriter class, for example, exposes open(), write(), and close() without requiring the caller to manage buffer allocation or OS file descriptors directly.
Inheritance lets one class (the subclass or child) derive attributes and methods from another class (the superclass or parent). A SavingsAccount class can inherit from BankAccount and then override or extend specific behaviors — adding interest calculation, for instance — without rewriting deposit and withdrawal logic.
Polymorphism allows objects of different classes to be treated as instances of a shared superclass. A method that accepts a Shape object can receive a Circle or a Rectangle interchangeably, calling the appropriate area() implementation for each. This is sometimes called runtime polymorphism when the correct method is resolved at execution time rather than at compile time.
Constructors and destructors are special methods that run automatically when an object is created or destroyed. In Python, __init__ serves as the constructor; in C++, explicit destructors manage memory deallocation since the language does not include garbage collection.
Causal relationships or drivers
OOP emerged as a direct response to the maintenance problems of large procedural codebases, where global state and tightly coupled functions made modification risky. The language Simula 67, developed at the Norwegian Computing Center in the 1960s, introduced classes and objects specifically to model real-world entities in simulation software — a problem that procedural code handled awkwardly.
Smalltalk, developed at Xerox PARC in the 1970s under Alan Kay, refined the paradigm into what Kay later described as "message passing" between objects, a framing that emphasizes object interaction rather than data mutation. Kay's original formulation is documented in his 1993 retrospective "The Early History of Smalltalk" — notably, it differs from how most languages labeled "OOP" actually work today.
The commercial driver was scale: as software teams grew beyond 10 engineers working on a single codebase, procedural organization broke down. OOP's encapsulation principle created natural module boundaries that mapped onto team structures, making parallel development more tractable.
Classification boundaries
Not all inheritance is the same, and the distinctions carry real consequences:
Single inheritance — one subclass, one direct parent — is enforced by Java and C#. This prevents certain categories of ambiguity but limits flexibility.
Multiple inheritance — a subclass inheriting from 2 or more parent classes simultaneously — is permitted in C++ and Python. Python resolves method resolution order using the C3 linearization algorithm (documented in Python's official data model reference), which defines a deterministic search order when the same method name appears in multiple parents.
Interface-based programming — used in Java and C# — allows a class to implement multiple interfaces (pure method signatures with no implementation) without the ambiguity of multiple inheritance. This is the standard workaround in languages that prohibit multiple class inheritance.
Mixin patterns appear in Python and Ruby, where small, focused classes provide reusable behavior that other classes adopt without forming a strict taxonomic hierarchy.
Composition over inheritance is a design principle documented in the Gang of Four's Design Patterns (Gamma et al., Addison-Wesley, 1994), which recommends building complex behavior by combining simpler objects rather than extending deep inheritance chains. This is not a rejection of OOP but a constraint on how inheritance is used within it.
Tradeoffs and tensions
The inheritance mechanism that makes OOP powerful is also its most criticized feature. Deep inheritance hierarchies — chains of 5 or more levels — are associated with high coupling between parent and child classes, making refactoring expensive. When a superclass changes, every subclass is potentially affected; this is sometimes called the fragile base class problem.
The SOLID principles, formalized by Robert C. Martin in his 2000 paper "Design Principles and Design Patterns," address several OOP failure modes directly. The Liskov Substitution Principle (the "L") states that objects of a subclass must be replaceable by objects of their superclass without altering program correctness — a constraint that many inheritance hierarchies violate in practice.
Performance is another axis of tension. Virtual method dispatch — the mechanism behind runtime polymorphism in C++ and Java — introduces overhead because the program must look up the correct method implementation at runtime. In performance-critical contexts like embedded systems or game engines, this overhead matters. C++ allows programmers to mark methods as final to prevent override and permit compile-time optimization, a tradeoff documented in the ISO C++ Standard (ISO/IEC 14882).
Functional programming advocates argue that OOP's reliance on mutable state — objects that change attribute values over time — makes programs harder to reason about and test than purely functional approaches where data is immutable. This is not a fringe position; the ACM's 2018 Alan Turing Award was awarded in part for work on functional languages, reflecting the field's sustained interest in alternatives to mutable state.
Common misconceptions
"Everything in OOP is an object." In Java, primitive types (int, double, boolean) are not objects — they are value types stored directly on the stack. Java's autoboxing feature wraps them into object equivalents (Integer, Double) on demand, but the distinction has real performance implications. Python, by contrast, treats every value including integers as an object.
"Inheritance models 'is-a' relationships naturally." The classic counterexample is the Square-Rectangle problem: a Square class that inherits from Rectangle violates the Liskov Substitution Principle because setting width and height independently — valid for a rectangle — produces nonsensical results for a square. The relationship feels intuitive geometrically but breaks OOP contracts.
"More inheritance means better OOP." The Gang of Four explicitly recommend preferring composition over inheritance in 23 of their documented patterns. Deep hierarchies are a design smell, not a sign of sophistication.
"OOP and object-based programming are the same." JavaScript before ES6 used prototype-based inheritance rather than class-based inheritance — objects could inherit directly from other objects without a formal class definition. ES6 introduced the class keyword, but it is syntactic sugar over the prototype system, not a structural replacement. The ECMAScript 2015 specification (ECMA-262) documents this explicitly in the section on class definitions.
Checklist or steps (non-advisory framing)
The following elements constitute a correctly structured class definition in a statically typed OOP language:
- [ ] Class name follows the language's naming convention (PascalCase in Java, C++, C#; same convention is standard in Python per PEP 8)
- [ ] Attributes declared with appropriate access modifiers (
privatefor internal state,publicfor interface) - [ ] Constructor method defined to initialize all required attributes
- [ ] Getter and setter methods (or properties) provided for attributes that require controlled external access
- [ ] Methods that modify state return
voidor a new object — not both, to avoid ambiguity - [ ] If the class is designed to be subclassed, the superclass interface documents which methods are intended for override (
virtualin C++; all non-final methods in Java) - [ ] If the class inherits from a parent, the child constructor explicitly calls the parent constructor (e.g.,
super()in Java and Python) - [ ] Classes intended as abstract base types are marked explicitly (
abstractin Java;ABCmodule in Python) - [ ] Unit tests cover at least one instance of each public method — verified against the SWEBOK Guide v4 testing knowledge area
Reference table or matrix
The table below compares OOP feature support across 5 major languages. The full language profiles for Java, Python, and C++ are covered in dedicated guides on this reference collection; the programming languages overview provides a broader comparison across the paradigm landscape. For those building foundational knowledge, programmingauthority.com organizes all reference material by topic, paradigm, and skill level.
| Feature | Java | Python | C++ | JavaScript (ES6+) | C# |
|---|---|---|---|---|---|
| Multiple class inheritance | No | Yes | Yes | No | No |
| Interface / protocol support | Yes (interfaces) | Yes (ABCs) | Yes (pure virtual) | No formal mechanism | Yes (interfaces) |
| Access modifiers | public / private / protected | Convention only (_prefix) | public / private / protected | No enforced access | public / private / protected |
| Garbage collection | Yes (JVM) | Yes (CPython GC) | No (manual / RAII) | Yes (V8 engine) | Yes (CLR) |
| Abstract classes | Yes | Yes (abc module) | Yes | No | Yes |
| Method overloading | Yes | No (last def wins) | Yes | No | Yes |
| Default method resolution | Single + interfaces | C3 linearization | Left-to-right DFS | Prototype chain | Single + interfaces |
| Class keyword syntax | Yes | Yes | Yes | Yes (sugar over prototype) | Yes |
Sources: Java Language Specification (JLS 21), Python Language Reference 3.12, ISO/IEC 14882:2020 (C++20), ECMAScript 2023 Specification, C# Language Specification (ECMA-334).
References
- IEEE SWEBOK Guide v4 — Software Engineering Body of Knowledge
- Python Language Reference 3.12 — Data Model
- PEP 8 — Style Guide for Python Code
- Java Language Specification, Java SE 21 Edition
- ECMAScript 2023 Language Specification (ECMA-262)
- ECMA-334: C# Language Specification
- ISO/IEC 14882:2020 — Programming Language C++ (C++20)
- MIT OpenCourseWare 6.009 — Fundamentals of Programming
- Alan Kay — "The Early History of Smalltalk" (1993)
- ACM Turing Award — Award Recipients Archive
- Robert C. Martin — "Design Principles and Design Patterns" (2000)