Bağımlılık Enjeksiyonu için Python Yöntemi Çözüm Sırasını Kullanma - bu kötü mü?


11

Raymond Hettinger'in Pycon'unun "Süper Süper Düşünülmüş" konuşmasını izledim ve bir sınıf "ebeveyn" sınıfını belirleyici bir şekilde lineerleştiren Python'un MRO'sunu (Yöntem Çözünürlük Sırası) biraz öğrendim. Bağımlılık enjeksiyonu yapmak için, aşağıdaki kodda olduğu gibi bunu kendi yararımıza kullanabiliriz. Şimdi, doğal olarak, superher şey için kullanmak istiyorum !

Aşağıdaki örnekte Usersınıf, bağımlılıklarını her ikisinden LoggingServiceve UserService. Bu çok özel değil. İlginç olan, Yöntem Çözüm Siparişi'ni birim testi sırasında bağımlılıkları taklit edebilmemizdir. Aşağıdaki kod , alay etmek istediğimiz yöntemlerden MockUserServicemiras kalan UserServiceve bunların uygulanmasını sağlayan bir kod oluşturur . Aşağıdaki örnekte, bir uygulamasını sağlıyoruz validate_credentials. MockUserServiceHerhangi bir çağrıyı ele alabilmek için MRO'da validate_credentialsdaha önce konumlandırmamız gerekir UserService. Bu etrafında sarıcı sınıf oluşturarak yapılır Useradlandırılan MockUserve onu kalıt sahip Userve MockUserService.

Şimdi, bunu yaptığımızda MockUser.authenticateve bunun karşılığında, çağrılar Yöntem Çözüm Siparişinde super().validate_credentials() MockUserServicedaha önce UserServiceve validate_credentialsbu uygulamanın somut bir uygulamasını sunduğu için çağrılar kullanılacak. Yay - UserServiceBirim testlerimizde başarılı bir şekilde alay ettik . Bunun UserServicepahalı ağ veya veritabanı çağrıları yapabileceğini düşünün - bunun gecikme faktörünü henüz kaldırdık. UserServiceCanlı / ürün verilerine dokunma riski de yoktur .

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())

Bu oldukça zeki hissettiriyor, ancak bu Python'un çoklu kalıtım ve Yöntem Çözüm Emri'nin iyi ve geçerli bir kullanımı mı? Ben Java ile OOP öğrenilen bu şekilde miras düşündüğümüzde biz diyemeyiz çünkü tamamen yanlış bu hissettiğini Userbir olduğunu UserServiceveya Userbir olduğunu LoggingService. Bu şekilde düşünmek, mirasın yukarıdaki kodun kullandığı şekilde kullanılması pek mantıklı değildir. Yoksa öyle mi? Devralmayı yalnızca kodun yeniden kullanılmasını sağlamak ve ebeveyn- çocuk ilişkileri açısından düşünmemek için kullanırsak, bu o kadar da kötü görünmez.

Yanlış mı yapıyorum?


Burada iki farklı soru var gibi görünüyor: "Bu tür MRO manipülasyonu güvenli / kararlı mı?" ve "Python kalıtım modellerinin" is-a "ilişkisi olduğunu söylemek yanlış mı? Her ikisini de mi, yoksa sadece birini mi sormaya çalışıyorsunuz? (her ikisi de iyi sorular, sadece doğru olanı cevapladığımızdan emin olmak ya da her ikisini de istemiyorsanız bunu iki soruya bölmek istiyoruz)
Ixrec

Soruları okurken giderdim, bir şey bıraktım mı?
Aaron Hall

@lxrec Bence kesinlikle haklısın. İki farklı soru sormaya çalışıyorum. Sanırım bunun "doğru" hissetmemesinin sebebi, "is-a" kalıtım tarzını (yani GoldenRetriever "-" Köpek ve Köpek "-" Hayvan) " bileşimsel yaklaşım. Sanırım bu başka bir soru açabileceğim bir şey :)
Iain

Bu da beni oldukça karıştırıyor. Kompozisyon kalıtım için tercih edilirse, neden LoggingService ve UserService örneklerini Kullanıcı yapıcısına iletip üye olarak ayarlamıyorsunuz? Daha sonra bağımlılık enjeksiyonu için ördek yazmayı kullanabilir ve bunun yerine MockUserService örneğini Kullanıcı yapıcısına iletebilirsiniz. Süper DI için neden tercih edilir?
Jake Spracher

Yanıtlar:


7

Bağımlılık Enjeksiyonu için Python Yöntemi Çözüm Sırasını Kullanma - bu kötü mü?

Hayır. Bu C3 doğrusallaştırma algoritmasının teorik olarak amaçlanan kullanımıdır. Bu sizin bildiğiniz ilişkilere aykırıdır, ancak bazıları kompozisyonun miras olarak tercih edildiğini düşünür. Bu durumda, bazı has-a ilişkileri oluşturdunuz. Görünüşe göre doğru yoldasınız (Python'un bir günlükleme modülü olmasına rağmen, anlambilim biraz tartışmalıdır, ancak akademik bir egzersiz olarak mükemmel bir şekilde iyidir).

Alaycı ya da maymun yamasının kötü bir şey olduğunu düşünmüyorum, ancak bu yöntemle onlardan kaçınabiliyorsanız, sizin için iyi - kuşkusuz daha karmaşık bir şekilde, üretim sınıfı tanımlarını değiştirmekten kaçındınız.

Yanlış mı yapıyorum?

Güzel görünüyor. Maymun yaması olmadan veya sahte bir yama kullanmadan potansiyel olarak pahalı bir yöntemi geçersiz kıldınız, bu da yine üretim sınıfı tanımlarını doğrudan değiştirmediğiniz anlamına geliyor.

Amaç, testte gerçekten kimlik bilgileri olmadan işlevselliği kullanmak olsaydı, muhtemelen şöyle bir şey yapmalısınız:

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

gerçek kimlik bilgilerinizi kullanmak yerine ve parametrelerin belki de iddialarla doğru bir şekilde alındığını kontrol edin (sonuçta bu test kodu olduğu için):

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

Aksi takdirde, çözdüğünüz anlaşılıyor. MRO'yu şu şekilde doğrulayabilirsiniz:

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

Ve MockUserServiceüzerinde önceliğe sahip olduğunu doğrulayabilirsiniz UserService.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.