Usemos las palabras interfaz, contrato o API, nos estaremos refieriendo a lo mismo. De hecho, API es el acrónimo para Application Programming Interface o interfaz de programación de aplicationes.
La interfaz es el conjunto de métodos que una clase expone para el control de su funcionalidad.
Una interfaz puede verse como un contrato entre el usuario de un software (el cliente o consumidor) y el software. Establece qué precondiciones debe asegurar el cliente antes de utilizar una funcionalidad, qué postcondiciones puede asumir tras la ejecución de la funcionalidad y cuáles son los invariantes.
-
Considera el siguiente código:
class HTTPRequest: def __init__(self): self.url = None self._state = 'NEW' def open(self, url): self.url = url print(f'Connecting to {url}') self._state = 'OPEN' def send(self, method): print(f'Sending {method.upper()}') self._state = 'SENT' if __name__ == '__main__': request = HTTPRequest() request.open('http://test.com') request.send('post')
¿Qué precondiciones debemos cumplir antes de usar el método
send
? ¿Qué podemos asumir tras usarlo? ¿Qué no ha cambiado? -
Modifica el programa para incluir la siguientes comprobaciones en tiempo de ejecución:
class HTTPRequest: def __init__(self): self.url = None self._state = 'NEW' def open(self, url): assert self._state == 'NEW', 'state of the request is invalid' self.url = url print(f'Connecting to {url}') self._state = 'OPEN' def send(self, method): assert self._state == 'OPEN', 'state of the request is invalid' print(f'Sending {method.upper()}') self._state = 'SENT' if __name__ == '__main__': request = HTTPRequest() request.open('http://test.com') request.send('post')
¿Qué ocurre ahora si ejecutas un método cuando no debes? ¿Qué pasa si pasas un método que no es una cadena (por ejemplo, un número)?
-
¿Cómo se te ocurre que se podría cambiar el método
send
para fallar de manera controlada si pasamos un método que no es uno de los valores'get'
o'post'
?
Python posee una sintaxis que permiten añadir anotaciones a tus funciones, de forma que podemos modelar ciertas relaciones entre entidades. Conoceremos más sobre estas anotaciones cuando alcancemos el tema sobre tipado progresivo.
-
Considera el siguiente código anotado:
class HTTPRequest: def __init__(self): self.url: str = None self._state = 'NEW' def open(self, url: str) -> None: assert self._state == 'NEW', 'state of the request is invalid' self.url = url print(f'Connecting to {url}') self._state = 'OPEN' def send(self, method: str) -> None: assert self._state == 'OPEN', 'state of the request is invalid' accepted = ['GET', 'POST'] if method.upper() not in accepted: raise RuntimeError(f'method must be one of {accepted}') print(f'Sending {method.upper()}') self._state = 'SENT' if __name__ == '__main__': request = HTTPRequest() request.open('http://test.com') request.send(10000)
¿Qué pasa si pegas este código en PyCharm? ¿Qué pasa si ejecutas el código?
El principal impulsor del diseño basado en contratos es Bertrand Meyer, quien también acuñó el término y lo integró fuertemente en su lenguaje de programación Eiffel. Algunos recursos de interés sobre el diseño de APIs: