Problema
Estoy trabajando en un proyecto de Python cuya clase principal es un poco " God Object ". ¡Hay tantos malditos atributos y métodos!
Quiero refactorizar la clase.
Hasta aquí…
Para el primer paso, quiero hacer algo relativamente simple; pero cuando probé el enfoque más directo, rompió algunas pruebas y ejemplos existentes.
Básicamente, la clase tiene una lista muuuucha larga de atributos, pero puedo verlos claramente y pensar: "Estos 5 atributos están relacionados ... Estos 8 también están relacionados ... y luego está el resto".
getattr
Básicamente, solo quería agrupar los atributos relacionados en una clase auxiliar tipo dict. Tenía la sensación de __getattr__
que sería ideal para el trabajo. Así que moví los atributos a una clase separada y, efectivamente, __getattr__
trabajé su magia perfectamente bien ...
En primer lugar .
Pero luego intenté ejecutar uno de los ejemplos. La subclase de ejemplo intenta establecer uno de estos atributos directamente (a nivel de clase ). Pero como el atributo ya no estaba "físicamente ubicado" en la clase principal, recibí un error que decía que el atributo no existía.
@propiedad
Luego leí sobre el @property
decorador. Pero también leí que crea problemas para las subclases que quieren hacer self.x = blah
cuando x
es una propiedad de la clase principal.
Deseado
- Haga que todo el código del cliente continúe funcionando
self.whatever
, incluso si lawhatever
propiedad del padre no está "físicamente ubicada" en la clase (o instancia) en sí misma. - Agrupe atributos relacionados en contenedores tipo dict.
- Reduzca el ruido extremo del código en la clase principal.
Por ejemplo, no quiero simplemente cambiar esto:
larry = 2
curly = 'abcd'
moe = self.doh()
Dentro de esto:
larry = something_else('larry')
curly = something_else('curly')
moe = yet_another_thing.moe()
... porque eso sigue siendo ruidoso. Aunque eso convierte con éxito un atributo simple en algo que puede administrar los datos, el original tenía 3 variables y la versión modificada todavía tiene 3 variables.
Sin embargo, estaría bien con algo como esto:
stooges = Stooges()
Y si una búsqueda self.larry
falla, algo verificaría stooges
y vería si larry
está allí. (Pero también debe funcionar si una subclase intenta hacerlo larry = 'blah'
a nivel de clase).
Resumen
- Desea reemplazar grupos de atributos relacionados en una clase principal con un solo atributo que almacena todos los datos en otro lugar
- Quiere trabajar con el código de cliente existente que usa (por ejemplo)
larry = 'blah'
a nivel de clase - Desea continuar permitiendo que las subclases extiendan, anulen y modifiquen estos atributos refactorizados sin saber que algo ha cambiado.
es posible? ¿O estoy ladrando el árbol equivocado?
fuente
Respuestas:
Habiendo escrito y luego refactorizado un "objeto de Dios" de Python, simpatizo. Lo que hice fue dividir el objeto original en subsecciones basadas en métodos. Por ejemplo, el original se parecía a este pseudocódigo:
El método de relleno es una "unidad" de trabajo autónoma. Lo migré a una nueva clase que la instancia original. Esto sacó las propiedades necesarias también. Algunos fueron utilizados solo por la subclase y podían moverse en línea recta. Otros fueron compartidos y se trasladaron a una clase compartida.
El "objeto de Dios" crea una nueva copia de la clase compartida al inicio, y cada una de las nuevas subclases acepta un puntero como parte de su método init. Por ejemplo, aquí hay una versión despojada del anuncio publicitario:
Se crea una vez y se comparte entre las diferentes clases que necesitan capacidades de correo.
Entonces, para usted, cree una clase
larry
con las propiedades y métodos que necesita. En todas partes el cliente dicelarry = blah
reemplazarlo conlarryObj.larry = blah
. Esto migra cosas a subproyectos sin romper la interfaz actual.La única otra cosa que hacer es buscar "unidades de trabajo". Si iba a convertir parte del "Objeto de Dios" en su propio método, hágalo . Pero, pon el método fuera de él. Esto te obliga a crear una interfaz entre los componentes.
Colocar esa base permite que todo lo demás lo siga. Por ejemplo, una parte del objeto auxiliar que demuestra cómo interactúa con el programa de correo:
Concéntrese en la unidad individual de trabajo más pequeña posible y muévala. Esto es más fácil de hacer y te permite jugar con la configuración rápidamente. No mire las propiedades para mover cosas, son auxiliares de las tareas que se realizan con ellos en la mayoría de los casos. Lo que quede después de que haya tratado con los métodos probablemente debería permanecer en el objeto original, ya que es parte del estado compartido.
Pero , los nuevos objetos ahora deberían aceptar las propiedades que necesitan como variables de inicio, sin tocar la propiedad de los objetos que llaman. Luego devuelven los valores necesarios, que la persona que llama puede usar para actualizar las propiedades compartidas según sea necesario. Esto ayuda a desacoplar los objetos y crea un sistema más robusto.
fuente