Bên cạnh việc phục vụ như các nhà máy để tạo ra nhiều đối tượng thể hiện, các lớp cũng cho phép chúng tôi thực hiện các thay đổi bằng cách giới thiệu các thành phần mới (được gọi là các lớp con), thay vì thay đổi các thành phần hiện có tại chỗ. Các đối tượng thể hiện được tạo từ một lớp kế thừa các thuộc tính của lớp. Python cũng cho phép các lớp kế thừa từ các lớp khác, mở cửa để mã hóa các phân cấp của các lớp chuyên về hành vi, bằng cách xác định lại các thuộc tính trong các lớp con xuất hiện thấp hơn trong hệ thống phân cấp, chúng tôi ghi đè lên các định nghĩa chung hơn về các thuộc tính đó cao hơn trong cây. Trên thực tế, chúng ta càng đi xuống, phần mềm càng trở nên cụ thể. Ở đây cũng vậy, không có song song với các mô -đun: các thuộc tính của chúng sống trong một không gian tên phẳng, duy nhất không thể tùy chỉnh.
Trong Python, các trường hợp kế thừa từ các lớp và các lớp kế thừa từ các lớp học. Dưới đây là những ý tưởng chính đằng sau máy móc thừa kế thuộc tính:
• Các lớp học được liệt kê trong ngoặc đơn trong tiêu đề lớp. Để kế thừa các thuộc tính từ một lớp khác, chỉ cần liệt kê lớp trong ngoặc đơn trong tiêu đề của một câu lệnh của lớp. Lớp kế thừa thường được gọi là một lớp con và lớp được kế thừa là siêu lớp của nó.
• Các lớp kế thừa các thuộc tính từ các lớp học của chúng. Giống như các trường hợp kế thừa các tên thuộc tính được xác định trong các lớp của họ, các lớp kế thừa tất cả các tên thuộc tính được xác định trong các siêu lớp của chúng; Python tìm thấy chúng tự động khi chúng được truy cập, nếu chúng không tồn tại trong các lớp con.
• Các thể hiện kế thừa các thuộc tính từ tất cả các lớp có thể truy cập. Mỗi trường hợp được đặt tên từ lớp mà nó được tạo từ, cũng như tất cả các lớp học của lớp đó. Khi tìm kiếm một cái tên, Python kiểm tra phiên bản, sau đó lớp của nó, sau đó tất cả các siêu lớp.
• Mỗi đối tượng. Tài liệu tham khảo về một tìm kiếm mới, độc lập. Python thực hiện một tìm kiếm độc lập của cây lớp cho mỗi biểu thức tìm nạp thuộc tính. Điều này bao gồm các tham chiếu đến các trường hợp và các lớp được thực hiện bên ngoài các câu lệnh (ví dụ: X.ATTR), cũng như các tham chiếu đến các thuộc tính của đối số bản thân trong các hàm phương thức lớp. Mỗi biểu thức self.attr trong một phương thức gọi một tìm kiếm mới cho attr trong bản thân và trên.
• Thay đổi logic được thực hiện bằng cách phân lớp, không phải bằng cách thay đổi các lớp học. Bằng cách xác định lại các tên siêu lớp trong các lớp con thấp hơn trong hệ thống phân cấp (cây lớp), các lớp con thay thế và do đó tùy chỉnh hành vi di truyền.
Hiệu ứng ròng và mục đích chính của tất cả các tìm kiếm này, là các lớp hỗ trợ bao thanh toán và tùy chỉnh mã tốt hơn bất kỳ công cụ ngôn ngữ nào khác mà chúng ta đã thấy cho đến nay. Một mặt, họ cho phép chúng tôi giảm thiểu dự phòng mã (và do đó giảm chi phí bảo trì) bằng cách bao thanh toán các hoạt động thành một triển khai được chia sẻ duy nhất; Mặt khác, họ cho phép chúng tôi lập trình bằng cách tùy chỉnh những gì đã tồn tại, thay vì thay đổi nó tại chỗ hoặc bắt đầu từ đầu.
Tiếp tục đọc ở đây: Các lớp có thể chặn các toán tử Python
Bài viết này hữu ích không?
Xem bây giờ hướng dẫn này có một khóa học video liên quan được tạo bởi nhóm Python thực sự. Xem nó cùng với hướng dẫn bằng văn bản để làm sâu sắc thêm sự hiểu biết của bạn: Kế thừa và sáng tác: Hướng dẫn Python OOP This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Inheritance and Composition: A Python OOP Guide
Trong bài viết này, bạn sẽ khám phá sự kế thừa và sáng tác trong Python. Kế thừa và thành phần là hai khái niệm quan trọng trong lập trình hướng đối tượng mô hình hóa mối quan hệ giữa hai lớp. Chúng là các khối xây dựng của thiết kế hướng đối tượng và chúng giúp các lập trình viên viết mã có thể sử dụng lại.inheritance and composition in Python.
Inheritance and composition are two important concepts in object oriented programming that model the relationship between two classes. They are the building blocks of object oriented design, and they help programmers to write
reusable code.
Đến cuối bài viết này, bạn sẽ biết cách::
Sử dụng thừa kế trong Python
Phân cấp lớp mô hình sử dụng kế thừa
Sử dụng nhiều kế thừa trong Python và hiểu được nhược điểm của nó
Sử dụng thành phần để tạo các đối tượng phức tạp
Tái sử dụng mã hiện có bằng cách áp dụng thành phần
Thay đổi hành vi ứng dụng trong thời gian chạy thông qua thành phần
Kế thừa và thành phần là gì?
Kế thừa và thành phần là hai khái niệm chính trong lập trình hướng đối tượng mô hình hóa mối quan hệ giữa hai lớp. Họ thúc đẩy thiết kế của một ứng dụng và xác định cách ứng dụng sẽ phát triển khi các tính năng mới được thêm vào hoặc yêu cầu thay đổi. and composition are two major concepts in object oriented programming that model the relationship between two classes. They drive the design of an application and determine how the application should evolve as new features are added or requirements change.
Cả hai đều cho phép tái sử dụng mã, nhưng họ làm điều đó theo những cách khác nhau.
Những gì thừa kế?
Mô hình kế thừa cái được gọi là một mối quan hệ. Điều này có nghĩa là khi bạn có một lớp
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
6 kế thừa từ lớp
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
7, bạn đã tạo ra một mối quan hệ trong đó
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
6 là phiên bản chuyên dụng của
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
7. models what is called an is a relationship. This means that when you have a
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
6 class that inherits from a
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
7 class, you created a relationship where
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
6 is a specialized version of
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
7.
Kế thừa được thể hiện bằng ngôn ngữ mô hình hóa hợp nhất hoặc UML theo cách sau:
Các lớp được biểu diễn dưới dạng hộp có tên lớp trên đầu. Mối quan hệ kế thừa được thể hiện bằng một mũi tên từ lớp dẫn xuất chỉ vào lớp cơ sở. Từ mở rộng thường được thêm vào mũi tên.extends is usually added to the arrow.
Hãy nói rằng bạn có một lớp cơ sở
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
0 và bạn xuất phát từ nó để tạo ra một lớp
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
1. Mối quan hệ thừa kế nói rằng
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
1 là
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
0. Điều này có nghĩa là
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
1 kế thừa giao diện và triển khai
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
0 và các đối tượng
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
1 có thể được sử dụng để thay thế các đối tượng
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
0 trong ứng dụng.is an
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
0. This means that
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
1 inherits the interface and implementation of
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
0, and
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
1 objects can be used to replace
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
0 objects in the
application.
Điều này được gọi là nguyên tắc thay thế Liskov. Nguyên tắc nêu rõ rằng trong một chương trình máy tính, nếu
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
8 là một kiểu con của
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
9, thì các đối tượng thuộc loại
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
9 có thể được thay thế bằng các đối tượng loại
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
8 mà không thay đổi bất kỳ thuộc tính mong muốn nào của chương trình.
Bạn sẽ thấy trong bài viết này tại sao bạn phải luôn tuân theo nguyên tắc thay thế Liskov khi tạo hệ thống phân cấp lớp của bạn và các vấn đề mà bạn sẽ gặp phải nếu bạn không ủng hộ.
Thành phần gì?
Thành phần là một khái niệm mà mô hình A có mối quan hệ. Nó cho phép tạo các loại phức tạp bằng cách kết hợp các đối tượng của các loại khác. Điều này có nghĩa là lớp
Thành phần được thể hiện thông qua một dòng với một viên kim cương ở lớp tổng hợp chỉ vào lớp thành phần. Phía tổng hợp có thể thể hiện tính chất của mối quan hệ. Cardinality cho biết số lượng hoặc phạm vi hợp lệ của các trường hợp
02 có thể chứa một phạm vi của các phiên bản ____10103. Phạm vi được chỉ định với số lượng trường hợp tối thiểu và tối đa, hoặc tối thiểu và nhiều trường hợp như trong 1 ..*. indicates that the
03 instances. The range is indicated with the minimum and maximum number of instances, or minimum and many instances like in 1..*.
Ví dụ: lớp
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
1 của bạn có thể được sáng tác bởi một đối tượng khác của loại
18. Thành phần cho phép bạn thể hiện mối quan hệ đó bằng cách nói
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
Thành phần cho phép bạn sử dụng lại mã bằng cách thêm các đối tượng vào các đối tượng khác, trái ngược với việc kế thừa giao diện và triển khai các lớp khác. Cả hai lớp
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
18 thông qua thành phần mà không cần lấy một lớp từ lớp kia.
Tổng quan về thừa kế trong Python
Tất cả mọi thứ trong Python là một đối tượng. Các mô -đun là đối tượng, định nghĩa lớp và chức năng là đối tượng và tất nhiên, các đối tượng được tạo từ các lớp cũng là đối tượng.
Kế thừa là một tính năng cần thiết của mọi ngôn ngữ lập trình hướng đối tượng. Điều này có nghĩa là Python hỗ trợ thừa kế và như bạn sẽ thấy sau đó, nó là một trong số ít ngôn ngữ hỗ trợ nhiều kế thừa.
Khi bạn viết mã Python bằng các lớp, bạn đang sử dụng kế thừa ngay cả khi bạn không biết bạn sử dụng nó. Hãy cùng xem xét điều đó có nghĩa là gì.
Đối tượng siêu lớp
Cách dễ nhất để thấy sự kế thừa trong Python là nhảy vào vỏ tương tác Python và viết một chút mã. Bạn sẽ bắt đầu bằng cách viết lớp đơn giản nhất có thể:
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> c = MyClass()
>>> dir(c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__']
25 Trả về danh sách tất cả các thành viên trong đối tượng được chỉ định. Bạn đã không tuyên bố bất kỳ thành viên nào trong >>> c = MyClass()
>>> dir(c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__']
24, vậy danh sách đến từ đâu? Bạn có thể tìm hiểu bằng cách sử dụng trình thông dịch tương tác:
31. Ngoại lệ đối với quy tắc này là các lớp được sử dụng để chỉ ra lỗi bằng cách nêu ra một ngoại lệ.
Bạn có thể thấy vấn đề bằng cách sử dụng trình thông dịch tương tác Python:
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
Bạn đã tạo một lớp mới để chỉ ra một loại lỗi. Sau đó, bạn đã cố gắng sử dụng nó để nâng cao một ngoại lệ. Một ngoại lệ được nâng lên nhưng đầu ra nói rằng ngoại lệ thuộc loại
Ví dụ, nhân viên hành chính có mức lương cố định, vì vậy mỗi tuần họ được trả cùng một số tiền:
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
46 được thực hiện bằng cách trả lại thời gian làm việc theo tỷ lệ giờ.
Cuối cùng, công ty sử dụng các cộng sự bán hàng được thanh toán thông qua mức lương cố định cộng với một khoản hoa hồng dựa trên doanh số của họ, vì vậy bạn tạo một lớp
# In hr.pyclassCommissionEmployee(SalaryEmployee):def__init__(self,id,name,weekly_salary,commission):super().__init__(id,name,weekly_salary)self.commission=commissiondefcalculate_payroll(self):fixed=super().calculate_payroll()returnfixed+self.commission
Chương trình tạo ra ba đối tượng nhân viên, một cho mỗi lớp dẫn xuất. Sau đó, nó tạo ra hệ thống bảng lương và chuyển danh sách nhân viên cho phương thức
50 trong ví dụ trên là cái được gọi là lớp cơ sở trừu tượng. Các lớp cơ sở trừu tượng tồn tại để được kế thừa, nhưng không bao giờ khởi tạo. Python cung cấp mô -đun
Bạn có thể sử dụng các dấu gạch dưới hàng đầu trong tên lớp của mình để truyền đạt rằng các đối tượng của lớp đó không nên được tạo. Undercores cung cấp một cách thân thiện để ngăn chặn việc lạm dụng mã của bạn, nhưng họ không ngăn cản người dùng háo hức tạo ra các trường hợp của lớp đó.
96. Các lớp dẫn xuất phải ghi đè phương thức để cho phép tạo các đối tượng thuộc loại của chúng.
Kế thừa thực hiện so với kế thừa giao diện
Khi bạn lấy một lớp này từ một lớp khác, lớp dẫn xuất kế thừa cả hai:
Giao diện lớp cơ sở: lớp dẫn xuất kế thừa tất cả các phương thức, thuộc tính và thuộc tính của lớp cơ sở. The derived class inherits all the methods, properties, and attributes
of the base class.
Việc triển khai lớp cơ sở: Lớp dẫn xuất kế thừa mã thực hiện giao diện lớp. The derived class inherits the code that implements the class interface.
Hầu hết thời gian, bạn sẽ muốn kế thừa việc triển khai một lớp, nhưng bạn sẽ muốn thực hiện nhiều giao diện, vì vậy các đối tượng của bạn có thể được sử dụng trong các tình huống khác nhau.
Ngôn ngữ lập trình hiện đại được thiết kế với khái niệm cơ bản này trong tâm trí. Chúng cho phép bạn kế thừa từ một lớp duy nhất, nhưng bạn có thể triển khai nhiều giao diện.
Trong Python, bạn không phải tuyên bố rõ ràng giao diện. Bất kỳ đối tượng nào thực hiện giao diện mong muốn có thể được sử dụng thay cho một đối tượng khác. Điều này được gọi là gõ vịt. Gõ vịt thường được giải thích là nếu nó hoạt động như một con vịt, thì nó là một con vịt.duck typing. Duck typing is usually explained as “if it behaves like a duck, then it’s a duck.”
44 vẫn có thể xử lý đối tượng mới vì nó đáp ứng giao diện mong muốn.
Vì bạn không phải xuất phát từ một lớp cụ thể để các đối tượng của bạn được chương trình có thể sử dụng lại, bạn có thể hỏi tại sao bạn nên sử dụng kế thừa thay vì chỉ triển khai giao diện mong muốn. Các quy tắc sau có thể giúp bạn:
Sử dụng kế thừa để sử dụng lại một triển khai: Các lớp dẫn xuất của bạn nên tận dụng hầu hết việc thực hiện lớp cơ sở của họ. Họ cũng phải mô hình hóa A là một mối quan hệ. Một lớp
50, vì vậy bạn không nên sử dụng kế thừa. Your derived classes should leverage most of their base class implementation. They must also model an is a relationship. A
Thực hiện một giao diện sẽ được sử dụng lại: Khi bạn muốn lớp của mình được sử dụng lại bởi một phần cụ thể trong ứng dụng của bạn, bạn sẽ thực hiện giao diện cần thiết trong lớp, nhưng bạn không cần cung cấp một lớp cơ sở hoặc kế thừa từ một lớp khác. When you want your class to be reused by a specific part of your application, you implement the required interface in your class, but you don’t need to provide a base class, or inherit from another class.
Bây giờ bạn có thể làm sạch ví dụ trên để chuyển sang chủ đề tiếp theo. Bạn có thể xóa tệp
Nếu bạn không cẩn thận, kế thừa có thể dẫn bạn đến một cấu trúc phân cấp khổng lồ của các lớp khó hiểu và duy trì. Điều này được gọi là vấn đề vụ nổ lớp.class explosion problem.
Bạn bắt đầu xây dựng một hệ thống phân cấp lớp gồm
64 theo dõi năng suất dựa trên vai trò của nhân viên. Có nhiều vai trò nhân viên khác nhau:
Người quản lý: Họ đi bộ xung quanh la hét với mọi người nói với họ phải làm gì. Họ là nhân viên được trả lương và kiếm được nhiều tiền hơn. They walk around yelling at people telling them what to do. They are salaried employees and make more money.
Thư ký: Họ làm tất cả các công việc giấy tờ cho các nhà quản lý và đảm bảo rằng mọi thứ được lập hóa đơn và thanh toán đúng hạn. Họ cũng là nhân viên được trả lương nhưng kiếm ít tiền hơn. They do all the paper work for managers and ensure that everything gets billed and payed on time. They are also salaried employees but make less money.
Nhân viên bán hàng: Họ thực hiện rất nhiều cuộc gọi điện thoại để bán sản phẩm. Họ có một mức lương, nhưng họ cũng nhận được hoa hồng để bán hàng. They make a lot of phone calls to sell products. They have a salary, but they also get commissions for sales.
Nhân viên nhà máy: Họ sản xuất các sản phẩm cho công ty. Họ được trả tiền theo giờ. They manufacture the products for the company. They are paid by the hour.
Chương trình tạo ra một danh sách các nhân viên thuộc các loại khác nhau. Danh sách nhân viên được gửi đến hệ thống năng suất để theo dõi công việc của họ trong 40 giờ. Sau đó, cùng một danh sách nhân viên được gửi đến hệ thống bảng lương để tính toán bảng lương của họ.
Chương trình cho thấy các nhân viên làm việc trong 40 giờ thông qua hệ thống năng suất. Sau đó, nó tính toán và hiển thị bảng lương cho mỗi nhân viên.
Chương trình hoạt động như mong đợi, nhưng bạn phải thêm bốn lớp mới để hỗ trợ các thay đổi. Khi các yêu cầu mới đến, hệ thống phân cấp lớp của bạn chắc chắn sẽ phát triển, dẫn đến vấn đề vụ nổ của lớp trong đó hệ thống phân cấp của bạn sẽ trở nên lớn đến mức chúng sẽ khó hiểu và duy trì.
Biểu đồ sau đây cho thấy hệ thống phân cấp lớp mới:
Sơ đồ cho thấy hệ thống phân cấp lớp đang phát triển như thế nào. Các yêu cầu bổ sung có thể có tác động theo cấp số nhân trong số lượng các lớp với thiết kế này.
Kế thừa nhiều lớp
Python là một trong số ít các ngôn ngữ lập trình hiện đại hỗ trợ nhiều kế thừa. Nhiều kế thừa là khả năng lấy một lớp từ nhiều lớp cơ sở cùng một lúc.
Nhiều kế thừa có tiếng xấu đến mức mà hầu hết các ngôn ngữ lập trình hiện đại không ủng hộ nó. Thay vào đó, các ngôn ngữ lập trình hiện đại hỗ trợ khái niệm giao diện. Trong các ngôn ngữ đó, bạn kế thừa từ một lớp cơ sở duy nhất và sau đó thực hiện nhiều giao diện, do đó lớp của bạn có thể được sử dụng lại trong các tình huống khác nhau.
Cách tiếp cận này đặt một số ràng buộc trong thiết kế của bạn. Bạn chỉ có thể kế thừa việc thực hiện một lớp bằng cách trực tiếp xuất phát từ nó. Bạn có thể triển khai nhiều giao diện, nhưng bạn có thể kế thừa việc triển khai nhiều lớp.
Hạn chế này tốt cho thiết kế phần mềm vì nó buộc bạn phải thiết kế các lớp học của mình với ít phụ thuộc hơn vào nhau. Bạn sẽ thấy sau trong bài viết này rằng bạn có thể tận dụng nhiều triển khai thông qua thành phần, điều này làm cho phần mềm linh hoạt hơn. Tuy nhiên, phần này là về nhiều kế thừa, vì vậy, hãy để xem cách thức hoạt động của nó.
Hóa ra đôi khi các thư ký tạm thời được thuê khi có quá nhiều giấy tờ để làm. Lớp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
Bây giờ, hãy chạy lại chương trình và xem điều gì sẽ xảy ra:
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
08 sẽ giúp:
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
1
Thử nó:
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
2
Điều đó cũng không làm việc. Được rồi, đó là thời gian để bạn đi sâu vào thứ tự phân giải phương pháp Python (MRO) để xem những gì đang diễn ra.method resolution order (MRO) to see
what’s going on.
Khi một phương thức hoặc thuộc tính của một lớp được truy cập, Python sử dụng lớp MRO để tìm nó. MRO cũng được
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
3
MRO hiển thị thứ tự mà Python sẽ tìm kiếm một thuộc tính hoặc phương thức phù hợp. Trong ví dụ, đây là những gì xảy ra khi chúng ta tạo đối tượng
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
13 được gọi.
Cuộc gọi
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
14 khớp với
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
17, mà MRO sẽ khớp với
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
03, được kế thừa từ
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
Bạn có thể bỏ qua MRO bằng cách đảo ngược thứ tự kế thừa và gọi trực tiếp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
21 như sau:
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
4
Điều đó giải quyết vấn đề tạo đối tượng, nhưng bạn sẽ gặp phải một vấn đề tương tự khi cố gắng tính toán bảng lương. Bạn có thể chạy chương trình để xem vấn đề:
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
5
Vấn đề bây giờ là bởi vì bạn đã đảo ngược thứ tự thừa kế, MRO đang tìm phương pháp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
28 để đảm bảo rằng bạn có được kết quả chính xác. Bạn có thể chạy lại chương trình để xem nó hoạt động:
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
7
Chương trình hiện hoạt động như mong đợi bởi vì bạn buộc phải thứ tự giải quyết phương thức bằng cách nói rõ ràng với trình thông dịch mà chúng tôi muốn sử dụng phương pháp nào.
Như bạn có thể thấy, nhiều kế thừa có thể gây nhầm lẫn, đặc biệt là khi bạn gặp phải vấn đề kim cương.
Biểu đồ sau đây cho thấy vấn đề kim cương trong hệ thống phân cấp lớp của bạn:
Sơ đồ cho thấy vấn đề kim cương với thiết kế lớp hiện tại.
50, đó là điều bạn muốn tránh trong các thiết kế của mình.
Vấn đề kim cương xuất hiện khi bạn sử dụng nhiều kế thừa và xuất phát từ hai lớp có lớp cơ sở chung. Điều này có thể khiến phiên bản sai của phương thức được gọi.
Như bạn đã thấy, Python cung cấp một cách để buộc đúng phương pháp được gọi và phân tích MRO có thể giúp bạn hiểu vấn đề.
Tuy nhiên, khi bạn gặp phải vấn đề kim cương, nó tốt hơn là suy nghĩ lại thiết kế. Bây giờ bạn sẽ thực hiện một số thay đổi để tận dụng nhiều kế thừa, tránh vấn đề kim cương.
Hệ thống năng suất theo dõi năng suất của nhân viên. that tracks employee productivity.
Hệ thống biên chế tính toán bảng lương của nhân viên. that calculates the employee payroll.
Điều này có nghĩa là tất cả mọi thứ liên quan đến năng suất nên được cùng nhau trong một mô -đun và mọi thứ liên quan đến bảng lương nên được cùng nhau ở cùng. Bạn có thể bắt đầu thay đổi mô -đun năng suất:
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
8
Mô -đun
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
44, tính toán bảng lương cho nhân viên. Nó cũng thực hiện các lớp chính sách cho bảng lương. Như bạn có thể thấy, các lớp chính sách don don bắt nguồn từ
50 khác nhau. Bạn vẫn đang sử dụng nhiều kế thừa để kế thừa việc thực hiện các lớp chính sách lương và vai trò năng suất, nhưng việc thực hiện từng lớp chỉ cần đối phó với việc khởi tạo.
Lưu ý rằng bạn vẫn cần khởi tạo rõ ràng các chính sách lương trong các nhà xây dựng. Bạn có thể thấy rằng các khởi tạo của
81 bằng cách sử dụng nhiều kế thừa, nhưng tránh được vấn đề kim cương.
Bạn có thể chạy chương trình và xem cách thức hoạt động:
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
1
Bạn đã thấy cách thừa kế và nhiều kế thừa hoạt động trong Python. Bây giờ bạn có thể khám phá chủ đề của sáng tác.
Thành phần trong Python
Thành phần là một khái niệm thiết kế định hướng đối tượng mô hình A có mối quan hệ. Trong thành phần, một lớp được gọi là tổng hợp chứa một đối tượng của một lớp khác được gọi là thành phần. Nói cách khác, một lớp tổng hợp có một thành phần của một lớp khác. is an object oriented design concept that models a has a relationship. In composition, a class known as composite contains an object of another class known to as component. In other
words, a composite class has a component of another class.
Thành phần cho phép các lớp tổng hợp sử dụng lại việc thực hiện các thành phần mà nó chứa. Lớp tổng hợp không kế thừa giao diện lớp thành phần, nhưng nó có thể tận dụng việc triển khai của nó.
Mối quan hệ thành phần giữa hai lớp được coi là kết hợp lỏng lẻo. Điều đó có nghĩa là các thay đổi đối với lớp thành phần hiếm khi ảnh hưởng đến lớp tổng hợp và thay đổi thành lớp tổng hợp không bao giờ ảnh hưởng đến lớp thành phần.
Điều này cung cấp khả năng thích ứng tốt hơn để thay đổi và cho phép các ứng dụng giới thiệu các yêu cầu mới mà không ảnh hưởng đến mã hiện có.
Khi xem xét hai thiết kế phần mềm cạnh tranh, một dựa trên kế thừa và một dựa trên thành phần, giải pháp thành phần thường là linh hoạt nhất. Bây giờ bạn có thể nhìn vào cách thức thành phần hoạt động.
Bạn đã sử dụng thành phần trong các ví dụ của chúng tôi. Nếu bạn nhìn vào lớp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57:
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
2
Bạn đã triển khai một lớp địa chỉ cơ bản chứa các thành phần thông thường cho một địa chỉ. Bạn đã thực hiện thuộc tính
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
58 tùy chọn vì không phải tất cả các địa chỉ sẽ có thành phần đó.
Bạn đã triển khai
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
59 để cung cấp một đại diện khá lớn của
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57. Bạn có thể thấy việc triển khai này trong trình thông dịch tương tác:
>>>
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
3
Khi bạn
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
61 biến
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
62, phương pháp đặc biệt
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
59 được gọi. Vì bạn đã quá tải phương thức để trả về một chuỗi được định dạng dưới dạng địa chỉ, bạn sẽ nhận được một đại diện tốt, dễ đọc. Toán tử và quá tải chức năng trong các lớp Python tùy chỉnh cung cấp một cái nhìn tổng quan tốt về các phương thức đặc biệt có sẵn trong các lớp có thể được thực hiện để tùy chỉnh hành vi của các đối tượng của bạn.
Bây giờ bạn có thể thêm
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
4
Bạn khởi tạo thuộc tính
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
62 cho
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
67 ngay bây giờ để làm cho nó tùy chọn, nhưng bằng cách đó, bây giờ bạn có thể gán một
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
71.
Thành phần là một mối quan hệ được kết hợp lỏng lẻo mà thường không yêu cầu lớp tổng hợp phải có kiến thức về thành phần.
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
93 có địa chỉ không và nếu có, bạn in nó. Bây giờ bạn có thể sửa đổi chương trình để gán một số địa chỉ cho nhân viên:
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
6
Bạn đã thêm một vài địa chỉ vào các đối tượng
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
80 và
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
81. Khi bạn chạy chương trình, bạn sẽ thấy các địa chỉ được in:
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
7
Lưu ý cách đầu ra bảng lương cho các đối tượng
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
80 và
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 mà không có bất kỳ kiến thức nào về đối tượng
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 là gì hoặc cách nó thể hiện. Kiểu thiết kế này linh hoạt đến mức bạn có thể thay đổi lớp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
Thành phần linh hoạt hơn sự kế thừa vì nó mô hình hóa một mối quan hệ được ghép nối lỏng lẻo. Các thay đổi đối với một lớp thành phần có hiệu ứng tối thiểu hoặc không có tác dụng đối với lớp tổng hợp. Thiết kế dựa trên bố cục phù hợp hơn để thay đổi.
Bạn thay đổi hành vi bằng cách cung cấp các thành phần mới thực hiện các hành vi đó thay vì thêm các lớp mới vào hệ thống phân cấp của bạn.
Hãy xem ví dụ về nhiều kế thừa ở trên. Hãy tưởng tượng làm thế nào các chính sách biên chế mới sẽ ảnh hưởng đến thiết kế. Cố gắng hình dung phân cấp lớp sẽ trông như thế nào nếu cần vai trò mới. Như bạn đã thấy trước đây, việc phụ thuộc quá nhiều vào thừa kế có thể dẫn đến vụ nổ giai cấp.
Vấn đề lớn nhất không phải là số lượng các lớp trong thiết kế của bạn, nhưng kết hợp chặt chẽ các mối quan hệ giữa các lớp đó. Các lớp kết hợp chặt chẽ ảnh hưởng đến nhau khi thay đổi được giới thiệu.
Trong phần này, bạn sẽ sử dụng thành phần để thực hiện một thiết kế tốt hơn vẫn phù hợp với các yêu cầu của
64 xác định một số vai trò bằng cách sử dụng một định danh chuỗi được ánh xạ tới một lớp vai trò thực hiện vai trò. Nó phơi bày một phương thức
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
93, với một định danh vai trò, trả về đối tượng loại vai trò. Nếu vai trò không được tìm thấy, thì ngoại lệ
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
94 được nâng lên.
Nó cũng phơi bày chức năng trước đó trong phương pháp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
95, trong đó đưa ra danh sách nhân viên, nó theo dõi năng suất của những nhân viên đó.
Bây giờ bạn có thể thực hiện các lớp vai trò khác nhau:
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
9
Mỗi vai trò bạn đã thực hiện để lộ một
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
74 hoạt động. Các phương pháp trả về một chuỗi đại diện cho các nhiệm vụ.
Các lớp vai trò độc lập với nhau, nhưng chúng phơi bày cùng một giao diện, vì vậy chúng có thể hoán đổi cho nhau. Bạn sẽ thấy sau đó chúng được sử dụng trong ứng dụng như thế nào.
47 được chỉ định không tồn tại trong hệ thống, thì phương thức này sẽ tăng ngoại lệ
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
23.
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
23 được sử dụng để tính toán
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
25, được thực hiện như một thuộc tính để nó được tính toán khi được yêu cầu. Trong ví dụ, chúng tôi giả định rằng việc bán hàng xảy ra cứ sau 5 giờ hoạt động và
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
25 là số lần bán hàng giá trị
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
23.
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
31 giữ cơ sở dữ liệu nội bộ của các đối tượng
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 cho mỗi nhân viên. Nó phơi bày một phương thức
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
94.
Việc triển khai lớp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 vẫn giống như trước đây:
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
2
Lớp quản lý các thành phần địa chỉ và cung cấp một đại diện khá lớn của một địa chỉ.
Cho đến nay, các lớp mới đã được mở rộng để hỗ trợ nhiều chức năng hơn, nhưng không có thay đổi đáng kể nào đối với thiết kế trước đó. Điều này sẽ thay đổi với thiết kế của mô -đun
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
62. Nó cũng yêu cầu năng suất
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
44 cho nhân viên và chính sách
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 và phụ thuộc vào giao diện
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
Thiết kế này được gọi là thiết kế dựa trên chính sách, nơi các lớp bao gồm các chính sách và họ ủy thác cho các chính sách đó để thực hiện công việc.
Thiết kế dựa trên chính sách đã được giới thiệu trong cuốn sách Thiết kế C ++ hiện đại và nó sử dụng metaproming mẫu trong C ++ để đạt được kết quả.
Python không hỗ trợ các mẫu, nhưng bạn có thể đạt được kết quả tương tự bằng cách sử dụng thành phần, như bạn đã thấy trong ví dụ trên.
Loại thiết kế này cung cấp cho bạn tất cả sự linh hoạt mà bạn sẽ cần khi yêu cầu thay đổi. Hãy tưởng tượng bạn cần thay đổi cách tính toán bảng lương cho một đối tượng trong thời gian chạy.
Tùy chỉnh hành vi với bố cục
Nếu thiết kế của bạn dựa vào kế thừa, bạn cần tìm cách thay đổi loại đối tượng để thay đổi hành vi của nó. Với thành phần, bạn chỉ cần thay đổi chính sách mà đối tượng sử dụng.
Hãy tưởng tượng rằng
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
80 của chúng tôi đột nhiên trở thành một nhân viên tạm thời được trả tiền theo giờ. Bạn có thể sửa đổi đối tượng trong quá trình thực hiện chương trình theo cách sau:
Kiểm tra Mary Poppins, người quản lý của chúng tôi, hiện có giá 2200 đô la thay vì mức lương cố định là 3000 đô la mà cô ấy có mỗi tuần.
Lưu ý cách chúng tôi đã thêm quy tắc kinh doanh vào chương trình mà không thay đổi bất kỳ lớp nào hiện có. Xem xét loại thay đổi nào sẽ được yêu cầu với một thiết kế thừa kế.
Bạn sẽ phải tạo ra một lớp mới và thay đổi loại nhân viên của người quản lý. Không có cơ hội bạn có thể thay đổi chính sách vào thời điểm chạy.
Lựa chọn giữa thừa kế và thành phần trong Python
Cho đến nay, bạn đã thấy cách thừa kế và sáng tác hoạt động trong Python. Bạn đã thấy rằng các lớp có nguồn gốc kế thừa giao diện và triển khai các lớp cơ sở của họ. Bạn cũng đã thấy rằng thành phần cho phép bạn sử dụng lại việc thực hiện một lớp khác.
Bạn đã thực hiện hai giải pháp cho cùng một vấn đề. Giải pháp đầu tiên sử dụng nhiều kế thừa và thành phần thứ hai được sử dụng.
Bạn cũng đã thấy rằng gõ vịt Python cho phép bạn sử dụng lại các đối tượng với các phần hiện có của chương trình bằng cách triển khai giao diện mong muốn. Trong Python, không cần thiết phải xuất phát từ một lớp cơ sở để các lớp học của bạn được tái sử dụng.
Tại thời điểm này, bạn có thể hỏi khi nào nên sử dụng kế thừa so với thành phần trong Python. Cả hai đều cho phép tái sử dụng mã. Kế thừa và thành phần có thể giải quyết các vấn đề tương tự trong các chương trình Python của bạn.
Lời khuyên chung là sử dụng mối quan hệ tạo ra ít phụ thuộc hơn giữa hai lớp. Mối quan hệ này là thành phần. Tuy nhiên, sẽ có những lúc thừa kế sẽ có ý nghĩa hơn.
Các phần sau đây cung cấp một số hướng dẫn để giúp bạn đưa ra lựa chọn đúng đắn giữa kế thừa và thành phần trong Python.
Kế thừa cho mô hình “là một mối quan hệ”
Kế thừa chỉ nên được sử dụng để mô hình hóa A là một mối quan hệ. Nguyên tắc thay thế Liskov, nói rằng một đối tượng loại
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
6, kế thừa từ
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
7, có thể thay thế một đối tượng loại
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
7 mà không làm thay đổi các thuộc tính mong muốn của chương trình.is a relationship. Liskov’s substitution principle says that an object of type
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
6, which inherits from
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
7, can replace an object of type
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
7 without altering the desirable properties of a program.
Nguyên tắc thay thế Liskov, là hướng dẫn quan trọng nhất để xác định xem việc kế thừa có phải là giải pháp thiết kế phù hợp hay không. Tuy nhiên, câu trả lời có thể không đơn giản trong mọi tình huống. May mắn thay, có một thử nghiệm đơn giản mà bạn có thể sử dụng để xác định xem thiết kế của bạn có tuân theo nguyên tắc thay thế của Liskov hay không.
Hãy nói rằng bạn có một lớp
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
77 cung cấp một triển khai và giao diện mà bạn muốn sử dụng lại trong một lớp khác
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
78. Suy nghĩ ban đầu của bạn là bạn có thể lấy
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
78 từ
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
77 và kế thừa cả giao diện và triển khai. Để chắc chắn rằng đây là thiết kế phù hợp, bạn làm theo các bước luận văn:
Đánh giá
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
78 là một
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
77: Hãy suy nghĩ về mối quan hệ này và biện minh cho nó. Nó có ý nghĩa không? Think about this relationship and justify it. Does it make sense?
Đánh giá
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
77 là một
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
78: đảo ngược mối quan hệ và biện minh cho nó. Nó cũng có ý nghĩa? Reverse the relationship and justify it. Does it also make sense?
Nếu bạn có thể biện minh cho cả hai mối quan hệ, thì bạn không bao giờ nên thừa hưởng các lớp đó từ nhau. Hãy cùng nhìn vào một ví dụ cụ thể hơn.
Bạn có một lớp
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85 phơi bày một thuộc tính
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
86. Bạn cần một lớp
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87, cũng có
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
86. Có vẻ như
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87 là một loại
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85 đặc biệt, vì vậy có lẽ bạn có thể lấy từ nó và tận dụng cả giao diện và triển khai.
Trước khi bạn nhảy vào việc thực hiện, bạn sử dụng nguyên tắc thay thế Liskov, để đánh giá mối quan hệ.
A
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87 là
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85 vì diện tích của nó được tính toán từ sản phẩm của
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
93 lần
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
94 của nó. Hạn chế là
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
95 và
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
96 phải bằng nhau.is a
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85 because its area is calculated from the product of its
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
93 times its
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
94. The constraint is that
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
95 and
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
96 must be equal.
Nó có ý nghĩa. Bạn có thể biện minh cho mối quan hệ và giải thích tại sao
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87 là
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85. Hãy để đảo ngược mối quan hệ để xem nó có ý nghĩa không.is a
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85. Let’s reverse the relationship to see if it makes sense.
A
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85 là
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87 vì diện tích của nó được tính toán từ sản phẩm của
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
93 lần
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
Nó cũng có ý nghĩa. Bạn có thể biện minh cho mối quan hệ và mô tả các ràng buộc đặc biệt cho mỗi lớp. Đây là một dấu hiệu tốt cho thấy hai lớp này không bao giờ nên xuất phát từ nhau.
Bạn có thể đã thấy các ví dụ khác xuất phát từ
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87 từ
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85 để giải thích kế thừa. Bạn có thể hoài nghi với bài kiểm tra nhỏ mà bạn vừa làm. Đủ công bằng. Hãy để viết một chương trình minh họa vấn đề với việc lấy
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87 từ
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85.
Đầu tiên, bạn thực hiện
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85. Bạn thậm chí sẽ gói gọn các thuộc tính để đảm bảo rằng tất cả các ràng buộc được đáp ứng:
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
0
Lớp
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85 được khởi tạo với
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
94 và
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
93, và nó cung cấp một thuộc tính
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
86 trả về khu vực.
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
94 và
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
93 được gói gọn để tránh thay đổi chúng trực tiếp.
Bây giờ, bạn rút ra
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87 từ
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85 và ghi đè giao diện cần thiết để đáp ứng các ràng buộc của
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87:
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
1
Lớp
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
Trong một chương trình nhỏ như thế này, có thể dễ dàng phát hiện ra nguyên nhân của hành vi kỳ lạ, nhưng trong một chương trình phức tạp hơn, vấn đề sẽ khó tìm hơn.
Thực tế là nếu bạn có thể biện minh cho mối quan hệ thừa kế giữa hai lớp cả hai cách, bạn không nên lấy một lớp này từ một lớp khác.
Trong ví dụ, nó không có ý nghĩa rằng
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
Sự khác biệt trong giao diện này biện minh cho việc không lấy được
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
87 từ
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
85 như bài kiểm tra ở trên nên được khuyên.
Trộn các tính năng với các lớp mixin
Một trong những cách sử dụng của nhiều kế thừa trong Python là mở rộng một tính năng của lớp thông qua mixin. Mixin là một lớp cung cấp các phương pháp cho các lớp khác nhưng không được coi là một lớp cơ sở.mixin is a class that provides methods to other classes but are not considered a base class.
Một mixin cho phép các lớp khác sử dụng lại giao diện và triển khai của nó mà không trở thành một lớp siêu. Họ thực hiện một hành vi độc đáo có thể được tổng hợp cho các lớp không liên quan khác. Chúng tương tự như sáng tác nhưng chúng tạo ra một mối quan hệ mạnh mẽ hơn.
Hãy nói rằng bạn muốn chuyển đổi các đối tượng thuộc một số loại nhất định trong ứng dụng của bạn thành biểu diễn từ điển của đối tượng. Bạn có thể cung cấp một phương thức
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
57 để hỗ trợ chức năng. Sẽ rất tuyệt khi hỗ trợ chức năng tương tự trong lớp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
2
Bạn áp dụng mixin cho lớp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 để hỗ trợ tính năng. Bây giờ, bạn có thể viết một chương trình nhỏ để kiểm tra nó:
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
52. Bạn có thể chạy chương trình để xem đầu ra của nó:
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57.
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 thực hiện tất cả các chức năng để xử lý các địa chỉ và nó có thể được sử dụng lại bởi các lớp khác.has an
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 object.
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 implements all the functionality to handle addresses, and it can be reused by other classes.
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
50. Họ có thể tận dụng cùng một triển khai đảm bảo rằng các địa chỉ được xử lý nhất quán trên toàn bộ ứng dụng.
Một vấn đề bạn có thể gặp phải khi sử dụng thành phần là một số lớp của bạn có thể bắt đầu phát triển bằng cách sử dụng nhiều thành phần. Các lớp của bạn có thể yêu cầu nhiều tham số trong hàm tạo chỉ để truyền trong các thành phần mà chúng được tạo. Điều này có thể làm cho các lớp học của bạn khó sử dụng.
Một cách để tránh vấn đề là sử dụng phương pháp nhà máy để xây dựng các đối tượng của bạn. Bạn đã làm điều đó với ví dụ thành phần.
Nếu bạn nhìn vào việc triển khai lớp
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
40, bạn sẽ nhận thấy rằng nó sử dụng
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
00.
Những thay đổi sau đây có thể cải thiện thiết kế của bạn. Bạn có thể bắt đầu với mô -đun
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
33:
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
5
Đầu tiên, bạn thực hiện lớp nội bộ
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
02, sau đó cung cấp một biến bên trong
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
03 cho mô -đun. Bạn đang liên lạc với các nhà phát triển khác rằng họ không nên tạo hoặc sử dụng trực tiếp
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
02. Thay vào đó, bạn cung cấp hai chức năng,
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
6
Một lần nữa, bạn thực hiện nội bộ
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
09 và cung cấp giao diện công cộng cho nó. Ứng dụng sẽ sử dụng giao diện công cộng để có được chính sách và tính toán bảng lương.
Bây giờ bạn sẽ làm tương tự với mô -đun
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
71:
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
7
Về cơ bản, bạn đang nói rằng chỉ nên có một
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
11, một
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
09 và một
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
02. Một lần nữa, mô hình thiết kế này được gọi là mẫu thiết kế Singleton, có ích cho các lớp mà chỉ nên có một, một trường hợp duy nhất.
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
15, nhưng bạn sẽ thực hiện một số thay đổi bổ sung:
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
8
Trước tiên, bạn nhập các chức năng và lớp liên quan từ các mô -đun khác.
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
15 được thực hiện nội bộ và ở phía dưới, bạn tạo một thể hiện duy nhất. Trường hợp này là công khai và một phần của giao diện vì bạn sẽ muốn sử dụng nó trong ứng dụng.
Bạn đã thay đổi thuộc tính
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
17 thành một từ điển trong đó chìa khóa là nhân viên
47 và sử dụng các chức năng công khai được phơi bày trong các mô -đun khác để khởi tạo các thuộc tính của nó.
Bây giờ bạn có thể thay đổi chương trình để kiểm tra các thay đổi:
# In hr.pyclassSalaryEmployee(Employee):def__init__(self,id,name,weekly_salary):super().__init__(id,name)self.weekly_salary=weekly_salarydefcalculate_payroll(self):returnself.weekly_salary
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
33, cũng như lớp
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
47 của nó. Bạn có thể chạy chương trình để xem đầu ra của nó:
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
0
Chương trình hoạt động giống như trước đây, nhưng bây giờ bạn có thể thấy rằng một đối tượng
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
50 là một tổng hợp chứa nhiều đối tượng cung cấp chức năng khác nhau. Nó chứa một
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
57 thực hiện tất cả các chức năng liên quan đến nơi nhân viên sống.
50 cũng chứa vai trò năng suất được cung cấp bởi mô -đun
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
33 và chính sách biên chế được cung cấp bởi mô -đun
Bạn đang sử dụng sáng tác theo hai cách khác nhau. Lớp
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
50 và các đối tượng đó được ghép nối một cách lỏng lẻo, cung cấp một số khả năng thú vị mà bạn sẽ thấy trong phần tiếp theo.
Thành phần để thay đổi hành vi thời gian chạy
Kế thừa, trái ngược với sáng tác, là một mối quan hệ cặp đôi chặt chẽ. Với sự kế thừa, chỉ có một cách để thay đổi và tùy chỉnh hành vi. Ghi đè phương thức là cách duy nhất để tùy chỉnh hành vi của một lớp cơ sở. Điều này tạo ra các thiết kế cứng nhắc khó thay đổi.
Mặt khác, thành phần cung cấp một mối quan hệ được ghép nối lỏng lẻo cho phép thiết kế linh hoạt và có thể được sử dụng để thay đổi hành vi trong thời gian chạy.
Hãy tưởng tượng bạn cần hỗ trợ chính sách khuyết tật dài hạn (LTD) khi tính toán bảng lương. Chính sách tuyên bố rằng một nhân viên trên LTD nên được trả 60% tiền lương hàng tuần của họ với 40 giờ làm việc.
Với một thiết kế kế thừa, đây có thể là một yêu cầu rất khó để hỗ trợ. Thêm nó vào ví dụ thành phần dễ dàng hơn rất nhiều. Hãy bắt đầu bằng cách thêm lớp chính sách:
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
2
Lưu ý rằng
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
47 không thừa kế
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
05, nhưng thực hiện cùng một giao diện. Điều này là do việc triển khai hoàn toàn khác nhau, vì vậy chúng tôi không muốn kế thừa bất kỳ triển khai
>>> classMyError(Exception):... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>__main__.MyError
05 nào.
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
47 khởi tạo
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
51 đến
>>> classMyError:... pass...>>> raiseMyError()Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: exceptions must derive from BaseException
67 và cung cấp một phương thức
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
53 nội bộ làm tăng ngoại lệ nếu
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
54 chưa được áp dụng. Sau đó, nó cung cấp một phương thức
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
55 để gán
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
51.
Giao diện công cộng trước tiên kiểm tra xem
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
51 đã được áp dụng, và sau đó thực hiện chức năng theo chính sách cơ sở đó. Phương pháp
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
3
Bạn đã thêm một phương thức
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
62 áp dụng chính sách bảng lương hiện có cho chính sách mới và sau đó thay thế nó. Bây giờ bạn có thể sửa đổi chương trình để áp dụng chính sách cho đối tượng
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
4
Chương trình truy cập
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
64, được đặt tại Index
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
65, tạo đối tượng
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
46 được gọi, sự thay đổi được phản ánh. Bạn có thể chạy chương trình để đánh giá đầu ra:
# In hr.pyclassHourlyEmployee(Employee):def__init__(self,id,name,hours_worked,hour_rate):super().__init__(id,name)self.hours_worked=hours_workedself.hour_rate=hour_ratedefcalculate_payroll(self):returnself.hours_worked*self.hour_rate
5
Số tiền kiểm tra cho nhân viên Kevin Bacon, nhân viên bán hàng, hiện có giá $ 1080 thay vì $ 1800. Đó là vì
# In hr.pyclassEmployee:def__init__(self,id,name):self.id=idself.name=name
47 đã được áp dụng cho mức lương.
Như bạn có thể thấy, bạn đã có thể hỗ trợ các thay đổi chỉ bằng cách thêm một chính sách mới và sửa đổi một vài giao diện. Đây là loại linh hoạt mà thiết kế chính sách dựa trên thành phần mang lại cho bạn.
Lựa chọn giữa thừa kế và thành phần trong Python
Python, như một ngôn ngữ lập trình định hướng đối tượng, hỗ trợ cả kế thừa và thành phần. Bạn thấy rằng sự kế thừa được sử dụng tốt nhất để mô hình hóa A là một mối quan hệ, trong khi các mô hình thành phần A có mối quan hệ.is a relationship, whereas composition models a has a relationship.
Đôi khi, thật khó để thấy mối quan hệ giữa hai lớp nên là gì, nhưng bạn có thể làm theo các hướng dẫn này:
Sử dụng kế thừa trên thành phần trong Python để mô hình hóa một rõ ràng là một mối quan hệ. Đầu tiên, biện minh cho mối quan hệ giữa lớp dẫn xuất và cơ sở của nó. Sau đó, đảo ngược mối quan hệ và cố gắng biện minh cho nó. Nếu bạn có thể biện minh cho mối quan hệ theo cả hai hướng, thì bạn không nên sử dụng kế thừa giữa chúng. to model a clear is a relationship. First, justify the relationship
between the derived class and its base. Then, reverse the relationship and try to justify it. If you can justify the relationship in both directions, then you should not use inheritance between them.
Sử dụng kế thừa trên thành phần trong Python để tận dụng cả giao diện và thực hiện lớp cơ sở. to leverage both the interface and implementation of the base class.
Sử dụng kế thừa trên thành phần trong Python để cung cấp các tính năng mixin cho một số lớp không liên quan khi chỉ có một triển khai của tính năng đó. to provide mixin features to several unrelated classes when
there is only one implementation of that feature.
Sử dụng thành phần trên sự kế thừa trong Python để mô hình A có mối quan hệ tận dụng việc thực hiện lớp thành phần. to model a has a relationship that leverages the implementation of the component class.
Sử dụng thành phần trên sự kế thừa trong Python để tạo ra các thành phần có thể được sử dụng lại bởi nhiều lớp trong các ứng dụng Python của bạn. to create components that can be reused by multiple classes in your Python applications.
Sử dụng thành phần trên sự kế thừa trong Python để thực hiện các nhóm hành vi và chính sách có thể được áp dụng thay thế cho các lớp khác để tùy chỉnh hành vi của họ. to implement groups of
behaviors and policies that can be applied interchangeably to other classes to customize their behavior.
Sử dụng thành phần trên kế thừa trong Python để cho phép thay đổi hành vi thời gian chạy mà không ảnh hưởng đến các lớp hiện có. to enable run-time behavior changes without affecting existing classes.
Sự kết luận
Bạn đã khám phá sự kế thừa và sáng tác trong Python. Bạn đã học về loại mối quan hệ mà sự kế thừa và sáng tác tạo ra. Bạn cũng đã trải qua một loạt các bài tập để hiểu làm thế nào kế thừa và sáng tác được thực hiện trong Python.inheritance and composition
in Python. You learned about the type of relationships that inheritance and composition create. You also went through a series of exercises to understand how inheritance and composition are implemented in Python.
Trong bài viết này, bạn đã học được cách:
Sử dụng thừa kế để thể hiện mối quan hệ giữa hai lớpis a relationship between two classes
Đánh giá xem thừa kế có phải là mối quan hệ đúng không
Sử dụng nhiều kế thừa trong Python và đánh giá Python từ MRO để khắc phục sự cố nhiều vấn đề về kế thừa
Mở rộng các lớp học với mixin và sử dụng lại việc triển khai của họ
Sử dụng thành phần để thể hiện A có mối quan hệ giữa hai lớphas a relationship between two classes
Cung cấp các thiết kế linh hoạt bằng cách sử dụng bố cục
Tái sử dụng mã hiện có thông qua thiết kế chính sách dựa trên thành phần
Đề xuất đọc
Dưới đây là một số cuốn sách và bài viết khám phá thêm thiết kế theo hướng đối tượng và có thể hữu ích để giúp bạn hiểu được việc sử dụng chính xác sự kế thừa và bố cục trong Python hoặc các ngôn ngữ khác:
Các mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng
Đầu thiết kế đầu tiên: Hướng dẫn thân thiện với não
Mã sạch: Cẩm nang về sự khéo léo của phần mềm Agile
Nguyên tắc vững chắc
Nguyên tắc thay thế Liskov
Xem bây giờ hướng dẫn này có một khóa học video liên quan được tạo bởi nhóm Python thực sự. Xem nó cùng với hướng dẫn bằng văn bản để làm sâu sắc thêm sự hiểu biết của bạn: Kế thừa và sáng tác: Hướng dẫn Python OOP This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Inheritance and Composition: A Python OOP Guide
Có nghĩa là gì bởi sự thừa kế trong Python?
Kế thừa cho phép chúng tôi xác định một lớp kế thừa tất cả các phương thức và thuộc tính từ một lớp khác. Lớp cha là lớp được kế thừa, còn được gọi là lớp cơ sở. Lớp con là lớp kế thừa từ một lớp khác, còn được gọi là lớp dẫn xuất.allows us to define a class that inherits all the methods and properties from another class. Parent class is the class being inherited from, also called base class. Child class is the class that inherits from another class, also called derived class.
Kế thừa trong mô hình thiết kế là gì?
Kế thừa có thể là một mẫu hữu ích khi bạn nhận thấy rằng bạn đang xác định cùng một hành vi trên nhiều trường hợp. Quay trở lại ví dụ về một chương trình liên quan đến các hình dạng của nhiều loại khác nhau, bạn có thể có một phương thức Drawborder được nhân đôi trên nhiều loại hình dạng khác nhau.defining the same behavior across multiple instances. Back in the example of a program that deals with shapes of various types, you might have a method drawBorder that's being duplicated across multiple different types of shapes.
Các loại phương pháp kế thừa khác nhau trong Python là gì?
Kế thừa trong Python với các ví dụ: tất cả những gì bạn cần biết..
Thừa kế duy nhất ..
Nhiều kế thừa ..
Thừa kế đa cấp ..
Thừa kế phân cấp ..
Thừa kế lai ..
Sự thừa kế trong Python với ví dụ thực tế là gì?
Mối quan hệ đơn giản giữa hai lớp nói rằng Apple Apple là một loại trái cây.Lớp Apple Apple kế thừa giao diện và triển khai lớp cơ sở, trái cây, sao cho các đối tượng của Apple có thể thay thế các đối tượng trái cây mà không thay đổi bất kỳ thuộc tính mong muốn nào trong một ứng dụng.