การใช้คำสั่งการแก้ไขวิธีการของ Python สำหรับการฉีดที่ต้องพึ่งพา - นี่มันแย่ไหม?


11

ฉันดู Pycon ของ Raymond Hettinger พูดว่า "Super Considered Super" และเรียนรู้เล็กน้อยเกี่ยวกับ PROON ของ MRO (Method Resolution Order) ซึ่งทำให้ชั้นเรียนเป็น "ผู้ปกครอง" ในลักษณะเชิงเส้น เราสามารถใช้สิ่งนี้เพื่อประโยชน์ของเราเช่นในรหัสด้านล่างเพื่อทำการฉีดพึ่งพา ตอนนี้ตามธรรมชาติฉันต้องการใช้superสำหรับทุกสิ่ง!

ในตัวอย่างด้านล่างที่Userระดับประกาศอ้างอิงมันโดยการสืบทอดจากทั้งสองและLoggingService UserServiceนี่ไม่ใช่สิ่งที่พิเศษเป็นพิเศษ ส่วนที่น่าสนใจคือเราสามารถใช้การแก้ไขวิธีสั่งซื้อยังจำลองการอ้างอิงในระหว่างการทดสอบหน่วย โค้ดด้านล่างนี้สร้างสิ่งMockUserServiceที่สืบทอดมาUserServiceและนำเสนอวิธีการที่เราต้องการจำลอง validate_credentialsในตัวอย่างด้านล่างเราให้การดำเนินการของ เพื่อที่จะได้MockUserServiceจัดการกับการโทรใด ๆ ที่validate_credentialsเราจำเป็นต้องวางไว้ก่อนUserServiceใน MRO นี้ทำได้โดยการสร้างชั้นห่อหุ้มรอบUserเรียกว่าMockUserและมีมันสืบทอดมาจากและUserMockUserService

ตอนนี้เมื่อเราทำMockUser.authenticateและในทางกลับกันการเรียกใช้มาsuper().validate_credentials() MockUserServiceก่อนUserServiceในการสั่งซื้อวิธีการแก้ปัญหาและเนื่องจากมีการใช้งานที่เป็นรูปธรรมของvalidate_credentialsการดำเนินการนี้จะถูกนำมาใช้ Yay - เราประสบความสำเร็จUserServiceในการทดสอบหน่วยการเรียนรู้ของเรา พิจารณาว่าUserServiceอาจทำการโทรผ่านเครือข่ายหรือฐานข้อมูลราคาแพง - เราเพิ่งลบปัจจัยแฝงของสิ่งนี้ นอกจากนี้ยังไม่มีความเสี่ยงในการUserServiceสัมผัสข้อมูลสด / กระทุ้ง

class LoggingService(object):
    """
    Just a contrived logging class for demonstration purposes
    """
    def log_error(self, error):
        pass


class UserService(object):
    """
    Provide a method to authenticate the user by performing some expensive DB or network operation.
    """
    def validate_credentials(self, username, password):
        print('> UserService::validate_credentials')
        return username == 'iainjames88' and password == 'secret'


class User(LoggingService, UserService):
    """
    A User model class for demonstration purposes. In production, this code authenticates user credentials by calling
    super().validate_credentials and having the MRO resolve which class should handle this call.
    """
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def authenticate(self):
        if super().validate_credentials(self.username, self.password):
            return True
        super().log_error('Incorrect username/password combination')
        return False

class MockUserService(UserService):
    """
    Provide an implementation for validate_credentials() method. Now, calls from super() stop here when part of MRO.
    """
    def validate_credentials(self, username, password):
        print('> MockUserService::validate_credentials')
        return True


class MockUser(User, MockUserService):
    """
    A wrapper class around User to change it's MRO so that MockUserService is injected before UserService.
    """
    pass

if __name__ == '__main__':
    # Normal useage of the User class which uses UserService to resolve super().validate_credentials() calls.
    user = User('iainjames88', 'secret')
    print(user.authenticate())

    # Use the wrapper class MockUser which positions the MockUserService before UserService in the MRO. Since the class
    # MockUserService provides an implementation for validate_credentials() calls to super().validate_credentials() from
    # MockUser class will be resolved by MockUserService and not passed to the next in line.
    mock_user = MockUser('iainjames88', 'secret')
    print(mock_user.authenticate())

สิ่งนี้ให้ความรู้สึกที่ค่อนข้างฉลาด แต่นี่เป็นการใช้งานที่ดีและถูกต้องของการรับมรดกที่หลากหลายและการสั่งซื้อวิธีการแก้ปัญหาของ Python หรือไม่? เมื่อฉันคิดเกี่ยวกับการรับมรดกในทางที่ผมได้เรียนรู้ OOP กับ Java รู้สึกนี้ผิดอย่างสมบูรณ์เพราะเราไม่สามารถพูดได้Userเป็นUserServiceหรือเป็นUser LoggingServiceเมื่อคิดอย่างนั้นการใช้การสืบทอดวิธีที่โค้ดด้านบนใช้นั้นไม่สมเหตุสมผลเลย หรือมันคืออะไร? ถ้าเราใช้การถ่ายทอดทางพันธุกรรมเพียงอย่างเดียวเพื่อให้นำรหัสมาใช้ใหม่และไม่คิดในแง่ของความสัมพันธ์ระหว่างผู้ปกครอง -> เด็กสิ่งนี้ก็ไม่ได้เลวร้ายนัก

