A operate that reads the system clock to resolve between “Good morning” and “Good night” is difficult to check: its output is dependent upon when the check runs. Change it to simply accept any object that may report the hour, and the issue disappears. This system passes in the true clock; the check passes in a clock caught at 8:00. The operate by no means is aware of which one it has. That’s abstraction: code written towards what one thing does, not how it does it.
This information explains abstraction as an object-oriented idea, with the identical instance in C++, Java and Python: what abstraction means, how summary courses and interfaces specific it, how Python does it with summary base courses and protocols, and methods to inform a helpful abstraction from an pointless one. Each program was run for this text on Ubuntu 24.04 with GCC 13.3 and Clang 18.1.3 (-std=c++17 -Wall -Wextra -pedantic -Werror), OpenJDK 21 (javac -Xlint:all -Werror) and Python 3.11, 3.12 and three.13 (-W error), and each output under is captured verbatim. The code is in a GitHub repository whose construct repeats these checks on every commit.
The Brief Reply
What’s abstraction in OOP? Describing a kind by what it does (its operations and their guarantees) and conserving the way it does them out of view. Code that makes use of the kind relies upon solely on that description, so any implementation that retains the guarantees can be utilized as a replacement.
What’s the distinction between abstraction and encapsulation? Abstraction decides what the surface world sees; encapsulation protects what it doesn’t see. An interface is an abstraction; non-public fields guarded by strategies are encapsulation. Most well-designed courses use each.
What’s the distinction between an summary class and an interface? An summary class can maintain fields and dealing code alongside the operations it leaves unimplemented, and a category can prolong just one. An interface (in Java) states operations solely, other than default strategies, and a category can implement many. C++ has no interface key phrase; a category with solely pure digital features performs that function.
What Is Abstraction?
Abstraction in object-oriented programming is the follow of representing one thing by its important operations whereas hiding the small print of how these operations are carried out. In code, it normally takes the type of an summary sort, an summary class or an interface, that declares what operations exist and what they promise, with concrete courses that present the implementations. Code written towards the summary sort works with each implementation.
Abstraction is older than object-oriented programming, and it seems at a number of ranges:
| Type | What’s hidden | Instance |
|---|---|---|
| Procedural abstraction | The steps of an algorithm | Calling kind() with out figuring out which sorting algorithm it makes use of |
| Knowledge abstraction | How a worth is saved | An account exposes steadiness(); whether or not it shops the steadiness or provides up its historical past is invisible |
| Summary sort | Which class does the work | greeting() takes any Clock, actual or fastened |
Barbara Liskov and Stephen Zilles described the second kind in 1974 as summary information varieties: a kind outlined completely by the operations that may be carried out on it. Object-oriented languages add the third kind, by which a number of courses implement the identical summary sort and callers can’t inform them aside. That substitution can also be what makes inheritance and polymorphism helpful.
One Abstraction in C++, Java and Python
The Clock sort under guarantees one factor: hour() returns the present hour, from 0 to 23. SystemClock retains the promise by studying the true time; FixedClock retains it by returning no matter hour it was given. greeting() is written towards Clock alone:
C++:
// greeting.cpp - abstraction: greeting() is dependent upon what a clock does
// (report the hour), not on how any explicit clock finds it out.
#embrace <ctime>
#embrace <iomanip>
#embrace <iostream>
#embrace <string>
class Clock {
public:
digital ~Clock() = default;
digital int hour() const = 0; // 0 to 23; each concrete clock should say how
};
class SystemClock : public Clock {
public:
int hour() const override {
std::time_t now = std::time(nullptr);
return std::localtime(&now)->tm_hour;
}
};
class FixedClock : public Clock {
public:
specific FixedClock(int hour) : hour_(hour) {}
int hour() const override { return hour_; }
non-public:
int hour_;
};
std::string greeting(const Clock& clock) {
int h = clock.hour();
if (h < 12) return "Good morning";
if (h < 18) return "Good afternoon";
return "Good night";
}
int major() {
for (int h : {8, 14, 21}) {
FixedClock clock(h);
std::cout << std::setw(2) << std::setfill('0') << h << ":00 -> " << greeting(clock) << 'n';
}
SystemClock now;
std::string g = greeting(now);
bool identified = g == "Good morning" || g == "Good afternoon" || g == "Good night";
std::cout << "system clock: hour in 0..23 = " << std::boolalpha
<< (now.hour() >= 0 && now.hour() <= 23) << ", greeting identified = " << identified << 'n';
// Clock c; // doesn't compile: Clock is summary
}
Java:
// Greeting.java - abstraction: greeting() is dependent upon what a clock does
// (report the hour), not on how any explicit clock finds it out.
import java.time.LocalTime;
import java.util.Checklist;
public remaining class Greeting {
interface Clock {
int hour(); // 0 to 23; each implementation should say how
}
static remaining class SystemClock implements Clock {
@Override
public int hour() { return LocalTime.now().getHour(); }
}
document FixedClock(int hour) implements Clock {} // the document's hour() implements Clock
static String greeting(Clock clock) {
int h = clock.hour();
if (h < 12) return "Good morning";
if (h < 18) return "Good afternoon";
return "Good night";
}
public static void major(String[] args) {
for (int h : new int[] {8, 14, 21}) {
System.out.printf("%02d:00 -> %spercentn", h, greeting(new FixedClock(h)));
}
Clock now = new SystemClock();
String g = greeting(now);
boolean identified = Checklist.of("Good morning", "Good afternoon", "Good night").comprises(g);
System.out.println("system clock: hour in 0..23 = " + (now.hour() >= 0 && now.hour() <= 23)
+ ", greeting identified = " + identified);
// Clock c = new Clock(); // doesn't compile: Clock is summary
}
}
Python:
"""greeting.py - abstraction: greeting() is dependent upon what a clock does
(report the hour), not on how any explicit clock finds it out."""
from abc import ABC, abstractmethod
from datetime import datetime
class Clock(ABC):
@abstractmethod
def hour(self) -> int:
"""0 to 23; each concrete clock should say how."""
class SystemClock(Clock):
def hour(self) -> int:
return datetime.now().hour
class FixedClock(Clock):
def __init__(self, hour: int) -> None:
self._hour = hour
def hour(self) -> int:
return self._hour
def greeting(clock: Clock) -> str:
h = clock.hour()
if h < 12:
return "Good morning"
if h < 18:
return "Good afternoon"
return "Good night"
for h in (8, 14, 21):
print(f"{h:02d}:00 -> {greeting(FixedClock(h))}")
now = SystemClock()
identified = greeting(now) in ("Good morning", "Good afternoon", "Good night")
print(f"system clock: hour in 0..23 = {str(0 <= now.hour() <= 23).decrease()}, greeting identified = {str(identified).decrease()}")
# Clock() # raises TypeError: Clock is summary
Output (equivalent for all three):
08:00 -> Good morning
14:00 -> Good afternoon
21:00 -> Good night
system clock: hour in 0..23 = true, greeting identified = true
The primary three traces are the explanation the abstraction exists. greeting() was checked at 8:00, 14:00 and 21:00 in a single run, and the end result is identical each time, which is what an automatic check wants. The final line makes use of the true clock; its actual greeting is dependent upon when this system runs, so this system prints solely that the hour was in vary and the greeting was one of many three. Neither line wanted any change to greeting().
Every language declares the abstraction in a different way:
- C++ makes
hour()a pure digital operate (= 0). A category with at the least one pure digital operate is summary, and every derived class should override it earlier than objects of that class can exist. The main points, together with pure digital destructors, are in pure digital features in C++. - Java makes use of an
interface.FixedClockis adocument, whose generated accessorhour()implements the interface with no additional code. - Python inherits from
abc.ABCand markshour()with@abstractmethod.
Summary Sorts Can’t Be Created
An summary sort describes objects however has none of its personal, and all three languages refuse to create one. C++ and Java refuse at compile time; Python refuses when the thing is created:
| Language | Assertion | Consequence |
|---|---|---|
| C++ | Clock c; |
GCC: can not declare variable 'c' to be of summary sort 'Clock' |
| Java | Clock c = new Clock(); |
Clock is summary; can't be instantiated |
| Python 3.12 and three.13 | Clock() |
TypeError: Cannot instantiate summary class Clock with out an implementation for summary technique 'hour' |
| Python 3.11 | Clock() |
TypeError: Cannot instantiate summary class Clock with summary technique hour |
The identical examine catches a subclass that forgets an operation: in C++, a category derived from Clock with out an hour() override is summary too, and declaring one produced the identical GCC error. Python’s wording modified in 3.12, which is why the Python program within the subsequent part prints its personal message quite than the interpreter’s.
Summary Lessons vs Interfaces
Each declare operations with out (totally) implementing them. They differ in what else they will include and in what number of a category can inherit:
| Side | Summary class | Interface |
|---|---|---|
| C++ | A category with at the least one pure digital operate; can have information members and carried out features | No key phrase; by conference a category with solely pure digital features and a digital destructor |
| Java | summary class; fields, constructors, any entry stage; a category extends just one |
interface; no occasion fields, summary and default strategies (Java 8+), plus static and personal helpers; a category implements any quantity |
| Python | A category derived from abc.ABC with @abstractmethod strategies; checked when an object is created |
typing.Protocol; matched by any class with the correct strategies, checked by static sort checkers |
| Use it for | A household of intently associated courses that share code or state | A functionality that unrelated courses can supply |
Java’s tutorial places the selection in the identical phrases: think about an summary class to share code amongst a number of intently associated courses, and an interface when unrelated courses would implement it (Summary Lessons In comparison with Interfaces). Clock is an interface by that check: a system clock and a check clock share nothing besides the promise.
C++ has a second solution to specific an abstraction that has no run-time price: a template accepts any sort with the required operations, checked at compile time, and C++20 ideas let the necessities be named and acknowledged. The usual library’s personal clocks (std::chrono::system_clock, steady_clock) work that method. Java’s commonplace library additionally has an summary clock, java.time.Clock, with Clock.fastened(...) for exams, which is identical thought as the instance above.
Abstraction in Python: ABCs and Protocols
Python gives two methods to state an abstraction, and so they examine various things. An summary base class requires implementations to inherit from it, and refuses to create an object that has not carried out each summary technique. A protocol (typing.Protocol, added in Python 3.8) requires solely {that a} class has the correct strategies. The Python documentation calls this structural subtyping, or “static duck-typing” (typing documentation):
"""protocol.py - two methods to state an abstraction in Python.
An ABC is checked when an object is created; a Protocol is checked solely
by a static sort checker, and any object with the correct technique works."""
from abc import ABC, abstractmethod
from typing import Protocol
class Clock(Protocol):
def hour(self) -> int: ...
class Sundial: # no base class: it matches Clock by having hour()
def hour(self) -> int:
return 9
def greeting(clock: Clock) -> str:
h = clock.hour()
if h < 12:
return "Good morning"
if h < 18:
return "Good afternoon"
return "Good night"
print(f"Sundial (no base class) -> {greeting(Sundial())}")
class AbstractClock(ABC):
@abstractmethod
def hour(self) -> int: ...
class BrokenClock(AbstractClock): # forgot to implement hour()
cross
attempt:
BrokenClock()
besides TypeError:
print("BrokenClock() -> TypeError: the summary technique hour() was not carried out")
Output:
Sundial (no base class) -> Good morning
BrokenClock() -> TypeError: the summary technique hour() was not carried out
Sundial by no means mentions Clock, but greeting() accepted it, as a result of it has an hour() technique. A sort checker equivalent to mypy would settle for it too, because it matches the protocol’s form. BrokenClock, then again, inherits from an summary base class and forgot hour(), so creating it failed instantly, earlier than any code may name the lacking technique. The abc documentation states the rule: a category with summary strategies “can’t be instantiated until all of its summary strategies and properties are overridden.”
Use an ABC while you need the assure enforced at run time and implementations to declare their intent by inheriting. Use a protocol when implementations come from code you don’t management, or when duck typing is sufficient and also you need a sort checker to substantiate it.
Selecting Good Abstractions
An abstraction is price having when it lets code ignore a element it shouldn’t depend upon. 4 habits assist:
- Identify the abstraction for what it does.
Clock.hour()says what callers get. A reputation equivalent toTimeHelper.getLocalHourFromSystem()describes one implementation, and the abstraction is misplaced. - Hold it small.
Clockhas one technique, so a brand new implementation takes just a few traces (one, as a Java document). An interface with fifteen strategies is difficult to implement for a check, and most callers use just a few of them. - Put it at a boundary. Time, recordsdata, networks, databases and randomness are the place abstractions pay for themselves first: they’re sluggish or unpredictable, and exams want to exchange them.
- Look ahead to the second implementation. An interface with one implementation and no check double provides a file and a layer of indirection, and its design is usually flawed when the second case lastly arrives. Extract it when it’s wanted.
Each abstraction additionally hides one thing that may matter. Joel Spolsky referred to as this the Legislation of Leaky Abstractions: “All non-trivial abstractions, to some extent, are leaky” (Joel on Software program, 2002). The Clock above is a small instance. SystemClock returns the hour within the laptop’s native time zone, which the Clock interface doesn’t point out, so the identical program, run on a server set to UTC, greets a person in Tokyo with “Good morning” at 7 p.m. Tokyo time. A greater Clock would state its time zone, or take one as a parameter. Good abstractions say which particulars they disguise and which of them the caller nonetheless wants to consider.
Abstraction vs Encapsulation
The 2 are taught collectively and sometimes confused:
| Side | Abstraction | Encapsulation |
|---|---|---|
| Query it solutions | What does this kind supply, no matter implementation? | Who could change this object’s information, and the way? |
| Most important instruments | Summary courses, interfaces, protocols | Non-public fields, strategies that examine each change |
| Degree | Design: which operations exist | Implementation: how one class protects its state |
| Within the examples | greeting() accepts any Clock |
An account’s steadiness modifications solely by way of withdraw |
They help one another. An abstraction is simply as dependable because the implementations behind it, and encapsulation is what retains these implementations legitimate. The safety aspect is roofed in encapsulation in object-oriented programming.
Key Takeaways
- Abstraction describes a kind by what it does and hides how. Code written towards the summary sort works with each implementation.
- Summary varieties can’t be instantiated. C++ and Java reject
Clockat compile time; Python raisesTypeErrorwhen the thing is created. - An summary class can share code and state; an interface states a functionality. In C++ each are courses with pure digital features; in Python they map to ABCs and protocols.
- Abstractions make code testable. A hard and fast clock let one run examine thrice of day, with the identical end result on each run.
- Each non-trivial abstraction leaks someplace. The system clock’s time zone is a element the
Clockinterface doesn’t point out however callers might have.
Conclusion
Abstraction is a call about what code is allowed to depend upon. When greeting() is dependent upon “one thing that is aware of the hour” as a substitute of the system clock, it turns into testable, and a brand new form of clock prices nothing. C++, Java and Python every have their very own syntax for that call: pure digital features, interfaces, and summary base courses or protocols. In all three, the helpful questions are which operations callers actually need, and what the abstraction ought to admit it’s hiding.
The idea pages on this collection cowl the remainder of the image: courses and objects are the constructing blocks, encapsulation protects their state, and inheritance lets one summary sort have many implementations. Extra subjects are collected within the OOP part.
Supply Code and Exams
All applications on this web page are within the oop/abstraction-concepts listing of the MYCPLUS C++ examples repository.
Construct and check:
bash exams/run_tests.sh g++
What the construct checks. On Linux, with GCC and with Clang, it compiles the C++ program with -Wall -Wextra -pedantic -Werror, compiles the Java program with javac -Xlint:all -Werror on Java 21, runs the Python applications with -W error on Python 3.12, and checks that each program prints precisely the output proven on this web page. Python 3.11 and three.13 have been checked regionally and produced the identical output. The compile errors and the TypeError messages within the desk have been produced by briefly including the statements proven; they don’t seem to be a part of the automated construct.

