Decoradores contra herencia

¿Cómo se decide entre usar decoradores y la herencia cuando ambos son posibles?

Por ejemplo, este problema tiene dos soluciones.

Estoy particularmente interesado en Python.

Decoradores …:

  • … debe utilizarse si lo que intenta hacer es “envolver”. La envoltura consiste en tomar algo, modificarlo (o registrarlo con algo) y / o devolver un objeto proxy que se comporte “casi exactamente” como el original.
  • … están bien para aplicar un comportamiento similar a la mezcla, siempre y cuando no esté creando una gran stack de objetos proxy.
  • … tiene una abstracción implícita de “stack”:

p.ej

 @decoA @decoB @decoC def myFunc(...): ... ... 

Es equivalente a:

 def myFunc(...): ... ... myFunc = decoA(decoB(decoC(myFunc))) #note the *ordering* 

Herencia múltiple …:

  • … es mejor para agregar métodos a las clases; No puedes usarlo para decorar funciones fácilmente. En este contexto, se puede utilizar para lograr un comportamiento similar a la mezcla si todo lo que necesita es un conjunto de métodos adicionales de “estilo de escritura de pato”.
  • … puede ser un poco difícil de manejar si su problema no es una buena combinación para él, con problemas con los constructores de superclase, etc. Por ejemplo, el método de subclases __init__ no se llamará a menos que se llame explícitamente (a través del método-resolution- protocolo de pedido)!

En resumen, usaría decoradores para comportamientos similares a la mezcla si no devolvieran objetos proxy. Algunos ejemplos incluirían cualquier decorador que devuelva la función original, ligeramente modificada (o después de registrarla en algún lugar o agregarla a alguna colección).

Las cosas para las que a menudo encontrará decoradores (como la memorización) también son buenos candidatos, pero deben usarse con moderación si devuelven objetos proxy; El orden en que se aplican la materia. Y demasiados decoradores uno encima del otro los están usando de una manera que no están destinados a ser usados.

Consideraría el uso de la herencia si se tratara de un “problema de herencia clásico”, o si todo lo que necesitaba para el comportamiento mixto fueran métodos. Un problema de herencia clásico es uno en el que puede usar el hijo donde quiera que pueda usar el padre.

En general, trato de escribir código donde no es necesario mejorar cosas arbitrarias.

El problema al que hace referencia no es decidir entre los decoradores y las clases. Está usando decoradores, pero tienes la opción de usar:

  • Un decorador, que devuelve una clase.
  • Un decorador, que devuelve una función.

Un decorador es solo un nombre elegante para el patrón de “envoltura”, es decir, reemplazar algo con otra cosa. La implementación depende de usted (clase o función).

Al decidir entre ellos, es completamente una cuestión de preferencia personal. Puedes hacer todo lo que puedes hacer en uno con el otro.

  • Si está decorando una función, puede que prefiera decoradores que devuelven funciones proxy.
  • Si está decorando una clase, puede preferir decoradores que devuelven clases proxy.

(¿Por qué es una buena idea? Puede haber suposiciones de que una función decorada sigue siendo una función, y una clase decorada sigue siendo una clase).

Aún mejor en ambos casos sería usar un decorador que simplemente devuelva el original, modificado de alguna manera.

edición: después de comprender mejor su pregunta, he publicado otra solución en Python functools.wraps equivalente para las clases

Si ambos son equivalentes, preferiría a los decoradores, ya que puede usar el mismo decorador para muchas clases, mientras que la herencia se aplica solo a una clase específica.

Personalmente, pensaría en términos de reutilización de código. El decorador es a veces más flexible que la herencia.

Tomemos como ejemplo el almacenamiento en caché. Si desea agregar la facilidad de almacenamiento en caché a dos clases en su sistema: A y B, con herencia, probablemente terminará teniendo un Cache y un Caché. Y al reemplazar algunos de los métodos en estas clases, probablemente duplicará muchos códigos para la misma lógica de almacenamiento en caché. Pero si usa decorador en este caso, solo necesita definir un decorador para decorar ambas clases.

Por lo tanto, cuando decida cuál usar, es posible que primero desee verificar si la funcionalidad extendida es solo específica para esta clase o si la misma funcionalidad extendida puede reutilizarse en otras partes de su sistema. Si no se puede reutilizar, la herencia probablemente debería hacer el trabajo. De lo contrario, puedes pensar en usar decorador.