Bài 15: Object-Oriented Programming - OOP (Phần 1)
Mục Tiêu Bài Học
Sau khi hoàn thành bài này, bạn sẽ:
- ✅ Hiểu OOP là gì và tại sao dùng
- ✅ Tạo classes và objects
- ✅ Làm việc với
__init__vàself - ✅ Định nghĩa instance và class attributes
- ✅ Viết methods (instance, class, static)
OOP Là Gì?
OOP (Object-Oriented Programming) là programming paradigm dựa trên objects và classes.
Procedural vs OOP
# ❌ Procedural - data và functions riêng biệtuser_name = "Alice"user_email = "[email protected]"user_age = 25 def greet_user(name): return f"Hello, {name}!" def get_user_info(name, email, age): return f"{name} ({email}) - {age} years old" # ✅ OOP - data và behavior togetherclass User: def __init__(self, name, email, age): self.name = name self.email = email self.age = age def greet(self): return f"Hello, {self.name}!" def get_info(self): return f"{self.name} ({self.email}) - {self.age} years old" # Create objectuser = User("Alice", "[email protected]", 25)print(user.greet())print(user.get_info())
OOP Concepts
- Class: Blueprint/template cho objects
- Object: Instance của class
- Attributes: Data/properties của object
- Methods: Functions của class
- Encapsulation: Gom data và methods lại
- Inheritance: Class kế thừa từ class khác
- Polymorphism: Cùng interface, khác implementation
Classes và Objects
Defining Classes
# Basic classclass Dog: """A simple dog class.""" pass # Create object (instance)my_dog = Dog()print(my_dog) # <__main__.Dog object at 0x...>print(type(my_dog)) # <class '__main__.Dog'> # Class with attributesclass Dog: """Dog class with attributes.""" def __init__(self, name, age): """Initialize dog.""" self.name = name self.age = age # Create objectsdog1 = Dog("Buddy", 3)dog2 = Dog("Max", 5) print(dog1.name) # Buddyprint(dog2.age) # 5 # Class naming convention: PascalCaseclass MyClass: pass class UserAccount: pass class ShoppingCart: pass
__init__ Method
__init__ là constructor, tự động chạy khi tạo object.
class Person: """Person class.""" def __init__(self, name, age): """ Initialize person. Args: name (str): Person's name age (int): Person's age """ print("Creating person...") self.name = name self.age = age print(f"Person {name} created!") # Create object - __init__ runs automaticallyperson = Person("Alice", 25)# Output:# Creating person...# Person Alice created! # Default parametersclass Person: def __init__(self, name, age=0, city="Unknown"): self.name = name self.age = age self.city = city person1 = Person("Alice", 25, "Hanoi")person2 = Person("Bob", 30)person3 = Person("Charlie") print(person3.name) # Charlieprint(person3.age) # 0print(person3.city) # Unknown
self Parameter
self đại diện cho instance hiện tại.
class Counter: def __init__(self): self.count = 0 # Instance attribute def increment(self): self.count += 1 # Access instance attribute def get_count(self): return self.count # Create multiple instancescounter1 = Counter()counter2 = Counter() # Each instance has its own datacounter1.increment()counter1.increment()counter2.increment() print(counter1.get_count()) # 2print(counter2.get_count()) # 1 # self is automatically passedcounter1.increment()# Equivalent to: Counter.increment(counter1)
Instance Attributes
Instance attributes là data riêng của mỗi object.
class Student: def __init__(self, name, student_id): # Instance attributes self.name = name self.student_id = student_id self.courses = [] def enroll(self, course): """Enroll in course.""" self.courses.append(course) def get_courses(self): """Get enrolled courses.""" return self.courses # Each student has own datastudent1 = Student("Alice", "S001")student2 = Student("Bob", "S002") student1.enroll("Python")student1.enroll("Django")student2.enroll("JavaScript") print(f"{student1.name}: {student1.get_courses()}")# Alice: ['Python', 'Django'] print(f"{student2.name}: {student2.get_courses()}")# Bob: ['JavaScript'] # Modify attributesstudent1.name = "Alice Nguyen"print(student1.name) # Alice Nguyen
Class Attributes
Class attributes shared giữa tất cả instances.
class Dog: # Class attribute (shared) species = "Canis familiaris" def __init__(self, name, age): # Instance attributes (unique per instance) self.name = name self.age = age dog1 = Dog("Buddy", 3)dog2 = Dog("Max", 5) # Access class attributeprint(dog1.species) # Canis familiarisprint(dog2.species) # Canis familiarisprint(Dog.species) # Canis familiaris # Modify class attributeDog.species = "Dog"print(dog1.species) # Dogprint(dog2.species) # Dog # Instance vs Class attributesclass Counter: # Class attribute - shared total_count = 0 def __init__(self, name): # Instance attribute - unique self.name = name self.count = 0 # Modify class attribute Counter.total_count += 1 def increment(self): self.count += 1 counter1 = Counter("Counter1")counter2 = Counter("Counter2") print(Counter.total_count) # 2 (total instances created) counter1.increment()counter1.increment()counter2.increment() print(counter1.count) # 2 (instance count)print(counter2.count) # 1 (instance count)
Methods
Instance Methods
Instance methods nhận self, work với instance data.
class BankAccount: """Bank account class.""" def __init__(self, owner, balance=0): self.owner = owner self.balance = balance def deposit(self, amount): """Deposit money.""" if amount > 0: self.balance += amount return f"Deposited ${amount}. New balance: ${self.balance}" return "Invalid amount" def withdraw(self, amount): """Withdraw money.""" if amount > self.balance: return "Insufficient funds" if amount > 0: self.balance -= amount return f"Withdrew ${amount}. New balance: ${self.balance}" return "Invalid amount" def get_balance(self): """Get current balance.""" return self.balance # Create accountaccount = BankAccount("Alice", 1000) # Call methodsprint(account.deposit(500)) # Deposited $500. New balance: $1500print(account.withdraw(200)) # Withdrew $200. New balance: $1300print(account.get_balance()) # 1300
Class Methods
Class methods nhận cls, work với class data.
class Date: """Date class.""" def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def from_string(cls, date_string): """Create date from string (YYYY-MM-DD).""" year, month, day = map(int, date_string.split('-')) return cls(year, month, day) @classmethod def today(cls): """Create date for today.""" import datetime today = datetime.date.today() return cls(today.year, today.month, today.day) def __str__(self): return f"{self.year}-{self.month:02d}-{self.day:02d}" # Regular constructordate1 = Date(2025, 10, 27) # Class method constructors (alternative constructors)date2 = Date.from_string("2025-10-27")date3 = Date.today() print(date1) # 2025-10-27print(date2) # 2025-10-27print(date3) # Current date # Class method with counterclass User: user_count = 0 def __init__(self, name): self.name = name User.user_count += 1 @classmethod def get_user_count(cls): """Get total users.""" return cls.user_count user1 = User("Alice")user2 = User("Bob") print(User.get_user_count()) # 2
Static Methods
Static methods không nhận self hay cls, utility functions.
class Math: """Math utilities.""" @staticmethod def add(a, b): """Add two numbers.""" return a + b @staticmethod def is_even(number): """Check if number is even.""" return number % 2 == 0 @staticmethod def factorial(n): """Calculate factorial.""" if n <= 1: return 1 return n * Math.factorial(n - 1) # Call without creating instanceprint(Math.add(5, 3)) # 8print(Math.is_even(4)) # Trueprint(Math.factorial(5)) # 120 # Can also call from instancemath = Math()print(math.add(10, 20)) # 30 # Real-world exampleclass Validator: """Input validators.""" @staticmethod def is_valid_email(email): """Validate email.""" return '@' in email and '.' in email.split('@')[1] @staticmethod def is_valid_phone(phone): """Validate phone (10 digits).""" return phone.isdigit() and len(phone) == 10 @staticmethod def is_strong_password(password): """Validate password strength.""" return ( len(password) >= 8 and any(c.isupper() for c in password) and any(c.islower() for c in password) and any(c.isdigit() for c in password) ) # Use validatorsprint(Validator.is_valid_email("[email protected]")) # Trueprint(Validator.is_valid_phone("0123456789")) # Trueprint(Validator.is_strong_password("Pass123")) # True
String Representation
__str__ và __repr__
class Book: """Book class.""" def __init__(self, title, author, year): self.title = title self.author = author self.year = year def __str__(self): """User-friendly string representation.""" return f"{self.title} by {self.author} ({self.year})" def __repr__(self): """Developer-friendly representation.""" return f"Book('{self.title}', '{self.author}', {self.year})" book = Book("Python Basics", "John Doe", 2025) # __str__ - for usersprint(str(book)) # Python Basics by John Doe (2025)print(book) # Python Basics by John Doe (2025) # __repr__ - for developersprint(repr(book)) # Book('Python Basics', 'John Doe', 2025) # In console# >>> book# Book('Python Basics', 'John Doe', 2025) # Without __str__, __repr__ is usedclass Person: def __init__(self, name): self.name = name def __repr__(self): return f"Person('{self.name}')" person = Person("Alice")print(person) # Person('Alice')
Ví Dụ Thực Tế
1. Shopping Cart
class Product: """Product class.""" def __init__(self, name, price, quantity=1): self.name = name self.price = price self.quantity = quantity def get_total(self): """Get total price.""" return self.price * self.quantity def __str__(self): return f"{self.name} x{self.quantity} - ${self.get_total():.2f}" class ShoppingCart: """Shopping cart class.""" def __init__(self): self.items = [] def add_item(self, product): """Add product to cart.""" self.items.append(product) print(f"Added: {product.name}") def remove_item(self, product_name): """Remove product by name.""" for item in self.items: if item.name == product_name: self.items.remove(item) print(f"Removed: {product_name}") return print(f"Product not found: {product_name}") def get_total(self): """Get cart total.""" return sum(item.get_total() for item in self.items) def display(self): """Display cart contents.""" if not self.items: print("Cart is empty") return print("\n=== Shopping Cart ===") for item in self.items: print(f" {item}") print(f" Total: ${self.get_total():.2f}") print("=" * 20) # Usagecart = ShoppingCart() # Add productscart.add_item(Product("Laptop", 999.99))cart.add_item(Product("Mouse", 29.99, 2))cart.add_item(Product("Keyboard", 79.99)) # Display cartcart.display() # Remove productcart.remove_item("Mouse")cart.display()
2. Bank Account System
class BankAccount: """Bank account with transaction history.""" # Class attribute account_count = 0 def __init__(self, owner, initial_balance=0): self.owner = owner self.balance = initial_balance self.transactions = [] # Increment account count BankAccount.account_count += 1 self.account_number = f"ACC{BankAccount.account_count:04d}" # Record initial deposit if initial_balance > 0: self._add_transaction("Initial Deposit", initial_balance) def _add_transaction(self, type, amount): """Private method to record transaction.""" self.transactions.append({ 'type': type, 'amount': amount, 'balance': self.balance }) def deposit(self, amount): """Deposit money.""" if amount <= 0: return "Invalid amount" self.balance += amount self._add_transaction("Deposit", amount) return f"Deposited ${amount:.2f}" def withdraw(self, amount): """Withdraw money.""" if amount <= 0: return "Invalid amount" if amount > self.balance: return "Insufficient funds" self.balance -= amount self._add_transaction("Withdrawal", -amount) return f"Withdrew ${amount:.2f}" def transfer(self, other_account, amount): """Transfer to another account.""" if amount > self.balance: return "Insufficient funds" self.withdraw(amount) other_account.deposit(amount) return f"Transferred ${amount:.2f} to {other_account.owner}" def get_statement(self): """Print account statement.""" print(f"\n=== Account Statement ===") print(f"Account: {self.account_number}") print(f"Owner: {self.owner}") print(f"Current Balance: ${self.balance:.2f}\n") print("Transactions:") for i, trans in enumerate(self.transactions, 1): print(f"{i}. {trans['type']}: ${trans['amount']:.2f} " f"(Balance: ${trans['balance']:.2f})") print("=" * 25) @classmethod def get_total_accounts(cls): """Get total number of accounts.""" return cls.account_count def __str__(self): return f"Account {self.account_number} - {self.owner}: ${self.balance:.2f}" # Usageaccount1 = BankAccount("Alice", 1000)account2 = BankAccount("Bob", 500) print(account1)print(account2) # Transactionsaccount1.deposit(500)account1.withdraw(200)account1.transfer(account2, 300) # Statementaccount1.get_statement()account2.get_statement() # Total accountsprint(f"\nTotal accounts: {BankAccount.get_total_accounts()}")
3. Student Grade Manager
class Student: """Student with grades.""" def __init__(self, name, student_id): self.name = name self.student_id = student_id self.grades = {} def add_grade(self, subject, grade): """Add grade for subject.""" if 0 <= grade <= 100: self.grades[subject] = grade print(f"Added {subject}: {grade}") else: print("Grade must be 0-100") def get_average(self): """Calculate average grade.""" if not self.grades: return 0 return sum(self.grades.values()) / len(self.grades) def get_letter_grade(self): """Get letter grade based on average.""" avg = self.get_average() if avg >= 90: return 'A' elif avg >= 80: return 'B' elif avg >= 70: return 'C' elif avg >= 60: return 'D' else: return 'F' def display_report(self): """Display grade report.""" print(f"\n=== Grade Report ===") print(f"Name: {self.name}") print(f"ID: {self.student_id}") print("\nGrades:") for subject, grade in self.grades.items(): print(f" {subject}: {grade}") print(f"\nAverage: {self.get_average():.2f}") print(f"Letter Grade: {self.get_letter_grade()}") print("=" * 20) def __str__(self): return f"{self.name} ({self.student_id}) - Avg: {self.get_average():.1f}" # Usagestudent = Student("Alice Nguyen", "S001") # Add gradesstudent.add_grade("Python", 95)student.add_grade("Django", 88)student.add_grade("JavaScript", 92)student.add_grade("Database", 85) # Display reportstudent.display_report()
4. Library System
class Book: """Book in library.""" def __init__(self, title, author, isbn): self.title = title self.author = author self.isbn = isbn self.is_borrowed = False self.borrower = None def __str__(self): status = "Borrowed" if self.is_borrowed else "Available" return f"{self.title} by {self.author} [{status}]" class Library: """Library management system.""" def __init__(self, name): self.name = name self.books = [] def add_book(self, book): """Add book to library.""" self.books.append(book) print(f"Added: {book.title}") def find_book(self, title): """Find book by title.""" for book in self.books: if book.title.lower() == title.lower(): return book return None def borrow_book(self, title, borrower): """Borrow a book.""" book = self.find_book(title) if not book: return "Book not found" if book.is_borrowed: return f"Book is already borrowed by {book.borrower}" book.is_borrowed = True book.borrower = borrower return f"{borrower} borrowed '{book.title}'" def return_book(self, title): """Return a book.""" book = self.find_book(title) if not book: return "Book not found" if not book.is_borrowed: return "Book was not borrowed" borrower = book.borrower book.is_borrowed = False book.borrower = None return f"'{book.title}' returned by {borrower}" def list_books(self): """List all books.""" print(f"\n=== {self.name} Books ===") if not self.books: print("No books in library") return for i, book in enumerate(self.books, 1): print(f"{i}. {book}") print("=" * 30) def available_books(self): """Get available books.""" return [book for book in self.books if not book.is_borrowed] # Usagelibrary = Library("City Library") # Add bookslibrary.add_book(Book("Python Basics", "John Doe", "ISBN001"))library.add_book(Book("Django Guide", "Jane Smith", "ISBN002"))library.add_book(Book("Web Development", "Bob Johnson", "ISBN003")) # List bookslibrary.list_books() # Borrow booksprint("\n" + library.borrow_book("Python Basics", "Alice"))print(library.borrow_book("Django Guide", "Bob")) # Try to borrow same bookprint(library.borrow_book("Python Basics", "Charlie")) # List bookslibrary.list_books() # Return bookprint("\n" + library.return_book("Python Basics")) # List availableprint(f"\nAvailable books: {len(library.available_books())}")
Best Practices
# 1. Class names: PascalCaseclass UserAccount: pass # 2. Method names: snake_caseclass User: def get_full_name(self): pass # 3. Use docstringsclass Person: """Represents a person.""" def __init__(self, name): """Initialize person with name.""" self.name = name # 4. One class per file (for larger projects)# user.pyclass User: pass # 5. Don't access attributes directly (use methods)# ❌ Not recommendeduser.balance += 100 # ✅ Betteruser.deposit(100) # 6. Use @property for computed attributesclass Circle: def __init__(self, radius): self.radius = radius @property def area(self): return 3.14159 * self.radius ** 2 circle = Circle(5)print(circle.area) # Computed, not stored
Bài Tập Thực Hành
Bài 1: Rectangle Class
Tạo class Rectangle:
- Attributes: width, height
- Methods: area(), perimeter(), is_square()
__str__method
Bài 2: Todo List
Tạo class TodoList:
- Add/remove/complete tasks
- List all tasks
- Count completed/pending
- Class method to create from list
Bài 3: User Management
Tạo class User:
- Attributes: username, email, created_at
- Class attribute: total_users
- Methods: update_email, is_active
__repr__và__str__
Bài 4: Calculator
Tạo class Calculator:
- Instance attribute: history
- Methods: add, subtract, multiply, divide
- get_history() method
- clear_history() method
Bài 5: Contact Book
Tạo classes Contact và ContactBook:
- Contact: name, phone, email
- ContactBook: add, remove, search contacts
- Export to JSON
- Class methods for import/export
Tóm Tắt
✅ Class: Template cho objects (PascalCase)
✅ Object: Instance của class
✅ __init__: Constructor method
✅ self: Reference đến instance hiện tại
✅ Instance attributes: Unique per object
✅ Class attributes: Shared giữa instances
✅ Instance methods: Nhận self, work với instance
✅ Class methods: @classmethod, nhận cls
✅ Static methods: @staticmethod, utility functions
Bài Tiếp Theo
Remember:
- Classes are blueprints, objects are instances
- Use
selfto access instance data - Instance methods for behavior, class methods for alternatives
- Static methods for utilities
- Always use
__str__for readable output!