Encapsulation
Melindungi data dengan access modifiers dan property decorators
Apa itu Encapsulation?
Encapsulation adalah prinsip OOP yang menyembunyikan detail implementasi internal dari pengguna eksternal. Dengan encapsulation, kita dapat:
- Melindungi data dari akses atau modifikasi yang tidak sah
- Mengontrol bagaimana data diakses dan dimodifikasi
- Menyediakan interface yang bersih untuk berinteraksi dengan object
- Mengurangi coupling antar komponen
Analogi
Encapsulation seperti ATM. Kalian tidak perlu tahu bagaimana ATM bekerja secara internal (koneksi database, mekanisme pengeluaran uang, dll). Kalian hanya berinteraksi melalui interface yang disediakan (tombol dan layar).
Access Modifiers di Python
Python tidak memiliki access modifiers strict seperti Java (public, private, protected). Sebaliknya, Python menggunakan naming conventions untuk mengindikasikan tingkat akses:
| Convention | Akses | Contoh | Deskripsi |
|---|---|---|---|
| No underscore | Public | name | Dapat diakses dari mana saja |
| Single underscore | Protected | _name | Konvensi untuk internal use (masih bisa diakses) |
| Double underscore | Private | __name | Name mangling - sulit diakses dari luar |
Python Philosophy
Python mengikuti prinsip "We are all consenting adults here". Access modifiers lebih merupakan konvensi daripada enforcement. Developer dipercaya untuk mengikuti konvensi yang ada.
Public Attributes
Atribut public dapat diakses dan dimodifikasi dari mana saja:
class Student:
def __init__(self, name, nim):
self.name = name # Public attribute
self.nim = nim # Public attribute
student = Student("Budi", "TI12345")
# Akses public attribute
print(student.name) # Budi
# Modifikasi public attribute
student.name = "Budi Santoso"
print(student.name) # Budi SantosoProtected Attributes
Atribut protected ditandai dengan single underscore _. Ini adalah konvensi yang menandakan atribut untuk internal use, tapi masih bisa diakses:
class Student:
def __init__(self, name, nim):
self.name = name
self._program = "Teknik" # Protected attribute
def get_program(self):
return self._program
student = Student("Budi", "TI12345")
# Bisa diakses tapi tidak disarankan
print(student._program) # Teknik
# Cara yang disarankan
print(student.get_program()) # TeknikKonvensi Protected
Protected attributes dapat diakses, tetapi dengan konvensi single underscore, developer lain tahu bahwa ini untuk internal use dan tidak seharusnya diakses langsung dari luar class.
Private Attributes
Atribut private ditandai dengan double underscore __. Python melakukan "name mangling" sehingga atribut ini sulit diakses dari luar class:
class Student:
def __init__(self, name, nim):
self.name = name
self.__id = "2023-" + nim # Private attribute
def get_id(self):
return self.__id
student = Student("Budi", "12345")
# Akses melalui method public
print(student.get_id()) # 2023-12345
# Akses langsung akan error
try:
print(student.__id)
except AttributeError as e:
print(f"Error: {e}")
# Name mangling - bisa diakses tapi tidak disarankan
print(student._Student__id) # 2023-12345Property Decorators
Property decorators menyediakan cara Pythonic untuk membuat getter dan setter:
Getter dengan @property
Property decorator mengubah method menjadi attribute yang dapat dibaca:
class Student:
def __init__(self, name, birth_year):
self.name = name
self._birth_year = birth_year
@property
def age(self):
from datetime import datetime
current_year = datetime.now().year
return current_year - self._birth_year
@property
def birth_year(self):
return self._birth_year
student = Student("Budi", 2000)
# Memanggil seperti attribute, bukan method
print(student.age) # 25 (tergantung tahun saat ini)
print(student.birth_year) # 2000Setter dengan @property.setter
Setter memungkinkan kita mengontrol bagaimana attribute dimodifikasi:
class Student:
def __init__(self, name, nim):
self.name = name
self._nim = nim
self._program = "Teknik"
@property
def program(self):
return self._program
@program.setter
def program(self, value):
valid_programs = ["Teknik", "Sains", "Bisnis"]
if value in valid_programs:
self._program = value
else:
raise ValueError(f"Program harus salah satu dari {valid_programs}")
student = Student("Budi", "12345")
# Menggunakan setter
student.program = "Sains" # OK
print(student.program) # Sains
try:
student.program = "Hukum" # Error
except ValueError as e:
print(f"Error: {e}")Deleter dengan @property.deleter
Deleter mengontrol perilaku ketika attribute dihapus:
class Student:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value:
raise ValueError("Name cannot be empty")
self._name = value
@name.deleter
def name(self):
print("Deleting name...")
self._name = None
student = Student("Budi")
print(student.name) # Budi
del student.name # Deleting name...
print(student.name) # NoneContoh Lengkap: Bank Account
Mari lihat contoh lengkap encapsulation pada class BankAccount:
class BankAccount:
"""Class untuk merepresentasikan akun bank dengan encapsulation"""
def __init__(self, account_number, owner, initial_balance=0):
self.account_number = account_number # Public
self._owner = owner # Protected
self.__balance = initial_balance # Private
self.__transaction_history = [] # Private
# Property untuk balance (read-only)
@property
def balance(self):
"""Getter untuk balance - read only"""
return self.__balance
# Property untuk owner
@property
def owner(self):
return self._owner
@owner.setter
def owner(self, new_owner):
if not new_owner or len(new_owner) < 3:
raise ValueError("Owner name must be at least 3 characters")
self._owner = new_owner
# Method untuk deposit
def deposit(self, amount):
"""Public method untuk deposit uang"""
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.__balance += amount
self.__add_transaction("Deposit", amount)
return f"Deposit successful. New balance: Rp{self.__balance:,.0f}"
# Method untuk withdraw
def withdraw(self, amount):
"""Public method untuk withdraw uang"""
if amount <= 0:
raise ValueError("Withdraw amount must be positive")
if amount > self.__balance:
raise ValueError("Insufficient balance")
self.__balance -= amount
self.__add_transaction("Withdraw", amount)
return f"Withdraw successful. New balance: Rp{self.__balance:,.0f}"
# Method untuk transfer
def transfer(self, target_account, amount):
"""Public method untuk transfer ke akun lain"""
if amount <= 0:
raise ValueError("Transfer amount must be positive")
if amount > self.__balance:
raise ValueError("Insufficient balance")
self.__balance -= amount
target_account.__balance += amount
self.__add_transaction(f"Transfer to {target_account.account_number}", amount)
target_account.__add_transaction(f"Transfer from {self.account_number}", amount)
return f"Transfer successful. New balance: Rp{self.__balance:,.0f}"
# Private method untuk menambah transaksi
def __add_transaction(self, type, amount):
"""Private method - hanya untuk internal use"""
from datetime import datetime
transaction = {
'type': type,
'amount': amount,
'timestamp': datetime.now(),
'balance_after': self.__balance
}
self.__transaction_history.append(transaction)
# Public method untuk melihat history
def get_transaction_history(self, last_n=5):
"""Public method untuk mendapatkan transaction history"""
return self.__transaction_history[-last_n:]
# Method untuk display info
def display_info(self):
print(f"\n{'='*50}")
print(f"Account Number: {self.account_number}")
print(f"Owner: {self._owner}")
print(f"Balance: Rp{self.__balance:,.0f}")
print(f"{'='*50}")
# Penggunaan
print("=== Creating Bank Accounts ===")
acc1 = BankAccount("001", "Budi Santoso", 1000000)
acc2 = BankAccount("002", "Ani Wijaya", 500000)
print("\n=== Account Info ===")
acc1.display_info()
print("\n=== Deposit ===")
print(acc1.deposit(500000))
print("\n=== Withdraw ===")
print(acc1.withdraw(200000))
print("\n=== Transfer ===")
print(acc1.transfer(acc2, 300000))
print("\n=== Check Balance (using property) ===")
print(f"Acc1 Balance: Rp{acc1.balance:,.0f}")
print(f"Acc2 Balance: Rp{acc2.balance:,.0f}")
print("\n=== Transaction History ===")
for trans in acc1.get_transaction_history():
print(f"{trans['type']}: Rp{trans['amount']:,.0f} "
f"- Balance: Rp{trans['balance_after']:,.0f}")
print("\n=== Try to access private attribute (will error) ===")
try:
print(acc1.__balance)
except AttributeError as e:
print(f"Error: {e}")
print("\n=== Access via name mangling (not recommended) ===")
print(f"Balance via name mangling: Rp{acc1._BankAccount__balance:,.0f}")
print("\n=== Try invalid withdraw ===")
try:
acc1.withdraw(10000000)
except ValueError as e:
print(f"Error: {e}")Kapan Menggunakan Access Modifiers
Gunakan Public
class Product:
def __init__(self, name, price):
self.name = name # Public - data yang umum diakses
self.price = price # PublicGunakan Protected
class DatabaseConnection:
def __init__(self, host):
self._host = host # Protected - untuk subclass
self._connection = None # Protected
def _connect(self): # Protected method
# Internal implementation
passGunakan Private
class PasswordManager:
def __init__(self, password):
self.__password = password # Private - data sensitif
def verify(self, input_password):
return input_password == self.__passwordPraktik Terbaik
1. Gunakan Properties untuk Validasi
class Student:
def __init__(self, name, gpa):
self.name = name
self._gpa = None
self.gpa = gpa # Using setter for validation
@property
def gpa(self):
return self._gpa
@gpa.setter
def gpa(self, value):
if not 0.0 <= value <= 4.0:
raise ValueError("GPA must be between 0.0 and 4.0")
self._gpa = value2. Encapsulate Complex Logic
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@property
def diameter(self):
return self._radius * 2
@property
def area(self):
import math
return math.pi * self._radius ** 2
@property
def circumference(self):
import math
return 2 * math.pi * self._radius3. Don't Expose Mutable Collections
# Bad - exposes internal list
class Classroom:
def __init__(self):
self.students = []
# Good - provides controlled access
class Classroom:
def __init__(self):
self.__students = []
def add_student(self, student):
self.__students.append(student)
def get_students(self):
return self.__students.copy() # Return copyLatihan
-
Buat class
Employeedengan:- Private attribute untuk salary
- Property untuk membaca salary
- Method untuk menaikkan salary dengan validasi
-
Buat class
ShoppingCartdengan:- Private list untuk items
- Methods untuk add_item, remove_item, get_total
- Property untuk item_count
-
Implementasikan class
Userdengan:- Private password
- Method untuk verify_password
- Property untuk email dengan validasi format
Langkah Selanjutnya
Setelah memahami encapsulation, kita akan mempelajari Polymorphism untuk membuat objek yang berbeda merespons method yang sama dengan cara berbeda.
