Abstraction
Abstraction is the concept of hiding complex implementation details and showing only the essential features of an object. It helps reduce complexity and increase efficiency by allowing users to interact with objects at a higher level without worrying about internal implementation.
Understanding Abstraction
Section titled “Understanding Abstraction”Abstraction focuses on what an object does rather than how it does it. It provides a simplified interface to complex systems.
Real-World Analogy
Section titled “Real-World Analogy”Think of a car:
- What you see: Steering wheel, pedals, gear shift
- What’s hidden: Engine mechanics, transmission system, fuel injection
You don’t need to know how the engine works to drive the car - that’s abstraction!
Abstract classes cannot be instantiated directly and must be subclassed. They define a contract that subclasses must follow.
Basic Abstract Class
Section titled “Basic Abstract Class”from abc import ABC, abstractmethod
class Shape(ABC): """Abstract base class - cannot be instantiated"""
@abstractmethod def area(self): """Abstract method - must be implemented by subclasses""" pass
@abstractmethod def perimeter(self): """Abstract method - must be implemented by subclasses""" pass
def describe(self): """Concrete method - can be used by all subclasses""" return f"Shape with area {self.area()} and perimeter {self.perimeter()}"
# This will raise TypeError# shape = Shape() # Can't instantiate abstract class
class Rectangle(Shape): """Concrete implementation of Shape""" def __init__(self, width: float, height: float): self.width = width self.height = height
def area(self): """Must implement abstract method""" return self.width * self.height
def perimeter(self): """Must implement abstract method""" return 2 * (self.width + self.height)
class Circle(Shape): """Concrete implementation of Shape""" def __init__(self, radius: float): self.radius = radius
def area(self): """Must implement abstract method""" import math return math.pi * self.radius ** 2
def perimeter(self): """Must implement abstract method""" import math return 2 * math.pi * self.radius
# Now we can create instancesrectangle = Rectangle(5, 3)print(rectangle.area()) # 15print(rectangle.describe()) # Uses inherited concrete method
circle = Circle(4)print(circle.area()) # ~50.27// Abstract classpublic abstract class Shape { // Abstract methods - must be implemented by subclasses public abstract double area(); public abstract double perimeter();
// Concrete method - can be used by all subclasses public String describe() { return String.format("Shape with area %.2f and perimeter %.2f", area(), perimeter()); }}
// Concrete implementationpublic class Rectangle extends Shape { private double width; private double height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
@Override public double area() { return width * height; }
@Override public double perimeter() { return 2 * (width + height); }}
public class Circle extends Shape { private double radius;
public Circle(double radius) { this.radius = radius; }
@Override public double area() { return Math.PI * radius * radius; }
@Override public double perimeter() { return 2 * Math.PI * radius; }}
// Usagepublic class Main { public static void main(String[] args) { Rectangle rectangle = new Rectangle(5, 3); System.out.println(rectangle.area()); // 15.0 System.out.println(rectangle.describe()); // Uses inherited concrete method
Circle circle = new Circle(4); System.out.println(circle.area()); // ~50.27 }}// Abstract classabstract class Shape { // Abstract methods - must be implemented by subclasses abstract area(): number; abstract perimeter(): number;
// Concrete method - can be used by all subclasses describe(): string { return `Shape with area ${this.area().toFixed(2)} and perimeter ${this.perimeter().toFixed(2)}`; }}
// Concrete implementationclass Rectangle extends Shape { constructor(private width: number, private height: number) { super(); }
area(): number { return this.width * this.height; }
perimeter(): number { return 2 * (this.width + this.height); }}
class Circle extends Shape { constructor(private radius: number) { super(); }
area(): number { return Math.PI * this.radius ** 2; }
perimeter(): number { return 2 * Math.PI * this.radius; }}
// Usageconst rectangle = new Rectangle(5, 3);console.log(rectangle.area()); // 15console.log(rectangle.describe()); // Uses inherited concrete method
const circle = new Circle(4);console.log(circle.area()); // ~50.27#include <iostream>#include <string>#include <cmath>#include <iomanip>
// Abstract classclass Shape {public: // Pure virtual methods - must be implemented by subclasses virtual double area() const = 0; virtual double perimeter() const = 0;
// Concrete method - can be used by all subclasses std::string describe() const { std::ostringstream oss; oss << std::fixed << std::setprecision(2); oss << "Shape with area " << area() << " and perimeter " << perimeter(); return oss.str(); }
virtual ~Shape() = default;};
// Concrete implementationclass Rectangle : public Shape {private: double width; double height;
public: Rectangle(double width, double height) : width(width), height(height) {}
double area() const override { return width * height; }
double perimeter() const override { return 2 * (width + height); }};
class Circle : public Shape {private: double radius;
public: Circle(double radius) : radius(radius) {}
double area() const override { return M_PI * radius * radius; }
double perimeter() const override { return 2 * M_PI * radius; }};
int main() { Rectangle rectangle(5, 3); std::cout << std::fixed << std::setprecision(2); std::cout << rectangle.area() << std::endl; // 15.00 std::cout << rectangle.describe() << std::endl; // Uses inherited concrete method
Circle circle(4); std::cout << circle.area() << std::endl; // ~50.27
return 0;}using System;
// Abstract classpublic abstract class Shape{ // Abstract methods - must be implemented by subclasses public abstract double Area(); public abstract double Perimeter();
// Concrete method - can be used by all subclasses public string Describe() { return $"Shape with area {Area():F2} and perimeter {Perimeter():F2}"; }}
// Concrete implementationpublic class Rectangle : Shape{ private double width; private double height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
public override double Area() { return width * height; }
public override double Perimeter() { return 2 * (width + height); }}
public class Circle : Shape{ private double radius;
public Circle(double radius) { this.radius = radius; }
public override double Area() { return Math.PI * radius * radius; }
public override double Perimeter() { return 2 * Math.PI * radius; }}
class Program{ static void Main() { Rectangle rectangle = new Rectangle(5, 3); Console.WriteLine(rectangle.Area()); // 15 Console.WriteLine(rectangle.Describe()); // Uses inherited concrete method
Circle circle = new Circle(4); Console.WriteLine(circle.Area()); // ~50.27 }}package main
import ( "fmt" "math")
type Shape interface { Area() float64 Perimeter() float64}
func Describe(s Shape) string { return fmt.Sprintf("Shape with area %.2f and perimeter %.2f", s.Area(), s.Perimeter())}
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
func main() { rect := Rectangle{5, 3} fmt.Println(rect.Area()) fmt.Println(Describe(rect))
circ := Circle{4} fmt.Println(circ.Area())}trait Shape { fn area(&self) -> f64; fn perimeter(&self) -> f64;}
fn describe(s: &dyn Shape) -> String { format!( "Shape with area {:.2} and perimeter {:.2}", s.area(), s.perimeter() )}
struct Rectangle { width: f64, height: f64,}
impl Shape for Rectangle { fn area(&self) -> f64 { self.width * self.height }
fn perimeter(&self) -> f64 { 2.0 * (self.width + self.height) }}
struct Circle { radius: f64,}
impl Shape for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
fn perimeter(&self) -> f64 { 2.0 * std::f64::consts::PI * self.radius }}
fn main() { let rect = Rectangle { width: 5.0, height: 3.0, }; println!("{}", rect.area()); println!("{}", describe(&rect));
let circ = Circle { radius: 4.0 }; println!("{}", circ.area());}Real-World Example: Payment Processing
Section titled “Real-World Example: Payment Processing”from abc import ABC, abstractmethod
class PaymentProcessor(ABC): """Abstract base class for payment processing"""
@abstractmethod def process_payment(self, amount: float) -> bool: """Process a payment - must be implemented""" pass
@abstractmethod def refund(self, transaction_id: str) -> bool: """Process a refund - must be implemented""" pass
def validate_amount(self, amount: float) -> bool: """Concrete method - shared validation logic""" return amount > 0
class CreditCardProcessor(PaymentProcessor): """Concrete implementation for credit card payments""" def __init__(self, api_key: str): self.api_key = api_key
def process_payment(self, amount: float) -> bool: """Process credit card payment""" if not self.validate_amount(amount): return False print(f"Processing ${amount} via credit card") return True
def refund(self, transaction_id: str) -> bool: """Process credit card refund""" print(f"Refunding transaction {transaction_id} via credit card") return True
class PayPalProcessor(PaymentProcessor): """Concrete implementation for PayPal payments""" def __init__(self, client_id: str, client_secret: str): self.client_id = client_id self.client_secret = client_secret
def process_payment(self, amount: float) -> bool: """Process PayPal payment""" if not self.validate_amount(amount): return False print(f"Processing ${amount} via PayPal") return True
def refund(self, transaction_id: str) -> bool: """Process PayPal refund""" print(f"Refunding transaction {transaction_id} via PayPal") return True
# Usage - abstraction allows treating all processors the same waydef checkout(processor: PaymentProcessor, amount: float): """Function works with any PaymentProcessor implementation""" return processor.process_payment(amount)
# All processors can be used interchangeablycredit_card = CreditCardProcessor("api_key_123")paypal = PayPalProcessor("client_id", "secret")
checkout(credit_card, 100.0) # Workscheckout(paypal, 100.0) # Works// Abstract classpublic abstract class PaymentProcessor { // Abstract methods - must be implemented public abstract boolean processPayment(double amount); public abstract boolean refund(String transactionId);
// Concrete method - shared validation logic public boolean validateAmount(double amount) { return amount > 0; }}
// Concrete implementationpublic class CreditCardProcessor extends PaymentProcessor { private String apiKey;
public CreditCardProcessor(String apiKey) { this.apiKey = apiKey; }
@Override public boolean processPayment(double amount) { if (!validateAmount(amount)) { return false; } System.out.println("Processing $" + amount + " via credit card"); return true; }
@Override public boolean refund(String transactionId) { System.out.println("Refunding transaction " + transactionId + " via credit card"); return true; }}
public class PayPalProcessor extends PaymentProcessor { private String clientId; private String clientSecret;
public PayPalProcessor(String clientId, String clientSecret) { this.clientId = clientId; this.clientSecret = clientSecret; }
@Override public boolean processPayment(double amount) { if (!validateAmount(amount)) { return false; } System.out.println("Processing $" + amount + " via PayPal"); return true; }
@Override public boolean refund(String transactionId) { System.out.println("Refunding transaction " + transactionId + " via PayPal"); return true; }}
// Usagepublic class Main { public static void checkout(PaymentProcessor processor, double amount) { processor.processPayment(amount); }
public static void main(String[] args) { PaymentProcessor creditCard = new CreditCardProcessor("api_key_123"); PaymentProcessor paypal = new PayPalProcessor("client_id", "secret");
checkout(credit_card, 100.0); // Works checkout(paypal, 100.0); // Works }}// Abstract base class for payment processingabstract class PaymentProcessor { // Abstract methods - must be implemented abstract processPayment(amount: number): boolean; abstract refund(transactionId: string): boolean;
// Concrete method - shared validation logic validateAmount(amount: number): boolean { return amount > 0; }}
// Concrete implementation for credit card paymentsclass CreditCardProcessor extends PaymentProcessor { constructor(private apiKey: string) { super(); }
processPayment(amount: number): boolean { if (!this.validateAmount(amount)) { return false; } console.log(`Processing $${amount} via credit card`); return true; }
refund(transactionId: string): boolean { console.log(`Refunding transaction ${transactionId} via credit card`); return true; }}
// Concrete implementation for PayPal paymentsclass PayPalProcessor extends PaymentProcessor { constructor(private clientId: string, private clientSecret: string) { super(); }
processPayment(amount: number): boolean { if (!this.validateAmount(amount)) { return false; } console.log(`Processing $${amount} via PayPal`); return true; }
refund(transactionId: string): boolean { console.log(`Refunding transaction ${transactionId} via PayPal`); return true; }}
// Usage - abstraction allows treating all processors the same wayfunction checkout(processor: PaymentProcessor, amount: number): boolean { return processor.processPayment(amount);}
const creditCard = new CreditCardProcessor("api_key_123");const paypal = new PayPalProcessor("client_id", "secret");
checkout(creditCard, 100.0); // Workscheckout(paypal, 100.0); // Works#include <iostream>#include <string>
// Abstract classclass PaymentProcessor {public: // Pure virtual methods - must be implemented virtual bool processPayment(double amount) = 0; virtual bool refund(const std::string& transactionId) = 0;
// Concrete method - shared validation logic virtual bool validateAmount(double amount) const { return amount > 0; }
virtual ~PaymentProcessor() = default;};
// Concrete implementationclass CreditCardProcessor : public PaymentProcessor {private: std::string apiKey;
public: CreditCardProcessor(const std::string& apiKey) : apiKey(apiKey) {}
bool processPayment(double amount) override { if (!validateAmount(amount)) { return false; } std::cout << "Processing $" << amount << " via credit card" << std::endl; return true; }
bool refund(const std::string& transactionId) override { std::cout << "Refunding transaction " << transactionId << " via credit card" << std::endl; return true; }};
class PayPalProcessor : public PaymentProcessor {private: std::string clientId; std::string clientSecret;
public: PayPalProcessor(const std::string& clientId, const std::string& clientSecret) : clientId(clientId), clientSecret(clientSecret) {}
bool processPayment(double amount) override { if (!validateAmount(amount)) { return false; } std::cout << "Processing $" << amount << " via PayPal" << std::endl; return true; }
bool refund(const std::string& transactionId) override { std::cout << "Refunding transaction " << transactionId << " via PayPal" << std::endl; return true; }};
// Usagevoid checkout(PaymentProcessor* processor, double amount) { processor->processPayment(amount);}
int main() { CreditCardProcessor creditCard("api_key_123"); PayPalProcessor paypal("client_id", "secret");
checkout(&creditCard, 100.0); // Works checkout(&paypal, 100.0); // Works
return 0;}using System;
// Abstract classpublic abstract class PaymentProcessor{ // Abstract methods - must be implemented public abstract bool ProcessPayment(double amount); public abstract bool Refund(string transactionId);
// Concrete method - shared validation logic public bool ValidateAmount(double amount) { return amount > 0; }}
// Concrete implementationpublic class CreditCardProcessor : PaymentProcessor{ private string apiKey;
public CreditCardProcessor(string apiKey) { this.apiKey = apiKey; }
public override bool ProcessPayment(double amount) { if (!ValidateAmount(amount)) { return false; } Console.WriteLine($"Processing ${amount} via credit card"); return true; }
public override bool Refund(string transactionId) { Console.WriteLine($"Refunding transaction {transactionId} via credit card"); return true; }}
public class PayPalProcessor : PaymentProcessor{ private string clientId; private string clientSecret;
public PayPalProcessor(string clientId, string clientSecret) { this.clientId = clientId; this.clientSecret = clientSecret; }
public override bool ProcessPayment(double amount) { if (!ValidateAmount(amount)) { return false; } Console.WriteLine($"Processing ${amount} via PayPal"); return true; }
public override bool Refund(string transactionId) { Console.WriteLine($"Refunding transaction {transactionId} via PayPal"); return true; }}
class Program{ static void Checkout(PaymentProcessor processor, double amount) { processor.ProcessPayment(amount); }
static void Main() { PaymentProcessor creditCard = new CreditCardProcessor("api_key_123"); PaymentProcessor paypal = new PayPalProcessor("client_id", "secret");
Checkout(creditCard, 100.0); // Works Checkout(paypal, 100.0); // Works }}package main
import "fmt"
type PaymentProcessor interface { ProcessPayment(amount float64) bool Refund(transactionID string) bool}
type paymentBase struct{}
func (paymentBase) validateAmount(amount float64) bool { return amount > 0 }
type CreditCardProcessor struct { paymentBase APIKey string}
func (c *CreditCardProcessor) ProcessPayment(amount float64) bool { if !c.validateAmount(amount) { return false } fmt.Printf("Processing $%g via credit card\n", amount) return true}
func (*CreditCardProcessor) Refund(id string) bool { fmt.Printf("Refunding transaction %s via credit card\n", id) return true}
type PayPalProcessor struct { paymentBase ClientID, ClientSecret string}
func (p *PayPalProcessor) ProcessPayment(amount float64) bool { if !p.validateAmount(amount) { return false } fmt.Printf("Processing $%g via PayPal\n", amount) return true}
func (*PayPalProcessor) Refund(id string) bool { fmt.Printf("Refunding transaction %s via PayPal\n", id) return true}
func checkout(proc PaymentProcessor, amount float64) { proc.ProcessPayment(amount)}
func main() { var cc PaymentProcessor = &CreditCardProcessor{APIKey: "api_key_123"} var pp PaymentProcessor = &PayPalProcessor{ClientID: "client_id", ClientSecret: "secret"} checkout(cc, 100.0) checkout(pp, 100.0)}trait PaymentProcessor { fn process_payment(&self, amount: f64) -> bool; fn refund(&self, transaction_id: &str) -> bool;}
fn validate_amount(amount: f64) -> bool { amount > 0.0}
struct CreditCardProcessor { api_key: String,}
impl CreditCardProcessor { fn validate_amount(&self, amount: f64) -> bool { validate_amount(amount) }}
impl PaymentProcessor for CreditCardProcessor { fn process_payment(&self, amount: f64) -> bool { if !self.validate_amount(amount) { return false; } println!("Processing ${} via credit card", amount); true }
fn refund(&self, transaction_id: &str) -> bool { println!("Refunding transaction {} via credit card", transaction_id); true }}
struct PayPalProcessor { client_id: String, client_secret: String,}
impl PayPalProcessor { fn validate_amount(&self, amount: f64) -> bool { validate_amount(amount) }}
impl PaymentProcessor for PayPalProcessor { fn process_payment(&self, amount: f64) -> bool { if !self.validate_amount(amount) { return false; } println!("Processing ${} via PayPal", amount); true }
fn refund(&self, transaction_id: &str) -> bool { println!("Refunding transaction {} via PayPal", transaction_id); true }}
fn checkout(proc: &dyn PaymentProcessor, amount: f64) { proc.process_payment(amount);}
fn main() { let cc: &dyn PaymentProcessor = &CreditCardProcessor { api_key: "api_key_123".into(), }; let pp: &dyn PaymentProcessor = &PayPalProcessor { client_id: "client_id".into(), client_secret: "secret".into(), }; checkout(cc, 100.0); checkout(pp, 100.0);}Abstract Properties
Section titled “Abstract Properties”You can also define abstract properties:
from abc import ABC, abstractmethod
class Animal(ABC): """Abstract base class with abstract properties"""
@property @abstractmethod def name(self) -> str: """Abstract property - must be implemented""" pass
@property @abstractmethod def sound(self) -> str: """Abstract property - must be implemented""" pass
def make_sound(self): """Concrete method using abstract properties""" return f"{self.name} says {self.sound}"
class Dog(Animal): def __init__(self, name: str): self._name = name
@property def name(self) -> str: return self._name
@property def sound(self) -> str: return "Woof!"
class Cat(Animal): def __init__(self, name: str): self._name = name
@property def name(self) -> str: return self._name
@property def sound(self) -> str: return "Meow!"
dog = Dog("Buddy")print(dog.make_sound()) # "Buddy says Woof!"
cat = Cat("Whiskers")print(cat.make_sound()) # "Whiskers says Meow!"// Abstract base class with abstract methods (Java doesn't have properties like Python)public abstract class Animal { // Abstract methods - must be implemented (similar to abstract properties) public abstract String getName(); public abstract String getSound();
// Concrete method using abstract methods public String makeSound() { return getName() + " says " + getSound(); }}
public class Dog extends Animal { private String name;
public Dog(String name) { this.name = name; }
@Override public String getName() { return name; }
@Override public String getSound() { return "Woof!"; }}
public class Cat extends Animal { private String name;
public Cat(String name) { this.name = name; }
@Override public String getName() { return name; }
@Override public String getSound() { return "Meow!"; }}
// Usagepublic class Main { public static void main(String[] args) { Dog dog = new Dog("Buddy"); System.out.println(dog.makeSound()); // "Buddy says Woof!"
Cat cat = new Cat("Whiskers"); System.out.println(cat.makeSound()); // "Whiskers says Meow!" }}Note: Java doesn’t have properties like Python. Abstract methods (getters) serve a similar purpose to abstract properties.
// Abstract base class with abstract gettersabstract class Animal { // Abstract getters - must be implemented abstract get name(): string; abstract get sound(): string;
// Concrete method using abstract properties makeSound(): string { return `${this.name} says ${this.sound}`; }}
class Dog extends Animal { constructor(private _name: string) { super(); }
get name(): string { return this._name; }
get sound(): string { return "Woof!"; }}
class Cat extends Animal { constructor(private _name: string) { super(); }
get name(): string { return this._name; }
get sound(): string { return "Meow!"; }}
const dog = new Dog("Buddy");console.log(dog.makeSound()); // "Buddy says Woof!"
const cat = new Cat("Whiskers");console.log(cat.makeSound()); // "Whiskers says Meow!"#include <iostream>#include <string>
// Abstract base class with abstract methods (C++ doesn't have properties)class Animal {public: // Pure virtual methods - must be implemented (similar to abstract properties) virtual std::string getName() const = 0; virtual std::string getSound() const = 0;
// Concrete method using abstract methods std::string makeSound() const { return getName() + " says " + getSound(); }
virtual ~Animal() = default;};
class Dog : public Animal {private: std::string name;
public: Dog(const std::string& name) : name(name) {}
std::string getName() const override { return name; }
std::string getSound() const override { return "Woof!"; }};
class Cat : public Animal {private: std::string name;
public: Cat(const std::string& name) : name(name) {}
std::string getName() const override { return name; }
std::string getSound() const override { return "Meow!"; }};
int main() { Dog dog("Buddy"); std::cout << dog.makeSound() << std::endl; // "Buddy says Woof!"
Cat cat("Whiskers"); std::cout << cat.makeSound() << std::endl; // "Whiskers says Meow!"
return 0;}Note: C++ doesn’t have properties like Python. Abstract methods (getters) serve a similar purpose to abstract properties.
using System;
// Abstract base class with abstract propertiespublic abstract class Animal{ // Abstract properties - must be implemented public abstract string Name { get; } public abstract string Sound { get; }
// Concrete method using abstract properties public string MakeSound() { return $"{Name} says {Sound}"; }}
public class Dog : Animal{ private string name;
public Dog(string name) { this.name = name; }
public override string Name { get { return name; } }
public override string Sound { get { return "Woof!"; } }}
public class Cat : Animal{ private string name;
public Cat(string name) { this.name = name; }
public override string Name { get { return name; } }
public override string Sound { get { return "Meow!"; } }}
class Program{ static void Main() { Dog dog = new Dog("Buddy"); Console.WriteLine(dog.MakeSound()); // "Buddy says Woof!"
Cat cat = new Cat("Whiskers"); Console.WriteLine(cat.MakeSound()); // "Whiskers says Meow!" }}package main
import "fmt"
type Animal interface { Name() string Sound() string MakeSound() string}
type Dog struct{ name string }
func NewDog(name string) *Dog { return &Dog{name: name} }
func (d *Dog) Name() string { return d.name }func (d *Dog) Sound() string { return "Woof!" }func (d *Dog) MakeSound() string { return fmt.Sprintf("%s says %s", d.Name(), d.Sound()) }
type Cat struct{ name string }
func NewCat(name string) *Cat { return &Cat{name: name} }
func (c *Cat) Name() string { return c.name }func (c *Cat) Sound() string { return "Meow!" }func (c *Cat) MakeSound() string { return fmt.Sprintf("%s says %s", c.Name(), c.Sound()) }
func main() { fmt.Println(NewDog("Buddy").MakeSound()) fmt.Println(NewCat("Whiskers").MakeSound())}trait Animal { fn name(&self) -> &str; fn sound(&self) -> &str; fn make_sound(&self) -> String { format!("{} says {}", self.name(), self.sound()) }}
struct Dog { name: String,}
impl Dog { fn new(name: impl Into<String>) -> Self { Self { name: name.into() } }}
impl Animal for Dog { fn name(&self) -> &str { &self.name }
fn sound(&self) -> &str { "Woof!" }}
struct Cat { name: String,}
impl Cat { fn new(name: impl Into<String>) -> Self { Self { name: name.into() } }}
impl Animal for Cat { fn name(&self) -> &str { &self.name }
fn sound(&self) -> &str { "Meow!" }}
fn main() { println!("{}", Dog::new("Buddy").make_sound()); println!("{}", Cat::new("Whiskers").make_sound());}Benefits of Abstraction
Section titled “Benefits of Abstraction”Example: Database Abstraction
Section titled “Example: Database Abstraction”from abc import ABC, abstractmethod
class Database(ABC): """Abstract database interface"""
@abstractmethod def connect(self): """Establish database connection""" pass
@abstractmethod def execute_query(self, query: str): """Execute a database query""" pass
@abstractmethod def close(self): """Close database connection""" pass
def __enter__(self): """Context manager entry""" self.connect() return self
def __exit__(self, exc_type, exc_val, exc_tb): """Context manager exit""" self.close()
class PostgreSQLDatabase(Database): """PostgreSQL implementation""" def connect(self): print("Connecting to PostgreSQL...")
def execute_query(self, query: str): print(f"Executing PostgreSQL query: {query}")
def close(self): print("Closing PostgreSQL connection")
class MongoDBDatabase(Database): """MongoDB implementation""" def connect(self): print("Connecting to MongoDB...")
def execute_query(self, query: str): print(f"Executing MongoDB query: {query}")
def close(self): print("Closing MongoDB connection")
# Code works with any database implementationdef run_query(database: Database, query: str): """Function works with any Database implementation""" with database: database.execute_query(query)
postgres = PostgreSQLDatabase()mongodb = MongoDBDatabase()
run_query(postgres, "SELECT * FROM users")run_query(mongodb, 'db.users.find({})')// Abstract database interfacepublic abstract class Database { // Abstract methods - must be implemented public abstract void connect(); public abstract void executeQuery(String query); public abstract void close();}
// PostgreSQL implementationpublic class PostgreSQLDatabase extends Database { @Override public void connect() { System.out.println("Connecting to PostgreSQL..."); }
@Override public void executeQuery(String query) { System.out.println("Executing PostgreSQL query: " + query); }
@Override public void close() { System.out.println("Closing PostgreSQL connection"); }}
// MongoDB implementationpublic class MongoDBDatabase extends Database { @Override public void connect() { System.out.println("Connecting to MongoDB..."); }
@Override public void executeQuery(String query) { System.out.println("Executing MongoDB query: " + query); }
@Override public void close() { System.out.println("Closing MongoDB connection"); }}
// Code works with any database implementationpublic class Main { // Function works with any Database implementation public static void runQuery(Database database, String query) { try { database.connect(); database.executeQuery(query); } finally { database.close(); } }
public static void main(String[] args) { Database postgres = new PostgreSQLDatabase(); Database mongodb = new MongoDBDatabase();
runQuery(postgres, "SELECT * FROM users"); runQuery(mongodb, "db.users.find({})"); }}Note: Java uses try-finally blocks for resource management, while Python uses context managers (with statement).
// Abstract database interfaceabstract class Database { // Abstract methods - must be implemented abstract connect(): void; abstract executeQuery(query: string): void; abstract close(): void;}
// PostgreSQL implementationclass PostgreSQLDatabase extends Database { connect(): void { console.log("Connecting to PostgreSQL..."); }
executeQuery(query: string): void { console.log(`Executing PostgreSQL query: ${query}`); }
close(): void { console.log("Closing PostgreSQL connection"); }}
// MongoDB implementationclass MongoDBDatabase extends Database { connect(): void { console.log("Connecting to MongoDB..."); }
executeQuery(query: string): void { console.log(`Executing MongoDB query: ${query}`); }
close(): void { console.log("Closing MongoDB connection"); }}
// Code works with any database implementationfunction runQuery(database: Database, query: string): void { try { database.connect(); database.executeQuery(query); } finally { database.close(); }}
const postgres = new PostgreSQLDatabase();const mongodb = new MongoDBDatabase();
runQuery(postgres, "SELECT * FROM users");runQuery(mongodb, 'db.users.find({})');Note: TypeScript doesn’t have context managers like Python. Use try-finally blocks for resource management.
#include <iostream>#include <string>
// Abstract database interfaceclass Database {public: // Pure virtual methods - must be implemented virtual void connect() = 0; virtual void executeQuery(const std::string& query) = 0; virtual void close() = 0; virtual ~Database() = default;};
// PostgreSQL implementationclass PostgreSQLDatabase : public Database {public: void connect() override { std::cout << "Connecting to PostgreSQL..." << std::endl; }
void executeQuery(const std::string& query) override { std::cout << "Executing PostgreSQL query: " << query << std::endl; }
void close() override { std::cout << "Closing PostgreSQL connection" << std::endl; }};
// MongoDB implementationclass MongoDBDatabase : public Database {public: void connect() override { std::cout << "Connecting to MongoDB..." << std::endl; }
void executeQuery(const std::string& query) override { std::cout << "Executing MongoDB query: " << query << std::endl; }
void close() override { std::cout << "Closing MongoDB connection" << std::endl; }};
// Code works with any database implementationvoid runQuery(Database* database, const std::string& query) { try { database->connect(); database->executeQuery(query); } catch (...) { database->close(); throw; } database->close();}
int main() { PostgreSQLDatabase postgres; MongoDBDatabase mongodb;
runQuery(&postgres, "SELECT * FROM users"); runQuery(&mongodb, "db.users.find({})");
return 0;}Note: C++ uses RAII (Resource Acquisition Is Initialization) or try-catch blocks for resource management.
using System;
// Abstract database interfacepublic abstract class Database{ // Abstract methods - must be implemented public abstract void Connect(); public abstract void ExecuteQuery(string query); public abstract void Close();}
// PostgreSQL implementationpublic class PostgreSQLDatabase : Database{ public override void Connect() { Console.WriteLine("Connecting to PostgreSQL..."); }
public override void ExecuteQuery(string query) { Console.WriteLine($"Executing PostgreSQL query: {query}"); }
public override void Close() { Console.WriteLine("Closing PostgreSQL connection"); }}
// MongoDB implementationpublic class MongoDBDatabase : Database{ public override void Connect() { Console.WriteLine("Connecting to MongoDB..."); }
public override void ExecuteQuery(string query) { Console.WriteLine($"Executing MongoDB query: {query}"); }
public override void Close() { Console.WriteLine("Closing MongoDB connection"); }}
class Program{ // Code works with any database implementation static void RunQuery(Database database, string query) { try { database.Connect(); database.ExecuteQuery(query); } finally { database.Close(); } }
static void Main() { Database postgres = new PostgreSQLDatabase(); Database mongodb = new MongoDBDatabase();
RunQuery(postgres, "SELECT * FROM users"); RunQuery(mongodb, "db.users.find({})"); }}Note: C# uses try-finally blocks for resource management, or using statements for IDisposable objects.
package main
import "fmt"
type Database interface { Connect() ExecuteQuery(query string) Close()}
type PostgreSQLDatabase struct{}
func (*PostgreSQLDatabase) Connect() { fmt.Println("Connecting to PostgreSQL...") }func (*PostgreSQLDatabase) ExecuteQuery(q string) { fmt.Println("Executing PostgreSQL query:", q) }func (*PostgreSQLDatabase) Close() { fmt.Println("Closing PostgreSQL connection") }
type MongoDBDatabase struct{}
func (*MongoDBDatabase) Connect() { fmt.Println("Connecting to MongoDB...") }func (*MongoDBDatabase) ExecuteQuery(q string) { fmt.Println("Executing MongoDB query:", q) }func (*MongoDBDatabase) Close() { fmt.Println("Closing MongoDB connection") }
func RunQuery(db Database, query string) { defer db.Close() db.Connect() db.ExecuteQuery(query)}
func main() { RunQuery(new(PostgreSQLDatabase), "SELECT * FROM users") RunQuery(new(MongoDBDatabase), "db.users.find({})")}trait Database { fn connect(&self); fn execute_query(&self, query: &str); fn close(&self);}
struct PostgreSqlDatabase;
impl Database for PostgreSqlDatabase { fn connect(&self) { println!("Connecting to PostgreSQL..."); }
fn execute_query(&self, query: &str) { println!("Executing PostgreSQL query: {}", query); }
fn close(&self) { println!("Closing PostgreSQL connection"); }}
struct MongoDbDatabase;
impl Database for MongoDbDatabase { fn connect(&self) { println!("Connecting to MongoDB..."); }
fn execute_query(&self, query: &str) { println!("Executing MongoDB query: {}", query); }
fn close(&self) { println!("Closing MongoDB connection"); }}
fn run_query(db: &dyn Database, query: &str) { db.connect(); db.execute_query(query); db.close();}
fn main() { run_query(&PostgreSqlDatabase, "SELECT * FROM users"); run_query(&MongoDbDatabase, "db.users.find({})");}Key Takeaways
Section titled “Key Takeaways”- Use abstraction to create flexible, maintainable code that can work with multiple implementations
Abstraction is about creating a contract that all implementations must follow, while hiding the complexity of how each implementation works internally.