0

Đa hình (Polymorphism) trong Python là gì? Giải thích với một ví dụ

Đa hình hay Polymorphism) là một nguyên tắc trong lập trình hướng đối tượng (OOP) giúp bạn viết phần mềm chất lượng cao, linh hoạt, dễ bảo trì, tái sử dụng, kiểm thử và đọc hiểu. Nếu bạn dự định làm việc với phần mềm hướng đối tượng, việc hiểu rõ về đa hình là rất quan trọng.

Vậy Đa hình là gì?

Từ "polymorphism" bắt nguồn từ tiếng Hy Lạp và có nghĩa là "có nhiều hình thức":

Trong đó:

  • Poly = nhiều
  • Morph = hình thức

Trong lập trình, đa hình là khả năng của một đối tượng có thể có nhiều hình thức khác nhau.

Lợi ích lớn nhất của đa hình là cho phép chúng ta viết mã tổng quát và có thể tái sử dụng. Thay vì viết logic riêng cho từng lớp khác nhau, chúng ta có thể định nghĩa các hành vi chung trong một lớp cha và để các lớp con ghi đè (override) chúng khi cần. Điều này giúp loại bỏ việc sử dụng quá nhiều câu lệnh if-else, làm cho mã dễ bảo trì và mở rộng hơn.

Các framework MVC như Django sử dụng đa hình để làm cho mã linh hoạt hơn. Ví dụ, Django hỗ trợ nhiều loại cơ sở dữ liệu như SQLite, MySQL và PostgreSQL. Thông thường, mỗi cơ sở dữ liệu sẽ yêu cầu các đoạn mã khác nhau để tương tác với nó. Tuy nhiên, Django cung cấp một API cơ sở dữ liệu chung có thể hoạt động với tất cả các loại cơ sở dữ liệu này.

Điều này có nghĩa là bạn có thể viết cùng một đoạn mã cho các thao tác với cơ sở dữ liệu, bất kể bạn sử dụng loại nào. Nếu bạn bắt đầu một dự án với SQLite và sau đó chuyển sang PostgreSQL, bạn sẽ không cần phải viết lại quá nhiều mã, nhờ vào tính đa hình.

Trong bài viết này, để giúp bạn dễ hiểu hơn, tôi sẽ đưa ra một ví dụ về sử dụng bad code mà không sử dụng đa hình. Chúng ta sẽ thảo luận về các vấn đề mà đoạn mã này gây ra, sau đó cải thiện nó bằng cách viết lại mã (refactor) để sử dụng đa hình.

Đầu tiên, đây là một ví dụ không sử dụng đa hình

class Car:
    def __init__(self, brand, model, year, number_of_doors):
        self.brand = brand
        self.model = model
        self.year = year
        self.number_of_doors = number_of_doors

    def start(self):
        print("Car is starting.")

    def stop(self):
        print("Car is stopping.")
class Motorcycle:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year

    def start_bike(self):
        print("Motorcycle is starting.")

    def stop_bike(self):
        print("Motorcycle is stopping.")

Giả sử chúng ta muốn tạo một danh sách các phương tiện, sau đó lặp qua danh sách này và thực hiện kiểm tra từng phương tiện:

# Create list of vehicles to inspect
vehicles = [
    Car("Ford", "Focus", 2008, 5),
    Motorcycle("Honda", "Scoopy", 2018),
]

# Loop through list of vehicles and inspect them
for vehicle in vehicles:
    if isinstance(vehicle, Car):
        print(f"Inspecting {vehicle.brand} {vehicle.model} ({type(vehicle).__name__})")
        vehicle.start()
        vehicle.stop()
    elif isinstance(vehicle, Motorcycle):
        print(f"Inspecting {vehicle.brand} {vehicle.model} ({type(vehicle).__name__})")
        vehicle.start_bike()
        vehicle.stop_bike()
    else:
        raise Exception("Object is not a valid vehicle")

Hãy chú ý đến đoạn mã cồng kềnh bên trong vòng lặp for! Vì danh sách vehicles chứa nhiều loại đối tượng khác nhau, chúng ta phải kiểm tra kiểu của từng đối tượng trước khi có thể truy cập thông tin của nó.

Đoạn mã này sẽ ngày càng trở nên rối rắm nếu chúng ta thêm nhiều loại phương tiện hơn. Ví dụ, nếu mở rộng mã để bao gồm một lớp Plane, chúng ta sẽ phải chỉnh sửa (và có thể vô tình làm hỏng) mã hiện có – cụ thể là thêm một điều kiện kiểm tra mới trong vòng lặp for để xử lý máy bay.

Giờ là lúc áp dụng đa hình

Ô tô và xe máy đều là phương tiện giao thông. Chúng chia sẻ một số thuộc tính và phương thức chung. Vì vậy, hãy tạo một lớp cha chứa các thuộc tính và phương thức này:

Lớp parent (hoặc "lớp siêu cấp"):

class Vehicle:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year

    def start(self):
        print("Vehicle is starting.")

    def stop(self):
        print("Vehicle is stopping.")

Bây giờ, Car và Motorcycle có thể kế thừa từ Vehicle. Hãy tạo các lớp con (hoặc "lớp con") từ lớp parent Vehicle:

class Car(Vehicle):
    def __init__(self, brand, model, year, number_of_doors):
        super().__init__(brand, model, year)
        self.number_of_doors = number_of_doors

    # Below, we "override" the start and stop methods, inherited from Vehicle, to provide car-specific behaviour

    def start(self):
        print("Car is starting.")

    def stop(self):
        print("Car is stopping.")
class Motorcycle(Vehicle):
    def __init__(self, brand, model, year):
        super().__init__(brand, model, year)

    # Below, we "override" the start and stop methods, inherited from Vehicle, to provide bike-specific behaviour

    def start(self):
        print("Motorcycle is starting.")

    def stop(self):
        print("Motorcycle is stopping.")

Cả Car và Motorcycle đều mở rộng từ Vehicle, vì chúng đều là phương tiện giao thông. Nhưng mục đích của việc cả hai đều kế thừa từ Vehicle là gì, nếu chúng sẽ triển khai phiên bản riêng của các phương thức start()stop()? Hãy xem đoạn mã dưới đây:

# Create list of vehicles to inspect
vehicles = [Car("Ford", "Focus", 2008, 5), Motorcycle("Honda", "Scoopy", 2018)]

# Loop through list of vehicles and inspect them
for vehicle in vehicles:
    if isinstance(vehicle, Vehicle):
        print(f"Inspecting {vehicle.brand} {vehicle.model} ({type(vehicle).__name__})")
        vehicle.start()
        vehicle.stop()
    else:
        raise Exception("Object is not a valid vehicle")

Trong ví dụ này:

  • Chúng ta có một danh sách vehicles, chứa các đối tượng của cả Car và Motorcycle.
  • Chúng ta lặp qua từng phương tiện trong danh sách và thực hiện kiểm tra tổng quát cho mỗi phương tiện.
  • Quá trình kiểm tra bao gồm việc khởi động phương tiện, kiểm tra thương hiệu và mẫu xe, và sau đó dừng phương tiện lại.
  • Mặc dù các phương tiện là những loại khác nhau, đa hình cho phép chúng ta xử lý tất cả chúng như là các đối tượng của lớp cơ sở Vehicle. Các phương thức start() và stop() cụ thể cho từng loại phương tiện sẽ được gọi động tại thời gian chạy, dựa trên kiểu thực tế của từng phương tiện.

Vì danh sách chỉ có thể chứa các đối tượng mở rộng từ lớp Vehicle, chúng ta biết rằng mọi đối tượng trong danh sách sẽ có các trường và phương thức chung. Điều này có nghĩa là chúng ta có thể gọi chúng một cách an toàn mà không cần phải lo lắng về việc mỗi phương tiện cụ thể có các trường hoặc phương thức này hay không.

Điều này minh họa cách mà đa hình cho phép viết mã theo cách tổng quát và linh hoạt hơn, giúp dễ dàng mở rộng và bảo trì khi thêm các loại phương tiện mới vào hệ thống.

Ví dụ, nếu chúng ta muốn thêm một phương tiện khác vào danh sách, chúng ta không cần phải thay đổi mã sử dụng để kiểm tra phương tiện (mã của khách hàng). Thay vào đó, chúng ta chỉ cần mở rộng mã nguồn (tạo một lớp mới), mà không cần chỉnh sửa mã hiện tại:

class Plane(Vehicle):
    def __init__(self, brand, model, year, number_of_doors):
        super().__init__(brand, model, year)
        self.number_of_doors = number_of_doors

    def start(self):
        print("Plane is starting.")

    def stop(self):
        print("Plane is stopping.")
# Create list of vehicles to inspect
vehicles = [
    Car("Ford", "Focus", 2008, 5),
    Motorcycle("Honda", "Scoopy", 2018),

    ########## ADD A PLANE TO THE LIST: #########

    Plane("Boeing", "747", 2015, 16),

    ############################################
]

Mã để thực hiện kiểm tra phương tiện không cần thay đổi để xử lý một chiếc máy bay. Mọi thứ vẫn hoạt động như cũ, mà không cần phải sửa đổi logic kiểm tra của chúng ta.

Kết luận

Đa hình cho phép các khách hàng xử lý các loại đối tượng khác nhau theo cùng một cách. Điều này cải thiện đáng kể tính linh hoạt và khả năng bảo trì phần mềm, vì các lớp mới có thể được tạo ra mà không cần phải sửa đổi (thường là bằng cách thêm các khối if/else if dư thừa) mã hiện tại đã được kiểm tra và hoạt động tốt.

Hy vọng bài viết giúp ích cho các bạn!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí