Abstract Class
Memahami abstract classes, abstract methods, dan interface di Python
Apa itu Abstract Class?
Abstract class adalah class yang tidak dapat diinstansiasi secara langsung dan berfungsi sebagai blueprint atau template untuk class lain. Abstract class dapat berisi abstract methods (method tanpa implementasi) yang harus diimplementasikan oleh subclass.
Analogi
Bayangkan blueprint rumah. Kalian tidak bisa tinggal di dalam blueprint, tapi Kalian bisa menggunakan blueprint tersebut untuk membangun rumah yang sebenarnya. Abstract class adalah blueprint, dan concrete class adalah rumah yang dibangun dari blueprint tersebut.
Module ABC (Abstract Base Classes)
Python menyediakan module abc (Abstract Base Classes) untuk membuat abstract class:
from abc import ABC, abstractmethodKomponen Utama
- ABC: Base class untuk membuat abstract class
- @abstractmethod: Decorator untuk menandai method sebagai abstract
Membuat Abstract Class
Basic Abstract Class
from abc import ABC, abstractmethod
class Shape(ABC):
"""Abstract base class untuk bentuk geometris"""
@abstractmethod
def area(self):
"""Method abstract untuk menghitung luas"""
pass
@abstractmethod
def perimeter(self):
"""Method abstract untuk menghitung keliling"""
pass
def describe(self):
"""Method concrete (non-abstract)"""
return "Ini adalah bentuk geometris"
# Error - tidak bisa membuat instance dari abstract class
try:
shape = Shape()
except TypeError as e:
print(f"Error: {e}")
# Output: Can't instantiate abstract class Shape with abstract methods area, perimeterConcrete Implementation
Subclass harus mengimplementasikan semua abstract methods:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
def describe(self):
return f"Rectangle {self.width}x{self.height}"
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
def perimeter(self):
import math
return 2 * math.pi * self.radius
# Sekarang bisa membuat instance
rect = Rectangle(5, 4)
circle = Circle(3)
print(f"Rectangle area: {rect.area()}") # 20
print(f"Rectangle perimeter: {rect.perimeter()}") # 18
print(f"Circle area: {circle.area():.2f}") # 28.27
print(f"Circle perimeter: {circle.perimeter():.2f}") # 18.85Partial Implementation
Jika subclass tidak mengimplementasikan semua abstract methods, subclass tersebut juga harus abstract:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
@abstractmethod
def move(self):
pass
# Partial implementation - masih abstract
class Mammal(Animal):
def speak(self):
return "Some sound"
# move() belum diimplementasikan, jadi Mammal masih abstract
# Error - tidak bisa instantiate Mammal
try:
m = Mammal()
except TypeError as e:
print(f"Error: {e}")
# Concrete implementation
class Dog(Mammal):
def move(self):
return "Walking on four legs"
# Sekarang bisa instantiate
dog = Dog()
print(dog.speak()) # Some sound
print(dog.move()) # Walking on four legsAbstract Properties
Kalian juga bisa membuat abstract properties menggunakan @property dan @abstractmethod:
from abc import ABC, abstractmethod
class Vehicle(ABC):
@property
@abstractmethod
def max_speed(self):
"""Abstract property untuk kecepatan maksimum"""
pass
@property
@abstractmethod
def fuel_type(self):
"""Abstract property untuk jenis bahan bakar"""
pass
@abstractmethod
def start_engine(self):
pass
class Car(Vehicle):
def __init__(self, brand, model):
self.brand = brand
self.model = model
self._max_speed = 200
self._fuel_type = "Gasoline"
@property
def max_speed(self):
return self._max_speed
@property
def fuel_type(self):
return self._fuel_type
def start_engine(self):
return f"{self.brand} {self.model} engine started"
class ElectricCar(Vehicle):
def __init__(self, brand, model):
self.brand = brand
self.model = model
self._max_speed = 180
self._fuel_type = "Electric"
@property
def max_speed(self):
return self._max_speed
@property
def fuel_type(self):
return self._fuel_type
def start_engine(self):
return f"{self.brand} {self.model} motor started silently"
# Penggunaan
car = Car("Toyota", "Camry")
electric = ElectricCar("Tesla", "Model 3")
print(f"{car.brand}: Max speed {car.max_speed} km/h, Fuel: {car.fuel_type}")
print(f"{electric.brand}: Max speed {electric.max_speed} km/h, Fuel: {electric.fuel_type}")Interface Pattern
Python tidak memiliki keyword "interface" seperti Java, tetapi kita bisa membuat interface menggunakan abstract class di mana semua method adalah abstract:
from abc import ABC, abstractmethod
class Drawable(ABC):
"""Interface untuk object yang bisa digambar"""
@abstractmethod
def draw(self):
pass
@abstractmethod
def resize(self, scale):
pass
class Clickable(ABC):
"""Interface untuk object yang bisa diklik"""
@abstractmethod
def on_click(self):
pass
# Multiple interface implementation
class Button(Drawable, Clickable):
def __init__(self, text, x, y):
self.text = text
self.x = x
self.y = y
self.scale = 1.0
def draw(self):
return f"Drawing button '{self.text}' at ({self.x}, {self.y}), scale: {self.scale}"
def resize(self, scale):
self.scale = scale
return f"Button resized to {scale}x"
def on_click(self):
return f"Button '{self.text}' clicked!"
class Image(Drawable):
def __init__(self, src, x, y):
self.src = src
self.x = x
self.y = y
self.scale = 1.0
def draw(self):
return f"Drawing image '{self.src}' at ({self.x}, {self.y})"
def resize(self, scale):
self.scale = scale
return f"Image resized to {scale}x"
# Penggunaan
button = Button("Submit", 100, 200)
image = Image("logo.png", 50, 50)
print(button.draw())
print(button.resize(1.5))
print(button.on_click())
print(image.draw())
print(image.resize(2.0))Multiple Inheritance
Python mendukung multiple inheritance, sehingga sebuah class bisa mengimplementasikan multiple interfaces (abstract classes). Ini sangat berguna untuk membuat sistem yang modular dan fleksibel.
Contoh Lengkap: Database Connection
Mari lihat contoh lengkap abstract class untuk database connection:
from abc import ABC, abstractmethod
from datetime import datetime
class DatabaseConnection(ABC):
"""Abstract base class untuk database connection"""
def __init__(self, host, port, database):
self.host = host
self.port = port
self.database = database
self.connected = False
self.connection_time = None
@abstractmethod
def connect(self):
"""Connect to database"""
pass
@abstractmethod
def disconnect(self):
"""Disconnect from database"""
pass
@abstractmethod
def execute_query(self, query):
"""Execute a query"""
pass
@abstractmethod
def fetch_results(self):
"""Fetch query results"""
pass
def get_connection_info(self):
"""Concrete method - available to all subclasses"""
return {
'host': self.host,
'port': self.port,
'database': self.database,
'connected': self.connected,
'connection_time': self.connection_time
}
def __str__(self):
return f"{self.__class__.__name__}({self.host}:{self.port}/{self.database})"
class MySQLConnection(DatabaseConnection):
"""MySQL database connection implementation"""
def __init__(self, host, port, database, username, password):
super().__init__(host, port, database)
self.username = username
self._password = password # Private
self.last_query = None
self.results = []
def connect(self):
"""Simulate MySQL connection"""
print(f"Connecting to MySQL: {self.host}:{self.port}/{self.database}")
print(f"Username: {self.username}")
# Simulate connection
self.connected = True
self.connection_time = datetime.now()
print("MySQL connection established!")
return True
def disconnect(self):
"""Disconnect from MySQL"""
if self.connected:
print("Disconnecting from MySQL...")
self.connected = False
self.connection_time = None
print("Disconnected successfully!")
return True
else:
print("Already disconnected")
return False
def execute_query(self, query):
"""Execute MySQL query"""
if not self.connected:
raise Exception("Not connected to database")
print(f"Executing MySQL query: {query}")
self.last_query = query
# Simulate query execution
if "SELECT" in query.upper():
self.results = [
{'id': 1, 'name': 'John Doe', 'email': 'john@example.com'},
{'id': 2, 'name': 'Jane Smith', 'email': 'jane@example.com'}
]
else:
self.results = []
return True
def fetch_results(self):
"""Fetch MySQL results"""
return self.results
class PostgreSQLConnection(DatabaseConnection):
"""PostgreSQL database connection implementation"""
def __init__(self, host, port, database, username, password):
super().__init__(host, port, database)
self.username = username
self._password = password
self.last_query = None
self.results = []
def connect(self):
"""Simulate PostgreSQL connection"""
print(f"Connecting to PostgreSQL: {self.host}:{self.port}/{self.database}")
print(f"Username: {self.username}")
# Simulate connection
self.connected = True
self.connection_time = datetime.now()
print("PostgreSQL connection established!")
return True
def disconnect(self):
"""Disconnect from PostgreSQL"""
if self.connected:
print("Disconnecting from PostgreSQL...")
self.connected = False
self.connection_time = None
print("Disconnected successfully!")
return True
else:
print("Already disconnected")
return False
def execute_query(self, query):
"""Execute PostgreSQL query"""
if not self.connected:
raise Exception("Not connected to database")
print(f"Executing PostgreSQL query: {query}")
self.last_query = query
# Simulate query execution
if "SELECT" in query.upper():
self.results = [
{'id': 1, 'product': 'Laptop', 'price': 15000000},
{'id': 2, 'product': 'Mouse', 'price': 150000}
]
else:
self.results = []
return True
def fetch_results(self):
"""Fetch PostgreSQL results"""
return self.results
class MongoDBConnection(DatabaseConnection):
"""MongoDB connection implementation"""
def __init__(self, host, port, database):
super().__init__(host, port, database)
self.collections = {}
def connect(self):
print(f"Connecting to MongoDB: {self.host}:{self.port}/{self.database}")
self.connected = True
self.connection_time = datetime.now()
print("MongoDB connection established!")
return True
def disconnect(self):
if self.connected:
print("Disconnecting from MongoDB...")
self.connected = False
print("Disconnected successfully!")
return True
return False
def execute_query(self, query):
"""Execute MongoDB query (simplified)"""
if not self.connected:
raise Exception("Not connected to database")
print(f"Executing MongoDB query: {query}")
# Simulate MongoDB query
return True
def fetch_results(self):
"""Fetch MongoDB results"""
return [
{'_id': '507f1f77bcf86cd799439011', 'name': 'Document 1'},
{'_id': '507f1f77bcf86cd799439012', 'name': 'Document 2'}
]
# Database Manager - polymorphic usage
class DatabaseManager:
"""Manager untuk mengelola berbagai jenis database connections"""
def __init__(self):
self.connections = []
def add_connection(self, connection):
"""Add a database connection (polymorphic parameter)"""
if isinstance(connection, DatabaseConnection):
self.connections.append(connection)
print(f"Added connection: {connection}")
else:
raise TypeError("Connection must be a DatabaseConnection instance")
def connect_all(self):
"""Connect to all databases"""
print("\n" + "="*60)
print("Connecting to all databases...")
print("="*60)
for conn in self.connections:
conn.connect()
print()
def execute_on_all(self, query):
"""Execute query on all connected databases"""
print("\n" + "="*60)
print(f"Executing on all databases: {query}")
print("="*60)
for conn in self.connections:
if conn.connected:
print(f"\n{conn}:")
conn.execute_query(query)
results = conn.fetch_results()
print(f"Results: {len(results)} rows")
for row in results:
print(f" {row}")
def disconnect_all(self):
"""Disconnect from all databases"""
print("\n" + "="*60)
print("Disconnecting from all databases...")
print("="*60)
for conn in self.connections:
conn.disconnect()
print()
# Penggunaan
manager = DatabaseManager()
# Add different types of database connections
mysql_conn = MySQLConnection("localhost", 3306, "users_db", "root", "password123")
postgres_conn = PostgreSQLConnection("localhost", 5432, "products_db", "admin", "pass456")
mongo_conn = MongoDBConnection("localhost", 27017, "documents_db")
manager.add_connection(mysql_conn)
manager.add_connection(postgres_conn)
manager.add_connection(mongo_conn)
# Connect to all
manager.connect_all()
# Execute queries on all
manager.execute_on_all("SELECT * FROM table")
# Disconnect from all
manager.disconnect_all()Abstract Static Methods & Class Methods
Kalian juga bisa membuat abstract static methods dan class methods:
from abc import ABC, abstractmethod
class DataParser(ABC):
"""Abstract base class untuk data parser"""
@abstractmethod
def parse(self, data):
"""Instance method - parse data"""
pass
@staticmethod
@abstractmethod
def validate_format(data):
"""Static method - validate data format"""
pass
@classmethod
@abstractmethod
def from_file(cls, filename):
"""Class method - create parser from file"""
pass
class JSONParser(DataParser):
def __init__(self):
self.parsed_data = None
def parse(self, data):
"""Parse JSON data"""
import json
self.parsed_data = json.loads(data)
return self.parsed_data
@staticmethod
def validate_format(data):
"""Validate JSON format"""
import json
try:
json.loads(data)
return True
except json.JSONDecodeError:
return False
@classmethod
def from_file(cls, filename):
"""Create JSONParser from file"""
with open(filename, 'r') as f:
data = f.read()
parser = cls()
parser.parse(data)
return parser
class XMLParser(DataParser):
def __init__(self):
self.parsed_data = None
def parse(self, data):
"""Parse XML data"""
# Simplified - in reality would use xml.etree
self.parsed_data = data
return self.parsed_data
@staticmethod
def validate_format(data):
"""Validate XML format"""
return data.strip().startswith('<') and data.strip().endswith('>')
@classmethod
def from_file(cls, filename):
"""Create XMLParser from file"""
with open(filename, 'r') as f:
data = f.read()
parser = cls()
parser.parse(data)
return parser
# Penggunaan
json_data = '{"name": "John", "age": 30}'
xml_data = '<person><name>John</name><age>30</age></person>'
print("JSON valid:", JSONParser.validate_format(json_data))
print("XML valid:", XMLParser.validate_format(xml_data))
json_parser = JSONParser()
print("Parsed JSON:", json_parser.parse(json_data))Praktik Terbaik
1. Use Abstract Classes untuk Shared Behavior
from abc import ABC, abstractmethod
class Report(ABC):
"""Base class untuk semua report"""
def __init__(self, title):
self.title = title
self.data = []
def add_data(self, item):
"""Concrete method - shared behavior"""
self.data.append(item)
@abstractmethod
def generate(self):
"""Abstract method - must be implemented by subclass"""
pass
def export(self, filename):
"""Template method pattern"""
content = self.generate()
with open(filename, 'w') as f:
f.write(content)
return f"Report exported to {filename}"2. Keep Abstract Classes Focused
# Good - focused interface
class Sortable(ABC):
@abstractmethod
def sort(self):
pass
class Searchable(ABC):
@abstractmethod
def search(self, query):
pass
# Better than one large abstract class with many methods3. Document Abstract Methods
from abc import ABC, abstractmethod
class Plugin(ABC):
@abstractmethod
def initialize(self):
"""
Initialize the plugin.
This method should set up any necessary resources
and prepare the plugin for use.
Returns:
bool: True if initialization successful
"""
passKapan Menggunakan Abstract Class?
| Gunakan Abstract Class | Jangan Gunakan |
|---|---|
| Ada shared code antar subclass | Tidak ada kode yang dibagikan |
| Ingin memaksa implementasi method tertentu | Hanya butuh dokumentasi |
| Membuat hierarchy yang kompleks | Cukup dengan duck typing |
| Ingin type checking yang ketat | Flexibilitas lebih penting |
Latihan
-
Buat abstract class
FileHandlerdengan methods:read()- abstractwrite()- abstractclose()- concrete- Implementasikan
TextFileHandlerdanBinaryFileHandler
-
Buat interface
Serializabledengan methods:to_json()- abstractfrom_json()- abstract class method- Implementasikan untuk minimal 2 class berbeda
-
Buat abstract class
GameCharacterdengan:- Abstract properties:
health,attack_power - Abstract methods:
attack(),defend() - Concrete method:
is_alive() - Implementasikan
Warrior,Mage,Archer
- Abstract properties:
