El principio de evitar condicionales fomenta el uso de polimorfismo para simplificar el código y adherirse al Principio de Responsabilidad Única (SRP). Exploremos esta idea con ejemplos del mundo real y cómo se puede aplicar en aplicaciones Angular.

🤔 ¿Por qué evitar los condicionales?


Mejora la legibilidad:
Cada clase o función maneja una única responsabilidad.
Mantenimiento más fácil: Añadir nueva funcionalidad se vuelve menos propenso a errores.
Escalabilidad: Evita una lógica condicional larga y enredada que crece exponencialmente.
Pruebas: Las unidades más pequeñas y aisladas son más fáciles de probar.🛠 Ejemplo del mundo real: Sistema de procesamiento de pagos
Mal Ejemplo: Lógica condicional

class PaymentProcessor {
  process(paymentType, amount) {
    switch (paymentType) {
      case 'CreditCard':
        return this.processCreditCard(amount);
      case 'PayPal':
        return this.processPayPal(amount);
      case 'Crypto':
        return this.processCrypto(amount);
      default:
        throw new Error('Unsupported payment type');
    }
  }
}

Este enfoque acopla estrechamente el PaymentProcessor a cada tipo de pago. Añadir un nuevo método de pago requiere modificar esta clase.

Un buen ejemplo: Utilización del polimorfismo

class PaymentProcessor {
  process(amount) {
    throw new Error('Method not implemented.');
  }
}

class CreditCardProcessor extends PaymentProcessor {
  process(amount) {
    return `Processing ${amount} with Credit Card`;
  }
}

class PayPalProcessor extends PaymentProcessor {
  process(amount) {
    return `Processing ${amount} with PayPal`;
  }
}

class CryptoProcessor extends PaymentProcessor {
  process(amount) {
    return `Processing ${amount} with Crypto`;
  }
}

// Usage
const paymentMethods = {
  CreditCard: new CreditCardProcessor(),
  PayPal: new PayPalProcessor(),
  Crypto: new CryptoProcessor(),
};

function handlePayment(type, amount) {
  return paymentMethods[type].process(amount);
}

console.log(handlePayment('Crypto', 100)); // Processing 100 with Crypto

Ahora, añadir un nuevo método de pago es tan sencillo como crear una nueva clase y actualizar el objeto paymentMethods.

🌐 Caso de uso de Angular: Renderizado dinámico de componentes


Problema: un cuadro de mandos con múltiples tipos de widgets
Supongamos que estás construyendo un dashboard donde diferentes widgets (por ejemplo, gráficos, tablas y formularios) necesitan ser renderizados dinámicamente en función de su tipo. Un enfoque condicional podría implicar complicadas sentencias if o switch.

CPU
1 vCPU
MEMORIA
1 GB
ALMACENAMIENTO
10 GB
TRANSFERENCIA
1 TB
PRECIO
$ 4 mes
Para obtener el servidor GRATIS debes de escribir el cupon "LEIFER"

Mal ejemplo: Lógica condicional en Angular

@Component({
  selector: 'app-widget-renderer',
  template: `
    <ng-container *ngIf="widget.type === 'chart'">
      <app-chart [data]="widget.data"></app-chart>
    </ng-container>
    <ng-container *ngIf="widget.type === 'table'">
      <app-table [data]="widget.data"></app-table>
    </ng-container>
    <ng-container *ngIf="widget.type === 'form'">
      <app-form [data]="widget.data"></app-form>
    </ng-container>
  `,
})
export class WidgetRendererComponent {
  @Input() widget!: { type: string; data: any };
}

Este enfoque funciona para aplicaciones pequeñas, pero se vuelve inmanejable a medida que se añaden nuevos tipos de widgets.

Un buen ejemplo: Uso del Polimorfismo con un Servicio de Fábrica

Clase Base Abstracta

export abstract class Widget {
  abstract render(data: any): Component;
}

Clases específicas de widgets

export class ChartWidget extends Widget {
  render(data: any): Component {
    return ChartComponent;
  }
}

export class TableWidget extends Widget {
  render(data: any): Component {
    return TableComponent;
  }
}

export class FormWidget extends Widget {
  render(data: any): Component {
    return FormComponent;
  }
}

Servicio de fábrica de widgets

@Injectable({ providedIn: 'root' })
export class WidgetFactory {
  getWidget(type: string): Widget {
    switch (type) {
      case 'chart':
        return new ChartWidget();
      case 'table':
        return new TableWidget();
      case 'form':
        return new FormWidget();
      default:
        throw new Error('Unsupported widget type');
    }
  }
}

Renderizado dinámico

@Component({
  selector: 'app-widget-renderer',
  template: `
    <ng-container *ngComponentOutlet="widgetComponent"></ng-container>
  `,
})
export class WidgetRendererComponent {
  @Input() widget!: { type: string; data: any };
  widgetComponent!: Type<any>;

  constructor(private widgetFactory: WidgetFactory) {}

  ngOnInit() {
    const widget = this.widgetFactory.getWidget(this.widget.type);
    this.widgetComponent = widget.render(this.widget.data);
  }
}

El cambio a polimorfismo usando un patrón de fábrica similar mejoró drásticamente:

  • La organización del código: Cada layout se encapsulaba en su propia clase.
  • Escalabilidad: Añadir nuevos diseños ya no afectaba al código existente.
  • Pruebas: Se podían escribir pruebas unitarias para cada clase de diseño de forma aislada.

👨‍💻 Conclusión


Refactorizar para utilizar el polimorfismo puede parecer un trabajo extra al principio, pero compensa con un código más limpio, más mantenible y escalable. Tanto si manejas lógica de negocio compleja como componentes de interfaz de usuario dinámicos, evitar los condicionales hará que tu aplicación sea más fácil de desarrollar y depurar.

Fuente