Transforme seu código TypeScript em uma obra de arte com os princípios SOLID
Pare de escrever código que quebra. Aprenda os princípios que separam devs júnior de sênior.
SOLID previne 80% dos bugs mais comuns. Seu código funciona e continua funcionando.
Menos tempo debugando, mais tempo criando. Features novas sem medo.
SOLID é requisito para dev sênior. Diferencial em entrevistas técnicas.
Código que outros devs entendem. Onboarding sem dor de cabeça.
Cada letra representa um princípio fundamental do código limpo.
Uma classe, uma responsabilidade
class User {
constructor(public name: string, public email: string) {}
// Responsabilidade 1: Validação
validateEmail(): boolean {
return /^[^s@]+@[^s@]+.[^s@]+$/.test(this.email);
}
// Responsabilidade 2: Persistência
save(): void {
// Salva no banco de dados
console.log('Salvando usuário...');
}
// Responsabilidade 3: Notificação
sendWelcomeEmail(): void {
// Envia email de boas-vindas
console.log('Enviando email...');
}
// Responsabilidade 4: Relatórios
generateReport(): string {
return `Relatório do usuário ${this.name}`;
}
}
// Uma responsabilidade por classe
class User {
constructor(public name: string, public email: string) {}
}
class EmailValidator {
validate(email: string): boolean {
return /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
}
}
class UserRepository {
save(user: User): void {
console.log('Salvando usuário...');
}
}
class EmailService {
sendWelcomeEmail(user: User): void {
console.log(`Enviando email para ${user.email}`);
}
}
class UserReportGenerator {
generate(user: User): string {
return `Relatório do usuário ${user.name}`;
}
}
🎯 Resultado: Cada classe tem um motivo para mudar. Testes isolados e código mais limpo.
Aberto para extensão, fechado para modificação
function calculateDiscount(customer: Customer): number {
switch (customer.type) {
case 'regular':
return customer.total * 0.05;
case 'premium':
return customer.total * 0.1;
case 'vip':
return customer.total * 0.2;
// Precisa modificar para adicionar novo tipo
default:
return 0;
}
}
abstract class Customer {
constructor(public total: number) {}
abstract getDiscount(): number;
}
class RegularCustomer extends Customer {
getDiscount(): number {
return this.total * 0.05;
}
}
class PremiumCustomer extends Customer {
getDiscount(): number {
return this.total * 0.1;
}
}
// Novo tipo SEM modificar código existente
class VIPCustomer extends Customer {
getDiscount(): number {
return this.total * 0.2;
}
}
🎯 Resultado: Novas funcionalidades sem quebrar o que já funciona. Zero regressões.
Subclasses devem ser substituíveis por suas classes base
class Bird {
fly(): void {
console.log('Voando...');
}
}
class Penguin extends Bird {
fly(): void {
// Pinguim não voa!
throw new Error('Pinguins não voam!');
}
}
// Quebra quando usa Penguin
function makeBirdFly(bird: Bird): void {
bird.fly(); // Pode dar erro!
}
abstract class Bird {
abstract move(): void;
}
class FlyingBird extends Bird {
move(): void {
console.log('Voando...');
}
}
class SwimmingBird extends Bird {
move(): void {
console.log('Nadando...');
}
}
class Eagle extends FlyingBird {}
class Penguin extends SwimmingBird {}
// Funciona com qualquer Bird
function makeBirdMove(bird: Bird): void {
bird.move(); // Sempre funciona!
}
🎯 Resultado: Polimorfismo que funciona. Sem surpresas desagradáveis.
Muitas interfaces específicas são melhores que uma geral
// Interface muito gorda
interface Worker {
work(): void;
eat(): void;
sleep(): void;
code(): void;
design(): void;
manage(): void;
}
// Robot precisa implementar tudo!
class Robot implements Worker {
work(): void { console.log('Trabalhando...'); }
eat(): void { throw new Error('Robôs não comem!'); }
sleep(): void { throw new Error('Robôs não dormem!'); }
code(): void { throw new Error('Robôs não programam!'); }
design(): void { throw new Error('Robôs não fazem design!'); }
manage(): void { throw new Error('Robôs não gerenciam!'); }
}
// Interfaces específicas
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
interface Programmable {
code(): void;
}
// Cada classe implementa só o que precisa
class Robot implements Workable {
work(): void {
console.log('Robô trabalhando...');
}
}
class Developer implements Workable, Eatable, Sleepable, Programmable {
work(): void { console.log('Desenvolvendo...'); }
eat(): void { console.log('Comendo...'); }
sleep(): void { console.log('Dormindo...'); }
code(): void { console.log('Programando...'); }
}
🎯 Resultado: Classes implementam apenas o que realmente usam. Sem métodos vazios.
Dependa de abstrações, não de implementações
class EmailService {
send(message: string): void {
console.log(`Enviando email: ${message}`);
}
}
// Acoplado diretamente ao EmailService
class NotificationManager {
private emailService = new EmailService();
notify(message: string): void {
// Só pode enviar email!
this.emailService.send(message);
}
}
// Abstração
interface NotificationService {
send(message: string): void;
}
class EmailService implements NotificationService {
send(message: string): void {
console.log(`Email: ${message}`);
}
}
class SMSService implements NotificationService {
send(message: string): void {
console.log(`SMS: ${message}`);
}
}
// Depende da abstração
class NotificationManager {
constructor(private service: NotificationService) {}
notify(message: string): void {
this.service.send(message);
}
}
// Uso flexível
const emailManager = new NotificationManager(new EmailService());
const smsManager = new NotificationManager(new SMSService());
🎯 Resultado: Código flexível e testável. Fácil de trocar implementações.
Veja todos os princípios SOLID trabalhando juntos.
// S: Cada classe tem uma responsabilidade
class Product {
constructor(
public id: string,
public name: string,
public price: number
) {}
}
class Order {
constructor(
public id: string,
public customerId: string,
public items: OrderItem[] = []
) {}
getTotal(): number {
return this.items.reduce((sum, item) => sum + item.getSubtotal(), 0);
}
}
class OrderItem {
constructor(
public product: Product,
public quantity: number
) {}
getSubtotal(): number {
return this.product.price * this.quantity;
}
}
// I: Interfaces específicas
interface OrderRepository {
save(order: Order): Promise<void>;
findById(id: string): Promise<Order | null>;
}
interface PaymentProcessor {
process(amount: number, method: string): Promise<boolean>;
}
interface NotificationSender {
send(message: string, recipient: string): Promise<void>;
}
// O: Aberto para extensão, fechado para modificação
abstract class DiscountCalculator {
abstract calculate(order: Order): number;
}
// L: Substituição de Liskov - todas funcionam igual
class NoDiscount extends DiscountCalculator {
calculate(order: Order): number {
return 0;
}
}
class PercentageDiscount extends DiscountCalculator {
constructor(private percentage: number) {
super();
}
calculate(order: Order): number {
return order.getTotal() * (this.percentage / 100);
}
}
class FixedAmountDiscount extends DiscountCalculator {
constructor(private amount: number) {
super();
}
calculate(order: Order): number {
return Math.min(this.amount, order.getTotal());
}
}
class BuyTwoGetOneDiscount extends DiscountCalculator {
calculate(order: Order): number {
let discount = 0;
order.items.forEach(item => {
const freeItems = Math.floor(item.quantity / 3);
discount += freeItems * item.product.price;
});
return discount;
}
}
// D: Dependency Inversion - depende de abstrações
class OrderService {
constructor(
private orderRepository: OrderRepository,
private paymentProcessor: PaymentProcessor,
private notificationSender: NotificationSender,
private discountCalculator: DiscountCalculator
) {}
async processOrder(order: Order, paymentMethod: string): Promise<boolean> {
try {
// Calcula desconto
const discount = this.discountCalculator.calculate(order);
const finalAmount = order.getTotal() - discount;
// Processa pagamento
const paymentSuccess = await this.paymentProcessor.process(
finalAmount,
paymentMethod
);
if (!paymentSuccess) {
throw new Error('Falha no pagamento');
}
// Salva pedido
await this.orderRepository.save(order);
// Envia notificação
await this.notificationSender.send(
`Pedido ${order.id} confirmado! Total: R$ ${finalAmount}`,
order.customerId
);
return true;
} catch (error) {
console.error('Erro ao processar pedido:', error);
return false;
}
}
}
// Implementações específicas
class DatabaseOrderRepository implements OrderRepository {
async save(order: Order): Promise<void> {
console.log(`Salvando pedido ${order.id} no banco`);
}
async findById(id: string): Promise<Order | null> {
console.log(`Buscando pedido ${id}`);
return null; // Simulação
}
}
class StripePaymentProcessor implements PaymentProcessor {
async process(amount: number, method: string): Promise<boolean> {
console.log(`Processando R$ ${amount} via ${method}`);
return true; // Simulação
}
}
class EmailNotificationSender implements NotificationSender {
async send(message: string, recipient: string): Promise<void> {
console.log(`Email para ${recipient}: ${message}`);
}
}
// Uso do sistema
const orderService = new OrderService(
new DatabaseOrderRepository(),
new StripePaymentProcessor(),
new EmailNotificationSender(),
new PercentageDiscount(10) // 10% de desconto
);
const order = new Order('001', 'customer123', [
new OrderItem(new Product('p1', 'Notebook', 2000), 1),
new OrderItem(new Product('p2', 'Mouse', 50), 2)
]);
await orderService.processOrder(order, 'credit_card');
🎯 Resultado: Sistema flexível, testável e extensível. Cada princípio SOLID em ação!
Como aplicar os princípios SOLID no seu código.
"Clean code is not written by following a set of rules. Clean code is written by programmers who care." - Robert C. Martin