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__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 objectsclasses.

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____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____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 ContactContactBook:

  • 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

Bài 15.2: OOP (Phần 2) - Inheritance, polymorphism, encapsulation, property decorators, và magic methods.


Remember:

  • Classes are blueprints, objects are instances
  • Use self to access instance data
  • Instance methods for behavior, class methods for alternatives
  • Static methods for utilities
  • Always use __str__ for readable output!