Eu preciso desenvolver uma API, as funções da API são solicitações que chamam o serviço exposto por um servidor.
Inicialmente, a API funcionava assim:
class Server:
def firstRequest(self, arg1, arg2):
# block of code A
async = Async()
async.callFirstRequest(arg1, arg2)
# block of code B
def secondRequest(self, argA, argB, argC):
# block of code A (identical to that of firstRequest)
async = Async()
async.callSecondRequest(argA, argB, argC)
# block of code B (identical to that of firstRequest)
class Async:
def callFirstRequest(self, arg1, arg2):
doFirstRequest(arg1, arg2)
# run the real request and wait for the answer
def doFirstRequest(self, arg1, arg2):
response = client.firstRequest(arg1, arg2)
def callSecondRequest(self, argA, argB, argC):
doSecondRequest(argA, argB, argC)
# run the real request and wait for the answer
def doSecondRequest(self, argA, argB, argC):
response = client.secondRequest(argA, argB, argC)
server = Server()
server.firstRequest(arg1=1, arg2=2)
server.secondRequest(argA='A', argB='B', argC='C')
Havia muito código duplicado e eu não gostei da maneira como ele passou os argumentos para a solicitação. Como existem muitos argumentos, eu queria extraí-los da solicitação e tornar algo mais paramétrico.
Então refatorei desta maneira:
# using a strategy pattern I was able to remove the duplication of code A and code B
# now send() receive and invoke the request I wanna send
class Server:
def send(self, sendRequest):
# block of code A
asynch = Async()
sendRequest(asynch)
# block of code B
# Request contains all the requests and a list of the arguments used (requestInfo)
class Request:
# number and name of the arguments are not the same for all the requests
# this function take care of this and store the arguments in RequestInfo for later use
def setRequestInfo(self, **kwargs):
if kwargs is not None:
for key, value in kwargs.iteritems():
self.requestInfo[key] = value
def firstRequest(async)
async.doFirstRequest(self.requestInfo)
def secondRequest(async)
async.doSecondRequest(self.requestInfo)
# Async run the real request and wait for the answer
class Async:
def doFirstRequest(requestInfo):
response = client.firstRequest(requestInfo['arg1'], requestInfo['arg2'])
def doSecondRequest(requestInfo)
response = client.secondRequest(requestInfo['argA'], requestInfo['argB'], requestInfo['argC'])
server = Server()
request = Request()
request.setRequestInfo(arg1=1, arg2=2) # set of the arguments needed for the request
server.send(request.firstRequest)
request.setRequestInfo(argA='A', argB='B', argC='C')
server.send(request.secondRequest)
O padrão de estratégia funcionou, a duplicação é removida. Independentemente disso, eu tenho medo de ter coisas complicadas, especialmente no que diz respeito aos argumentos, não gosto da maneira como lida com eles, porque, quando olho para o código, não parece fácil e claro.
Então, eu queria saber se existe um padrão ou uma maneira melhor e mais clara de lidar com esse tipo de código API do lado do cliente.
Respostas:
Eu reconsideraria usando um dicionário (hash, mapa, qualquer que seja o seu idioma que chame um conjunto de pares chave / valor) para os argumentos. Fazer dessa maneira torna impossível para o compilador verificar se um chamador incluiu todos os valores necessários. Torna difícil para o desenvolvedor usá-lo para descobrir se eles têm todos os argumentos necessários. Torna fácil incluir acidentalmente algo que você não precisa e esquecer algo que você precisa. E você acaba tendo que colocar todos os valores no dicionário ao chamar e ter que verificar o dicionário em todas as funções para extrair todos os argumentos, aumentando a sobrecarga. O uso de algum tipo de estrutura especializada pode reduzir o número de argumentos sem reduzir a capacidade dos compiladores de verificá-los e a capacidade dos desenvolvedores de ver claramente o que é necessário.
fonte
Acho que a API do servidor deve ter tantas entradas quanto solicitações necessárias. Assim, qualquer desenvolvedor poderá ler a API facilmente ( veja o roteamento de lask como exemplo ).
Para evitar duplicação de código, você pode usar métodos internos
fonte
API, arquitetura e padrão têm tudo a ver com comunicação e intenção. Sua segunda versão parece bastante simples para mim e parece um pouco extensível também, mas minha opinião (ou até a sua) não é o que importa aqui.
Obter feedbacks
Veja o seu software de fora para dentro (não de dentro para fora). Se houver um site, comece a partir daí. Espere encontrar facilmente uma e apenas uma maneira óbvia de fazer o que seu software deve fazer. Encontre alguém no corredor e peça feedback sobre a usabilidade.
Identifique o principal objeto de negócios
Como a programação de software é sempre sobre criar algo novo a partir de duas coisas diferentes, ter um
Server
queRequest
pareça sensato para mim. OServer
provavelmente exigir configuração e tem padrões sensíveis. Você provavelmente fornece algo como um singleton ou uma fábrica para facilitar seu uso. O verdadeiro material interessante acontece noRequest
. Seus clientes precisam apenas criar oRequest
objeto adequado . Deixe sua intenção óbvia e sua empresa clara.Esteja aberto para extensão, mas fechado para modificação
Você pode simplificar ainda mais a codificação de comportamentos diferentes usando herança em vez de vários métodos públicos no
Request
objeto, como no padrão de comando . Dessa forma, os clientes também podem escrever suas próprias solicitações, e novas solicitações podem ser fornecidas por plug-ins (usando os pontos de entrada dos setuptools, por exemplo), se necessário. Isso também garantiria que oRequest
objeto nunca se tornasse uma classe god ou alterasse sua API se novos recursos fossem adicionados.fonte