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.
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.