Oi Habr. Recentemente, fiquei louco por modificadores de acesso a design e interfaces, depois o transportei para a linguagem de programação Python. Eu pergunto no kat - eu compartilho os resultados e como ele funciona. Para os interessados, no final do artigo, há um link para o projeto no Github.
Modificadores de acesso
Modificadores de acesso restringem o acesso a objetos - a métodos de sua classe ou a classes filho - a métodos de sua classe pai. O uso de modificadores de acesso ajuda a ocultar dados na classe para que ninguém de fora possa interferir no trabalho dessa classe.
métodos
privados estão disponíveis apenas dentro da classe,
protected (inside) - dentro da classe e nas classes filho.
Como métodos privados e protegidos são implementados no Python
Spoiler - no nível do acordo que os adultos simplesmente não os chamarão fora da sala de aula. Antes dos métodos privados, você precisa escrever um sublinhado duplo antes de proteger. E você ainda pode acessar os métodos, apesar do acesso "limitado".
class Car: def _start_engine(self): return "Engine's sound." def run(self): return self._start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() assert "Engine's sound." == car._start_engine()
As seguintes desvantagens podem ser determinadas:
- Se o método _start_engine atualizou algumas variáveis de classe ou manteve o estado, e não apenas retornou um “cálculo burro”, você poderia ter quebrado algo para trabalhos futuros com a classe. Você não se permite reparar algo no motor do seu carro, porque não vai a lugar algum, certo?
- O ponto de fluxo do anterior - para garantir que você possa "com segurança" (chamar o método não prejudique a própria classe) usar um método protegido - é necessário examinar seu código e gastar tempo.
- Os autores das bibliotecas esperam que ninguém use os métodos protegidos e privados das classes que você usa em seus projetos. Portanto, eles podem alterar sua implementação em qualquer versão (que não afetará os métodos públicos devido à compatibilidade com versões anteriores, mas você sofrerá).
- O autor da classe, seu colega, espera que você não aumente a dívida técnica do projeto usando um método protegido ou privado fora da classe que ele criou. Afinal, quem irá refatorá-lo ou modificá-lo (método de classe privada) terá que garantir (por exemplo, através de testes) que as alterações dele não quebrem seu código. E se eles quebrarem, ele terá que gastar tempo tentando resolver esse problema (com uma muleta, porque ele precisa ontem).
- Talvez você tenha certeza de que outros programadores não usem métodos privados ou protegidos na revisão de código e “vença por isso”; portanto, gaste tempo.
Como implementar métodos protegidos usando uma biblioteca
from accessify import protected class Car: @protected def start_engine(self): return "Engine's sound." def run(self): return self.start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() car.start_engine()
Tentar chamar o método
start_engine fora da classe resultará no seguinte erro (o método não está disponível de acordo com a política de acesso):
Traceback (most recent call last): File "examples/access/private.py", line 24, in <module> car.start_engine() File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/main.py", line 92, in private_wrapper class_name=instance_class.__name__, method_name=method.__name__, accessify.errors.InaccessibleDueToItsProtectionLevelException: Car.start_engine() is inaccessible due to its protection level
Usando a biblioteca:
- Você não precisa usar sublinhados feios (subjetivos) ou sublinhados duplos.
- Você obtém um belo método (subjetivo) para implementar modificadores de acesso no código - decoradores privados e protegidos .
- Transferir a responsabilidade de pessoa para intérprete.
Como funciona:
- O decorador privado ou protegido - o decorador mais "alto", é acionado antes do método de classe , que foi declarado um modificador de acesso privado ou protegido.
- Usando a biblioteca de inspeção interna , o decorador recupera o objeto atual da pilha de chamadas - inspect.currentframe () . Este objeto possui os seguintes atributos que são úteis para nós: o espaço para nome (locais) e o link para o objeto anterior da pilha de chamadas (o objeto que chama o método com o modificador de acesso).
(Ilustração muito simplificada)
- inspect.currentframe (). f_back - use este atributo para verificar se o objeto anterior da pilha de chamadas está no corpo da classe ou não. Para fazer isso, observe o espaço para nome - f_locals . Se o atributo próprio estiver no espaço para nome, o método será chamado dentro da classe, se não, fora da classe. Se você chamar um método com um modificador de acesso privado ou protegido fora da classe, haverá um erro na política de acesso.
Interfaces
Interfaces são um contrato de interação com uma classe que a implementa. A interface contém as assinaturas do método (o nome das funções, os argumentos de entrada) e a classe que implementa a interface, seguindo as assinaturas, implementa a lógica. Resumindo, se duas classes implementam a mesma interface, você pode ter certeza de que os dois objetos dessas classes têm os mesmos métodos.
Exemplo
Temos uma classe de
usuário que usa o objeto de
armazenamento para criar um novo usuário.
class User: def __init__(self, storage): self.storage = storage def create(self, name): return storage.create_with_name(name=name)
Você pode salvar o usuário no banco de dados usando
DatabaseStorage.create_with_name .
class DatabaseStorage: def create_with_name(self, name): ...
Você pode salvar o usuário em arquivos usando
FileStorage.create_with_name .
class FileStorage: def create_with_name(self, name): ...
Devido ao fato de as assinaturas dos métodos
create_with_name (nome, argumentos) serem as mesmas para as classes - a classe
User não precisa se preocupar com qual objeto foi substituído se ambos tiverem os mesmos métodos. Isso pode ser alcançado se as classes
FileStorage e
DatabaseStorage implementarem a mesma interface (ou seja, elas estão vinculadas pelo contrato para definir algum método com lógica interna).
if __name__ == '__main__': if settings.config.storage = FileStorage: storage = FileStorage() if settings.config.storage = DatabaseStorage: storage = DatabaseStorage() user = User(storage=storage) user.create_with_name(name=...)
Como trabalhar com interfaces usando a biblioteca
Se uma classe implementa uma interface, ela deve conter
todos os métodos da interface . No exemplo abaixo, a interface HumanInterface contém o método eat, e a classe Human o implementa, mas não implementa o método eat.
from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: pass
O script será encerrado com o seguinte erro:
Traceback (most recent call last): File "examples/interfaces/single.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanInterface.eat(food, args, allergy, kwargs)
Se uma classe implementa uma interface, ela deve conter todos os métodos da interface,
incluindo todos os argumentos recebidos . No exemplo abaixo, a interface HumanInterface contém o método eat, que leva 4 argumentos para a entrada, e a classe Human o implementa, mas implementa o método eat com apenas 1 argumento.
from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food): pass
O script será encerrado com o seguinte erro:
Traceback (most recent call last): File "examples/interfaces/single_arguments.py", line 16, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 87, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedWithMismatchedArgumentsException: class Human implements interface member HumanInterface.eat(food, args, allergy, kwargs) with mismatched arguments
Se uma classe implementa uma interface, ela deve conter todos os métodos da interface, incluindo argumentos recebidos e
modificadores de acesso . No exemplo abaixo, a interface HumanInterface contém o método eat privado, e a classe Human o implementa, mas não implementa o modificador de acesso privado para o método eat.
from accessify import implements, private class HumanInterface: @private @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass
O script será encerrado com o seguinte erro:
Traceback (most recent call last): File "examples/interfaces/single_access.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 77, in decorator interface_method_name=interface_method.name, accessify.errors.ImplementedInterfaceMemberHasIncorrectAccessModifierException: Human.eat(food, args, allergy, kwargs) mismatches HumanInterface.eat() member access modifier.
Uma classe pode implementar várias interfaces (número ilimitado de). Se uma classe implementa várias interfaces, ela deve conter
todos os métodos de todas as interfaces, incluindo argumentos recebidos e modificadores de acesso . No exemplo abaixo, a classe Human implementa o método eat da interface HumanBasicsInterface, mas não implementa o método de amor da interface HumanSoulInterface.
from accessify import implements class HumanSoulInterface: def love(self, who, *args, **kwargs): pass class HumanBasicsInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanSoulInterface, HumanBasicsInterface) class Human: def love(self, who, *args, **kwargs): pass
O script será encerrado com o seguinte erro:
Traceback (most recent call last): File "examples/interfaces/multiple.py", line 19, in <module> @implements(HumanSoulInterface, HumanBasicsInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanBasicsInterface.eat(food, args, allergy, kwargs)
Recurso assassino - um método de interface pode "declarar" quais erros um método de uma classe que implementa deve "lançar". No exemplo abaixo, é "declarado" que o método "love" da interface "HumanInterface" deve lançar uma exceção "HumanDoesNotExistError" e
“HumanAlreadyInLoveError”, mas o método “love” da classe “Human” não lança um deles.
from accessify import implements, throws class HumanDoesNotExistError(Exception): pass class HumanAlreadyInLoveError(Exception): pass class HumanInterface: @throws(HumanDoesNotExistError, HumanAlreadyInLoveError) def love(self, who, *args, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: def love(self, who, *args, **kwargs): if who is None: raise HumanDoesNotExistError('Human whom need to love does not exist')
O script será encerrado com o seguinte erro:
Traceback (most recent call last): File "examples/interfaces/throws.py", line 21, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 103, in decorator class_method_arguments=class_member.arguments_as_string, accessify.errors.DeclaredInterfaceExceptionHasNotBeenImplementedException: Declared exception HumanAlreadyInLoveError by HumanInterface.love() member has not been implemented by Human.love(self, who, args, kwargs)
Para resumir, usando a biblioteca:
- Você pode implementar uma ou mais interfaces.
- As interfaces são combinadas com modificadores de acesso.
- Você obterá uma separação de interfaces e classes abstratas ( módulo abc em Python ), agora você não precisa usar classes abstratas como interfaces, se tiver (eu fiz).
- Comparado às classes abstratas. Se você não definiu todos os argumentos do método a partir da interface, receberá um erro usando uma classe abstrata - no.
- Comparado às classes abstratas. Usando interfaces, você receberá um erro ao criar a classe (quando você escreveu a classe e chamou o arquivo * .py ). Nas classes abstratas, você já receberá um erro no estágio de chamar um método de um objeto de classe.
Como funciona:
- Usando a biblioteca de inspeção interna no decorador de implementos , todos os métodos da classe e suas interfaces - inspect.getmembers () são obtidos . Um índice exclusivo de um método é uma combinação de seu nome e tipo (método estático, propriedade e assim por diante).
- E com inspect.signature () , os argumentos para o método
- Percorremos todos os métodos da interface e verificamos se existe um método (por um índice exclusivo) na classe que implementa a interface, se os argumentos recebidos são os mesmos, se os modificadores de acesso são os mesmos, se o método implementa os erros declarados no método da interface.
Obrigado por sua atenção ao artigo.
Link para o projeto no Github .