poo - polimorfismo python
¿Deberían los métodos de clase internos devolver valores o simplemente modificar las variables de instancia? (4)
Estoy creando una clase de generador de consultas que ayudará en la construcción de una consulta para mongodb a partir de parámetros de URL. Nunca he hecho mucha programación orientada a objetos, ni he diseñado clases para el consumo por parte de personas que no sean yo, además de usar construcciones de lenguaje básico y usar los modelos incorporados de django.
Así que tengo esta clase QueryBuilder
class QueryHelper():
"""
Help abstract out the problem of querying over vastly
different dataschemas.
"""
def __init__(self, collection_name, field_name, params_dict):
self.query_dict = {}
self.params_dict = params_dict
db = connection.get_db()
self.collection = db[collection_name]
def _build_query(self):
# check params dict and build a mongo query
pass
Ahora en _build_query
params_dict
el params_dict
y query_dict
para pasarlo a la función find()
mongo. Al hacer esto, me preguntaba si existía un enfoque absolutamente correcto para determinar si _build_query
debería devolver un diccionario o si solo debería modificar self.query_dict
. Dado que es un método interno, asumiría que está bien modificar solo self.query_dict
. ¿Hay una manera correcta (pythonic) de acercarse a esto? ¿Es esto una tontería y no es una decisión de diseño importante? Cualquier ayuda es apreciada.
Es preferible __init__
un valor, ya que le permite mantener todas las modificaciones de atributos en un solo lugar ( __init__
). Además, esto hace que sea más fácil extender el código más adelante; Supongamos que desea anular _build_query
en una subclase, entonces el método de anulación solo puede devolver un valor, sin necesidad de saber qué atributo establecer. Aquí hay un ejemplo:
class QueryHelper(object):
def __init__(self, param, text):
self._param = param
self._query = self._build_query(text)
def _build_query(self, text):
return text + " and ham!"
class RefinedQueryHelper(QueryHelper):
def _build_query(self, text):
# no need to know how the query object is going to be used
q = super(RefinedQueryHelper, self)._build_query()
return q.replace("ham", "spam")
vs. la "versión de setter":
class QueryHelper(object):
def __init__(self, param, text):
self._param = param
self._build_query(text)
def _build_query(self, text):
self._query = text + " and ham!"
class RefinedQueryHelper(QueryHelper):
def _build_query(self, text):
# what if we want to store the query in __query instead?
# then we need to modify two classes...
super(RefinedQueryHelper, self)._build_query()
self._query = self._query.replace("ham", "spam")
Si elige establecer un atributo, es posible que desee llamar al método _set_query
para mayor claridad.
Está perfectamente bien modificar self.query_dict
ya que la idea general de la programación orientada a objetos es que los métodos pueden modificar el estado de un objeto. Siempre que un objeto se encuentre en un estado coherente después de que un método haya finalizado, está bien. El hecho de que _build_query
sea un método interno no importa. Puede elegir llamar a _build_query
después en __init__
para construir la consulta ya cuando se crea el objeto.
La decisión en su mayoría importa para fines de prueba. Para propósitos de pruebas de piel, es conveniente probar cada método individualmente sin tener que probar necesariamente el estado de todo el objeto. Pero eso no se aplica en este caso porque estamos hablando de un método interno para que usted solo decida cuándo llamar a ese método, no a otros objetos u otro código.
Si bien es común que los métodos de un objeto modifiquen directamente su estado, a veces puede ser ventajoso que un objeto sea su propio "cliente", y acceder a ellos mismos de manera indirecta a través de métodos de acceso privados (típicamente).
Hacer esto proporciona una mejor encapsulation y los beneficios que se derivan de ello (el principal es el aislamiento de los detalles de la implementación). Sin embargo, hacerlo puede ser poco práctico porque requeriría demasiado código adicional y, a menudo, es más lento, lo que podría afectar negativamente al rendimiento, por lo que las compensaciones deben hacerse con el ideal.
Si devuelves algo en absoluto, te sugiero que lo hagas. Devolver self
desde métodos de instancia es conveniente para el encadenamiento de métodos, ya que cada valor devuelto permite otra llamada de método en el mismo objeto:
foo.add_thing(x).add_thing(y).set_goal(42).execute()
Esto se conoce a veces como una API "fluida".
Sin embargo, mientras Python permite el encadenamiento de métodos para tipos inmutables como int
y str
, no lo proporciona para los métodos de contenedores mutables tales como list
y set
por diseño, por lo que posiblemente no sea "Pythonic" hacerlo por su propio mutable. tipo. Sin embargo, muchas bibliotecas de Python tienen API "fluidas".
Un inconveniente es que tal API puede hacer que la depuración sea más difícil. Ya que ejecuta la declaración completa o ninguna de ellas, no puede ver fácilmente el objeto en puntos intermedios dentro de la declaración. Por supuesto, generalmente encuentro que la print
perfectamente adecuada para depurar el código Python, ¡así que simplemente lanzo una print
en cualquier método cuyo valor de devolución me interesara!