Bài 9.3. Python - Kế thừa

Bài 9.3. Python - Kế thừa
Bạn không phải lúc nào cũng phải bắt đầu từ đầu khi viết một lớp. Nếu lớp bạn đang viết là một phiên bản chuyên biệt của một lớp khác mà bạn đã viết, bạn có thể sử dụng kế thừa. Khi một lớp kế thừa từ một lớp khác, nó sẽ nhận các thuộc tính và phương thức của lớp đầu tiên. Lớp gốc được gọi là lớp cha, và lớp mới là lớp con. Lớp con có thể kế thừa bất kỳ hoặc tất cả các thuộc tính và phương thức của lớp cha, nhưng nó cũng có thể định nghĩa các thuộc tính và phương thức mới của riêng mình.
Phương thức __init__()
cho lớp con
Khi bạn viết một lớp mới dựa trên một lớp hiện có, bạn sẽ thường muốn gọi phương thức __init__()
từ lớp cha. Điều này sẽ khởi tạo bất kỳ thuộc tính nào đã được định nghĩa trong phương thức __init__()
của lớp cha và làm cho chúng có sẵn trong lớp con.
Làm ví dụ, hãy mô hình hóa một chiếc xe điện. Một chiếc xe điện chỉ là một loại xe cụ thể, vì vậy chúng ta có thể dựa trên lớp ElectricCar
mới của mình trên lớp Car
mà chúng ta đã viết trước đó. Sau đó, chúng ta chỉ cần viết mã cho các thuộc tính và hành vi cụ thể cho xe điện.
Hãy bắt đầu bằng cách tạo một phiên bản đơn giản của lớp ElectricCar
, làm mọi thứ mà lớp Car
làm:
class Car:
"""Một nỗ lực đơn giản để đại diện cho một chiếc xe hơi."""
def __init__(self, make, model, year):
"""Khởi tạo các thuộc tính để mô tả một chiếc xe hơi."""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""Trả về một tên mô tả được định dạng gọn gàng."""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
"""In ra một câu cho biết số dặm của xe."""
print(f"This car has {self.odometer_reading} miles on it.")
def update_odometer(self, mileage):
"""Đặt giá trị của odometer_reading thành giá trị được cung cấp."""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
"""Thêm số lượng được cung cấp vào giá trị của odometer_reading."""
self.odometer_reading += miles
class ElectricCar(Car):
"""Đại diện cho các khía cạnh của một chiếc xe hơi, cụ thể là xe điện."""
def __init__(self, make, model, year):
"""Khởi tạo các thuộc tính của lớp cha."""
super().__init__(make, model, year)
my_leaf = ElectricCar('nissan', 'leaf', 2024)
print(my_leaf.get_descriptive_name())
Chúng ta bắt đầu với lớp Car
. Khi bạn tạo một lớp con, lớp cha phải là một phần của tệp hiện tại và phải xuất hiện trước lớp con trong tệp. Sau đó, chúng ta định nghĩa lớp con, ElectricCar
. Tên của lớp cha phải được bao gồm trong dấu ngoặc đơn trong định nghĩa của lớp con. Phương thức __init__()
nhận thông tin cần thiết để tạo một thể hiện của Car
.
Hàm super()
là một hàm đặc biệt cho phép bạn gọi một phương thức từ lớp cha. Dòng này yêu cầu Python gọi phương thức __init__()
từ Car
, điều này cung cấp cho một thể hiện của ElectricCar
tất cả các thuộc tính được định nghĩa trong phương thức đó. Tên super
xuất phát từ quy ước gọi lớp cha là superclass và lớp con là subclass.
Chúng ta kiểm tra xem kế thừa có hoạt động đúng không bằng cách cố gắng tạo một chiếc xe điện với cùng loại thông tin mà chúng ta sẽ cung cấp khi tạo một chiếc xe thông thường. Chúng ta tạo một thể hiện của lớp ElectricCar
và gán nó cho my_leaf
. Dòng này gọi phương thức __init__()
được định nghĩa trong ElectricCar
, sau đó yêu cầu Python gọi phương thức __init__()
được định nghĩa trong lớp cha Car
. Chúng ta cung cấp các đối số 'nissan', 'leaf', và 2024.
Ngoài phương thức __init__()
, hiện tại không có thuộc tính hoặc phương thức nào cụ thể cho xe điện. Tại thời điểm này, chúng ta chỉ đảm bảo rằng xe điện có các hành vi phù hợp của xe hơi:
2024 Nissan Leaf
Thể hiện của ElectricCar
hoạt động giống như một thể hiện của Car
, vì vậy bây giờ chúng ta có thể bắt đầu định nghĩa các thuộc tính và phương thức cụ thể cho xe điện.
Định nghĩa các thuộc tính và phương thức cho lớp con
Khi bạn có một lớp con kế thừa từ một lớp cha, bạn có thể thêm bất kỳ thuộc tính và phương thức mới nào cần thiết để phân biệt lớp con với lớp cha.
Hãy thêm một thuộc tính cụ thể cho xe điện (ví dụ, một pin) và một phương thức để báo cáo về thuộc tính này. Chúng ta sẽ lưu trữ kích thước pin và viết một phương thức in ra mô tả về pin:
class Car:
"""Một nỗ lực đơn giản để đại diện cho một chiếc xe hơi."""
def __init__(self, make, model, year):
"""Khởi tạo các thuộc tính để mô tả một chiếc xe hơi."""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""Trả về một tên mô tả được định dạng gọn gàng."""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
"""In ra một câu cho biết số dặm của xe."""
print(f"This car has {self.odometer_reading} miles on it.")
def update_odometer(self, mileage):
"""Đặt giá trị của odometer_reading thành giá trị được cung cấp."""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
"""Thêm số lượng được cung cấp vào giá trị của odometer_reading."""
self.odometer_reading += miles
class ElectricCar(Car):
"""Đại diện cho các khía cạnh của một chiếc xe hơi, cụ thể là xe điện."""
def __init__(self, make, model, year):
"""
Khởi tạo các thuộc tính của lớp cha.
Sau đó khởi tạo các thuộc tính cụ thể cho xe điện.
"""
super().__init__(make, model, year)
self.battery_size = 40
def describe_battery(self):
"""In ra một câu mô tả kích thước pin."""
print(f"This car has a {self.battery_size}-kWh battery.")
my_leaf = ElectricCar('nissan', 'leaf', 2024)
print(my_leaf.get_descriptive_name())
my_leaf.describe_battery()
Chúng ta thêm một thuộc tính mới self.battery_size
và đặt giá trị ban đầu của nó là 40. Thuộc tính này sẽ được liên kết với tất cả các thể hiện được tạo từ lớp ElectricCar
nhưng sẽ không được liên kết với bất kỳ thể hiện nào của Car
. Chúng ta cũng thêm một phương thức gọi là describe_battery()
in ra thông tin về pin. Khi chúng ta gọi phương thức này, chúng ta nhận được một mô tả rõ ràng là cụ thể cho xe điện:
2024 Nissan Leaf
This car has a 40-kWh battery.
Không có giới hạn về mức độ bạn có thể chuyên biệt hóa lớp ElectricCar
. Bạn có thể thêm bao nhiêu thuộc tính và phương thức tùy thích để mô hình hóa một chiếc xe điện đến mức độ chính xác mà bạn cần. Một thuộc tính hoặc phương thức có thể thuộc về bất kỳ chiếc xe nào, thay vì một chiếc xe điện cụ thể, nên được thêm vào lớp Car
thay vì lớp ElectricCar
. Sau đó, bất kỳ ai sử dụng lớp Car
sẽ có chức năng đó có sẵn, và lớp ElectricCar
sẽ chỉ chứa mã cho thông tin và hành vi cụ thể cho xe điện.
Ghi đè phương thức từ lớp cha
Bạn có thể ghi đè bất kỳ phương thức nào từ lớp cha không phù hợp với những gì bạn đang cố gắng mô hình hóa với lớp con. Để làm điều này, bạn định nghĩa một phương thức trong lớp con với cùng tên như phương thức bạn muốn ghi đè trong lớp cha. Python sẽ bỏ qua phương thức của lớp cha và chỉ chú ý đến phương thức bạn định nghĩa trong lớp con.
Giả sử lớp Car
có một phương thức gọi là fill_gas_tank()
. Phương thức này không có ý nghĩa đối với một chiếc xe điện, vì vậy bạn có thể muốn ghi đè phương thức này. Dưới đây là một cách để làm điều đó:
class ElectricCar(Car):
"""Đại diện cho các khía cạnh của một chiếc xe hơi, cụ thể là xe điện."""
def __init__(self, make, model, year):
"""
Khởi tạo các thuộc tính của lớp cha.
Sau đó khởi tạo các thuộc tính cụ thể cho xe điện.
"""
super().__init__(make, model, year)
self.battery_size = 40
def describe_battery(self):
"""In ra một câu mô tả kích thước pin."""
print(f"This car has a {self.battery_size}-kWh battery.")
def fill_gas_tank(self):
"""Xe điện không có bình xăng."""
print("This car doesn't have a gas tank!")
Bây giờ nếu ai đó cố gắng gọi fill_gas_tank()
với một chiếc xe điện, Python sẽ bỏ qua phương thức fill_gas_tank()
trong Car
và chạy mã này thay thế. Khi bạn sử dụng kế thừa, bạn có thể làm cho các lớp con của mình giữ lại những gì bạn cần và ghi đè bất kỳ điều gì bạn không cần từ lớp cha.
Thể hiện như thuộc tính
Khi mô hình hóa một cái gì đó từ thế giới thực trong mã, bạn có thể thấy rằng bạn đang thêm ngày càng nhiều chi tiết vào một lớp. Bạn sẽ thấy rằng bạn có một danh sách các thuộc tính và phương thức ngày càng tăng và rằng các tệp của bạn đang trở nên dài. Trong những tình huống này, bạn có thể nhận ra rằng một phần của một lớp có thể được viết dưới dạng một lớp riêng biệt. Bạn có thể chia lớp lớn của mình thành các lớp nhỏ hơn làm việc cùng nhau; cách tiếp cận này được gọi là composition.
Ví dụ, nếu chúng ta tiếp tục thêm chi tiết vào lớp ElectricCar
, chúng ta có thể nhận thấy rằng chúng ta đang thêm nhiều thuộc tính và phương thức cụ thể cho pin của xe. Khi chúng ta thấy điều này xảy ra, chúng ta có thể dừng lại và chuyển các thuộc tính và phương thức đó sang một lớp riêng gọi là Battery
. Sau đó, chúng ta có thể sử dụng một thể hiện của Battery
như một thuộc tính trong lớp ElectricCar
:
class Car:
"""Một nỗ lực đơn giản để đại diện cho một chiếc xe hơi."""
def __init__(self, make, model, year):
"""Khởi tạo các thuộc tính để mô tả một chiếc xe hơi."""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""Trả về một tên mô tả được định dạng gọn gàng."""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
"""In ra một câu cho biết số dặm của xe."""
print(f"This car has {self.odometer_reading} miles on it.")
def update_odometer(self, mileage):
"""Đặt giá trị của odometer_reading thành giá trị được cung cấp."""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
"""Thêm số lượng được cung cấp vào giá trị của odometer_reading."""
self.odometer_reading += miles
class Battery:
"""Một nỗ lực đơn giản để mô hình hóa một pin cho xe điện."""
def __init__(self, battery_size=40):
"""Khởi tạo các thuộc tính của pin."""
self.battery_size = battery_size
def describe_battery(self):
"""In ra một câu mô tả kích thước pin."""
print(f"This car has a {self.battery_size}-kWh battery.")
class ElectricCar(Car):
"""Đại diện cho các khía cạnh của một chiếc xe hơi, cụ thể là xe điện."""
def __init__(self, make, model, year):
"""
Khởi tạo các thuộc tính của lớp cha.
Sau đó khởi tạo các thuộc tính cụ thể cho xe điện.
"""
super().__init__(make, model, year)
self.battery = Battery()
my_leaf = ElectricCar('nissan', 'leaf', 2024)
print(my_leaf.get_descriptive_name())
my_leaf.battery.describe_battery()
Chúng ta định nghĩa một lớp mới gọi là Battery
không kế thừa từ bất kỳ lớp nào khác. Phương thức __init__()
có một tham số, battery_size
, ngoài self
. Đây là một tham số tùy chọn đặt kích thước pin là 40 nếu không có giá trị nào được cung cấp. Phương thức describe_battery()
đã được chuyển sang lớp này.
Trong lớp ElectricCar
, chúng ta bây giờ thêm một thuộc tính gọi là self.battery
. Dòng này yêu cầu Python tạo một thể hiện mới của Battery
(với kích thước mặc định là 40, vì chúng ta không chỉ định giá trị) và gán thể hiện đó cho thuộc tính self.battery
. Điều này sẽ xảy ra mỗi khi phương thức __init__()
được gọi; bất kỳ thể hiện nào của ElectricCar
bây giờ sẽ có một thể hiện của Battery
được tạo tự động.
Chúng ta tạo một chiếc xe điện và gán nó cho biến my_leaf
. Khi chúng ta muốn mô tả pin, chúng ta cần làm việc thông qua thuộc tính pin của xe:
my_leaf.battery.describe_battery()
Dòng này yêu cầu Python nhìn vào thể hiện my_leaf
, tìm thuộc tính pin của nó và gọi phương thức describe_battery()
liên kết với thể hiện Battery
được gán cho thuộc tính.
Đầu ra giống hệt như những gì chúng ta đã thấy trước đó:
2024 Nissan Leaf
This car has a 40-kWh battery.
Điều này trông có vẻ như là nhiều công việc bổ sung, nhưng bây giờ chúng ta có thể mô tả pin chi tiết như chúng ta muốn mà không làm lộn xộn lớp ElectricCar
. Hãy thêm một phương thức khác vào Battery
báo cáo phạm vi của xe dựa trên kích thước pin:
class Car:
"""Một nỗ lực đơn giản để đại diện cho một chiếc xe hơi."""
def __init__(self, make, model, year):
"""Khởi tạo các thuộc tính để mô tả một chiếc xe hơi."""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""Trả về một tên mô tả được định dạng gọn gàng."""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
"""In ra một câu cho biết số dặm của xe."""
print(f"This car has {self.odometer_reading} miles on it.")
def update_odometer(self, mileage):
"""Đặt giá trị của odometer_reading thành giá trị được cung cấp."""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
"""Thêm số lượng được cung cấp vào giá trị của odometer_reading."""
self.odometer_reading += miles
class Battery:
"""Một nỗ lực đơn giản để mô hình hóa một pin cho xe điện."""
def __init__(self, battery_size=40):
"""Khởi tạo các thuộc tính của pin."""
self.battery_size = battery_size
def describe_battery(self):
"""In ra một câu mô tả kích thước pin."""
print(f"This car has a {self.battery_size}-kWh battery.")
def get_range(self):
"""In ra một câu về phạm vi mà pin này cung cấp."""
if self.battery_size == 40:
range = 150
elif self.battery_size == 65:
range = 225
print(f"This car can go about {range} miles on a full charge.")
class ElectricCar(Car):
"""Đại diện cho các khía cạnh của một chiếc xe hơi, cụ thể là xe điện."""
def __init__(self, make, model, year):
"""
Khởi tạo các thuộc tính của lớp cha.
Sau đó khởi tạo các thuộc tính cụ thể cho xe điện.
"""
super().__init__(make, model, year)
self.battery = Battery()
my_leaf = ElectricCar('nissan', 'leaf', 2024)
print(my_leaf.get_descriptive_name())
my_leaf.battery.describe_battery()
my_leaf.battery.get_range()
Phương thức mới get_range()
thực hiện một số phân tích đơn giản. Nếu dung lượng pin là 40 kWh, get_range()
đặt phạm vi là 150 dặm, và nếu dung lượng là 65 kWh, nó đặt phạm vi là 225 dặm. Sau đó, nó báo cáo giá trị này. Khi chúng ta muốn sử dụng phương thức này, chúng ta lại phải gọi nó thông qua thuộc tính pin của xe.
Đầu ra cho chúng ta biết phạm vi của xe dựa trên kích thước pin:
2024 Nissan Leaf
This car has a 40-kWh battery.
This car can go about 150 miles on a full charge.
Mô hình hóa các đối tượng trong thế giới thực
Khi bạn bắt đầu mô hình hóa những thứ phức tạp hơn như xe điện, bạn sẽ đối mặt với những câu hỏi thú vị. Phạm vi của một chiếc xe điện là một thuộc tính của pin hay của xe? Nếu chúng ta chỉ mô tả một chiếc xe, có lẽ việc duy trì sự liên kết của phương thức get_range()
với lớp Battery
là ổn. Nhưng nếu chúng ta đang mô tả toàn bộ dòng xe của một nhà sản xuất, có lẽ chúng ta muốn chuyển get_range()
sang lớp ElectricCar
. Phương thức get_range()
vẫn sẽ kiểm tra kích thước pin trước khi xác định phạm vi, nhưng nó sẽ báo cáo một phạm vi cụ thể cho loại xe mà nó liên kết với. Ngoài ra, chúng ta có thể duy trì sự liên kết của phương thức get_range()
với pin nhưng truyền cho nó một tham số như car_model
. Phương thức get_range()
sau đó sẽ báo cáo một phạm vi dựa trên kích thước pin và mô hình xe.
Điều này đưa bạn đến một điểm thú vị trong sự phát triển của bạn như một lập trình viên. Khi bạn đối mặt với những câu hỏi như thế này, bạn đang suy nghĩ ở một mức độ logic cao hơn thay vì một mức độ tập trung vào cú pháp. Bạn không nghĩ về Python, mà về cách đại diện cho thế giới thực trong mã. Khi bạn đạt đến điểm này, bạn sẽ nhận ra rằng thường không có cách tiếp cận đúng hay sai để mô hình hóa các tình huống trong thế giới thực. Một số cách tiếp cận hiệu quả hơn những cách khác, nhưng cần thực hành để tìm ra các đại diện hiệu quả nhất. Nếu mã của bạn hoạt động như bạn muốn, bạn đang làm tốt! Đừng nản lòng nếu bạn thấy mình phải xé toạc các lớp của mình và viết lại chúng nhiều lần bằng các cách tiếp cận khác nhau. Trong quá trình tìm kiếm để viết mã chính xác và hiệu quả, mọi người đều trải qua quá trình này.
Comments