ฉันทำผิดหรือเปล่า?


ดูเหมือนว่ามีคำถามที่แตกต่างกันสองข้อที่นี่: "การจัดการ MRO แบบนี้ปลอดภัยหรือไม่?" และ "ไม่ถูกต้องหรือไม่ที่จะบอกว่าการสืบทอดของ Python ทำให้เกิดความสัมพันธ์แบบ" is-a "? คุณพยายามถามทั้งสองอย่างหรือแค่หนึ่งข้อ? (พวกเขากำลังคำถามทั้งดีเพียงแค่ต้องการให้แน่ใจว่าเราจะตอบหนึ่งที่เหมาะสมหรือแยกนี้เป็นสองคำถามถ้าคุณไม่ต้องการให้ทั้งสอง)
Ixrec

ฉันได้ตอบคำถามในขณะที่อ่านแล้วฉันไม่ได้ทำอะไรเลยใช่ไหม
Aaron Hall

@lxrec ฉันคิดว่าคุณพูดถูก ฉันพยายามถามคำถามสองคำถาม ฉันคิดว่าเหตุผลที่ไม่รู้สึก "ถูก" คือเพราะฉันกำลังคิดถึง "รูปแบบ" ของการสืบทอด (ดังนั้น GoldenRetriever "จึงเป็น" สุนัขและสุนัข "เป็น" สัตว์ ") แทนที่จะเป็นประเภทนี้ วิธีการจัดองค์ประกอบ ฉันคิดว่านี่เป็นสิ่งที่ฉันสามารถเปิดคำถามอีกครั้งสำหรับ :)
46411 Iain

สิ่งนี้ทำให้ฉันสับสนอย่างมาก หากการแต่งเพลงนั้นดีกว่าการรับมรดกทำไมไม่ผ่านอินสแตนซ์ของ LoggingService และ UserService ไปยังผู้สร้างของผู้ใช้และตั้งเป็นสมาชิก? จากนั้นคุณสามารถใช้การพิมพ์เป็ดสำหรับการฉีดพึ่งพาและส่งตัวอย่างของ MockUserService ไปยังตัวสร้างผู้ใช้แทน เหตุใดการใช้ super เพื่อ DI ที่ดีกว่า
Jake Spracher

คำตอบ:


7

การใช้คำสั่งการแก้ไขวิธีการของ Python สำหรับการฉีดที่ต้องพึ่งพา - นี่มันแย่ไหม?

ไม่นี่คือการใช้งานตามวัตถุประสงค์เชิงทฤษฎีของอัลกอริธึมเชิงเส้น C3 สิ่งนี้ขัดแย้งกับความสัมพันธ์ที่คุณคุ้นเคย แต่บางคนคิดว่าองค์ประกอบที่ควรจะเป็นในการสืบทอด ในกรณีนี้คุณแต่งความสัมพันธ์แบบมี ดูเหมือนว่าคุณกำลังอยู่ในเส้นทางที่ถูกต้อง (แม้ว่า Python มีโมดูลการบันทึกดังนั้นความหมายนั้นค่อนข้างน่าสงสัย แต่เป็นแบบฝึกหัดทางวิชาการ

ฉันไม่คิดว่าการเยาะเย้ยหรือการปะติดลิงเป็นสิ่งที่ไม่ดี แต่ถ้าคุณสามารถหลีกเลี่ยงพวกมันด้วยวิธีนี้ดีสำหรับคุณ - ด้วยความซับซ้อนที่เป็นที่ยอมรับมากขึ้นคุณหลีกเลี่ยงการแก้ไขคำจำกัดความของคลาสการผลิต

ฉันทำผิดหรือเปล่า?

มันดูดี. คุณได้แทนที่วิธีการที่อาจมีราคาแพงโดยไม่ใช้การปะแก้ลิงหรือการใช้แผ่นปะจำลองซึ่งหมายความว่าคุณยังไม่ได้แก้ไขคำจำกัดความของคลาสการผลิตโดยตรง

หากเจตนาคือการใช้งานฟังก์ชันที่ไม่มีข้อมูลประจำตัวจริงในการทดสอบคุณควรทำดังนี้:

>>> print(MockUser('foo', 'bar').authenticate())
> MockUserService::validate_credentials
True

แทนที่จะใช้ข้อมูลประจำตัวจริงของคุณและตรวจสอบว่าได้รับพารามิเตอร์อย่างถูกต้องอาจมีการยืนยัน (เนื่องจากนี่คือรหัสทดสอบหลังจากทั้งหมด):

def validate_credentials(self, username, password):
    print('> MockUserService::validate_credentials')
    assert username_ok(username), 'username expected to be ok'
    assert password_ok(password), 'password expected to be ok'
    return True

มิฉะนั้นดูเหมือนว่าคุณจะคิดออก คุณสามารถตรวจสอบ MRO ดังนี้:

>>> MockUser.mro()
[<class '__main__.MockUser'>, 
 <class '__main__.User'>, 
 <class '__main__.LoggingService'>, 
 <class '__main__.MockUserService'>, 
 <class '__main__.UserService'>, 
 <class 'object'>]

และคุณสามารถตรวจสอบว่ามีความสำคัญเหนือMockUserServiceUserService

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.