Curso de Python / Reutilizar código con herencia y composición

La herencia define una clase a partir de otra y la composición construye un objeto que utiliza otros objetos como colaboradores. En Python, conviene elegir según la relación y el contrato del diseño: una subclase debe poder cumplir el papel de su base; un colaborador puede aportar una función intercambiable.

Distinguir una especialización de una colaboración

Un registro de gastos utiliza una política de recargos, pero no es una política. En cambio, una política de recargo fijo puede especializar el comportamiento de una política base. Esta separación permite combinar herencia y composición sin convertir cada diferencia en una nueva subclase del registro.

Antes de introducir clases, comprueba si una función basta. La reutilización por sí sola no justifica una jerarquía: también importan la estabilidad de las reglas y la posibilidad de cambiar un comportamiento independientemente del objeto que lo usa.

Combinar una política heredada con un registro

Guarda el siguiente código como policies.py y ejecútalo con python policies.py. La base conserva el importe y la especialización suma un recargo fijo. El registro recibe su política al construirse.

class FeePolicy:
    def apply(self, amount):
        return amount

class FixedFee(FeePolicy):
    def __init__(self, fee):
        self.fee = fee

    def apply(self, amount):
        return super().apply(amount) + self.fee

class Ledger:
    def __init__(self, policy):
        self.policy = policy

    def charge(self, amount):
        return self.policy.apply(amount)

print(Ledger(FeePolicy()).charge(8000))
print(Ledger(FixedFee(500)).charge(8000))
8000
8500

Ambas políticas reciben el mismo importe de 8000. La primera lo devuelve sin cambios; la segunda devuelve 8500. super() busca la implementación siguiente en el orden de resolución de métodos. En esta jerarquía sencilla llega a la base, pero no debe entenderse como una llamada rígida a un único «padre» en cualquier diseño.

Ledger delega en policy.apply. Python puede aceptar otro objeto con una operación compatible aunque no herede de esa base. Sin embargo, tener el mismo nombre de método no garantiza un contrato equivalente: también deben coincidir los argumentos, los resultados y las condiciones de error.

Comparar dos soluciones al mismo recargo

Crea policy_comparison.py con este segundo programa independiente. Mantiene el importe y el recargo para que la comparación no cambie varias condiciones a la vez.

class InheritedLedger:
    def fee(self):
        return 0
    def charge(self, amount):
        return amount + self.fee()
class FixedLedger(InheritedLedger):
    def fee(self):
        return 500
class ComposedLedger:
    def __init__(self, fee_policy):
        self.fee_policy = fee_policy
    def charge(self, amount):
        return amount + self.fee_policy()
def fixed_fee():
    return 500
print(FixedLedger().charge(8000))
ledger = ComposedLedger(fixed_fee)
print(ledger.charge(8000))
ledger.fee_policy = lambda: 0
print(ledger.charge(8000))
8500
8500
8000

Las dos primeras operaciones producen 8500. En la solución compuesta se reemplaza después la función colaboradora por otra sin recargo y el resultado pasa a 8000. La especialización heredada expresa una variante estable de clase; la composición permite sustituir esa pieza durante la vida del objeto. Ninguna elección es universalmente superior.

Revisar el contrato antes de ampliar el diseño

Comprueba si una subclase exige entradas que la base aceptaba o devuelve resultados con otro significado. Una jerarquía que obliga a sus usuarios a comprobar continuamente el tipo concreto puede estar mezclando responsabilidades. Las anotaciones de tipos ayudan a comunicar interfaces, pero no reemplazan las pruebas ni validan los datos automáticamente.

Cambia únicamente el recargo fijo a cero, compara los resultados y restaura 500. Mantén la impresión y el almacenamiento fuera del cálculo de la política para poder probarla sin archivos ni servicios. Puedes revisar las reglas de búsqueda y sustitución en la documentación oficial de herencia y repasar clases e instancias si aún no distingues la clase del objeto colaborador.