Là thừa kế của Python có phải là kiểu phong cách thừa kế hay phong cách sáng tác của Python không?


10

Cho rằng Python cho phép nhiều kế thừa, thừa kế thành ngữ trong Python trông như thế nào?

Trong các ngôn ngữ có tính kế thừa duy nhất, như Java, tính kế thừa sẽ được sử dụng khi bạn có thể nói rằng một đối tượng "là-a" của một đối tượng khác và bạn muốn chia sẻ mã giữa các đối tượng (từ đối tượng cha sang đối tượng con). Ví dụ: bạn có thể nói đó DogAnimal:

public class Animal {...}
public class Dog extends Animal {...}

Nhưng vì Python hỗ trợ nhiều kế thừa, chúng ta có thể tạo một đối tượng bằng cách kết hợp nhiều đối tượng khác lại với nhau. Hãy xem xét ví dụ dưới đây:

class UserService(object):
    def validate_credentials(self, username, password):
        # validate the user credentials are correct
        pass


class LoggingService(object):
    def log_error(self, error):
        # log an error
        pass


class User(UserService, LoggingService):
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def authenticate(self):
        if not super().validate_credentials(self.username, self.password):
            super().log_error('Invalid credentials supplied')
            return False
         return True

Đây có phải là một sự chấp nhận hay sử dụng tốt của nhiều kế thừa trong Python? Thay vì nói thừa kế là khi một đối tượng "là-a" của một đối tượng khác, chúng ta tạo một Usermô hình bao gồm UserServiceLoggingService.

Tất cả logic cho cơ sở dữ liệu hoặc hoạt động mạng có thể được tách biệt khỏi Usermô hình bằng cách đặt chúng vào UserServiceđối tượng và giữ tất cả logic để đăng nhập LoggingService.

Tôi thấy một số vấn đề với phương pháp này là:

  • Điều này có tạo ra một đối tượng Thiên Chúa? Vì Userthừa kế từ, hoặc bao gồm, UserServiceLoggingServicenó có thực sự tuân theo nguyên tắc trách nhiệm đơn lẻ không?
  • Để truy cập các phương thức trên một đối tượng cha / dòng tiếp theo (ví dụ: UserService.validate_credentialschúng ta phải sử dụng super. Điều này làm cho khó khăn hơn một chút để xem đối tượng nào sẽ xử lý phương thức này và không rõ ràng như, nói , bắt đầu UserServicevà làm một cái gì đó nhưself.user_service.validate_credentials

Điều gì sẽ là cách Pythonic để thực hiện mã trên?

Câu trả lời:


9

Là thừa kế của Python có phải là kiểu phong cách thừa kế hay phong cách sáng tác của Python không?

Python hỗ trợ cả hai phong cách. Bạn đang thể hiện mối quan hệ có thành phần, trong đó Người dùng có chức năng ghi nhật ký từ một nguồn và xác thực thông tin xác thực từ một nguồn khác. Các cơ sở LoggingServiceUserServicelà các mixin: chúng cung cấp chức năng và không có ý định tự khởi tạo.

Bằng cách kết hợp các mixin trong loại, bạn có một Người dùng có thể Đăng nhập, nhưng người này phải thêm chức năng khởi tạo của riêng mình.

Điều này không có nghĩa là người ta không thể dính vào thừa kế đơn lẻ. Python cũng hỗ trợ điều đó. Nếu khả năng phát triển của bạn bị cản trở bởi sự phức tạp của nhiều kế thừa, bạn có thể tránh nó cho đến khi bạn cảm thấy thoải mái hơn hoặc đi đến một điểm trong thiết kế nơi bạn tin rằng nó đáng để đánh đổi.

Điều này có tạo ra một đối tượng Thiên Chúa?

Việc ghi nhật ký có vẻ hơi tiếp tuyến - Python có mô-đun ghi nhật ký riêng với đối tượng logger và quy ước là có một logger cho mỗi mô-đun.

Nhưng đặt mô-đun đăng nhập sang một bên. Có lẽ điều này vi phạm trách nhiệm đơn lẻ, nhưng có lẽ, trong bối cảnh cụ thể của bạn, việc xác định Người dùng là rất quan trọng. Trách nhiệm có thể gây tranh cãi. Nhưng nguyên tắc rộng hơn là Python cho phép người dùng đưa ra quyết định.

Là siêu ít rõ ràng?

superchỉ cần thiết khi bạn cần ủy quyền cho cha mẹ trong Lệnh phân giải phương thức (MRO) từ bên trong một hàm cùng tên. Sử dụng nó thay vì mã hóa cứng, một cuộc gọi đến phương thức của cha mẹ là cách tốt nhất. Nhưng nếu bạn không định mã hóa phụ huynh, bạn không cần super.

Trong ví dụ của bạn ở đây, bạn chỉ cần làm self.validate_credentials. selfkhông rõ ràng hơn, từ quan điểm của bạn. Cả hai đều theo MRO. Tôi chỉ đơn giản là sử dụng mỗi nơi thích hợp.

Nếu bạn đã gọi authenticate, validate_credentialsthay vào đó, bạn sẽ cần phải sử dụng super(hoặc mã cứng cha mẹ) để tránh lỗi đệ quy.

Đề xuất mã thay thế

Vì vậy, giả sử ngữ nghĩa là OK (như đăng nhập), điều tôi sẽ làm là, trong lớp User:

    def validate_credentials(self): # changed from "authenticate" to 
                                    # demonstrate need for super
        if not super().validate_credentials(self.username, self.password):
            # just use self on next call, no need for super:
            self.log_error('Invalid credentials supplied') 
            return False
        return True

1
Tôi không đồng ý. Kế thừa luôn tạo một bản sao của giao diện chung của một lớp trong các lớp con của nó. Đây không phải là một mối quan hệ "có-có". Đây là phân nhóm, đơn giản và đơn giản, và do đó không phù hợp với ứng dụng được mô tả.
Jules

@Jules Bạn không đồng ý với điều gì? Tôi đã nói rất nhiều điều có thể chứng minh được, và đưa ra kết luận theo logic. Bạn không chính xác khi bạn nói, "Thừa kế luôn tạo ra một bản sao của giao diện công cộng của một lớp trong lớp con của nó." Trong Python, không có bản sao - các phương thức được tra cứu động theo thứ tự độ phân giải phương thức thuật toán C3 (MRO).
Aaron Hall

1
Vấn đề không phải là về các chi tiết cụ thể về cách thức triển khai hoạt động, đó là về giao diện chung của lớp trông như thế nào. Trong trường hợp của ví dụ, Usercác đối tượng có trong các giao diện của chúng không chỉ các thành viên được định nghĩa trong Userlớp, mà cả các đối tượng được định nghĩa trong UserServiceLoggingService. Đây không phải là mối quan hệ "có-có", bởi vì giao diện công cộng được sao chép (mặc dù không thông qua sao chép trực tiếp, mà bằng cách tra cứu gián tiếp các giao diện của siêu lớp).
Jules

Has-a có nghĩa là Thành phần. Mixins là một dạng của Thành phần. Lớp Người dùng không phải Dịch vụ Người dùng hoặc LoggingService, nhưng nó chức năng đó. Tôi nghĩ rằng sự kế thừa của Python khác với Java hơn bạn nghĩ.
Aaron Hall

@AaronHall Bạn đang đơn giản hóa quá mức (điều này có xu hướng mâu thuẫn với bạn câu trả lời khác , mà tôi tình cờ tìm thấy). Từ quan điểm của mối quan hệ phụ, Người dùng vừa là Dịch vụ người dùng vừa là Dịch vụ ghi nhật ký. Bây giờ, tinh thần ở đây là sáng tác các chức năng để Người dùng có các chức năng như vậy và như vậy. Mixins nói chung không yêu cầu phải được thực hiện với nhiều kế thừa. Tuy nhiên đây là cách thông thường để làm như vậy trong Python.
coredump

-1

Khác với thực tế là nó cho phép nhiều siêu lớp, tính kế thừa của Python không khác biệt nhiều so với Java, tức là các thành viên của một lớp con cũng là thành viên của mỗi siêu kiểu của chúng [1]. Việc Python sử dụng kiểu gõ vịt cũng không có gì khác biệt: lớp con của bạn có tất cả các thành viên của siêu lớp của nó, do đó, có thể được sử dụng bởi bất kỳ mã nào có thể sử dụng các lớp con đó. Thực tế là nhiều kế thừa được thực hiện một cách hiệu quả bằng cách sử dụng thành phần là một cá trích đỏ: việc sao chép tự động các thuộc tính của lớp này sang lớp khác là vấn đề, và nó không quan trọng dù nó sử dụng thành phần hay chỉ đoán một cách kỳ diệu cách các thành viên được cho là để làm việc: có chúng là sai.

Đúng, điều này vi phạm trách nhiệm đơn lẻ, bởi vì bạn đang cho đối tượng của mình khả năng thực hiện các hành động không phải là một phần logic của những gì chúng được thiết kế để thực hiện. Vâng, nó tạo ra các đối tượng "thần", về cơ bản là một cách khác để nói điều tương tự.

Khi thiết kế các hệ thống hướng đối tượng trong Python, câu châm ngôn tương tự được giảng dạy bởi sách thiết kế Java cũng được áp dụng: thích sáng tác hơn so với kế thừa. Điều tương tự cũng đúng với (hầu hết [2]) các hệ thống khác có nhiều kế thừa.

[1]: bạn có thể gọi đó là mối quan hệ "is-a", mặc dù cá nhân tôi không thích thuật ngữ này vì nó cho thấy ý tưởng mô hình hóa thế giới thực và mô hình hướng đối tượng không giống với thế giới thực.

[2]: Tôi không chắc lắm về C ++. C ++ hỗ trợ "thừa kế riêng", về cơ bản là thành phần mà không cần chỉ định tên trường khi bạn muốn sử dụng các thành viên công khai của lớp kế thừa. Nó hoàn toàn không ảnh hưởng đến giao diện chung của lớp. Tôi không thích sử dụng nó, nhưng tôi không thể thấy bất kỳ lý do chính đáng nào để không.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.