Artigo

SOLID - Liskov Substitution Principle (LSP)

Rodrigo Branas
Rodrigo Branas
05 jan 2024·2 min de leitura
#solid#lsp

O Liskov Substitution Principle (LSP) auxilia na redução da fragilidade por meio de hierarquias de classes mais coerentes. Ele afirma: "se S e um subtipo de T, então objetos do tipo T em um programa podem ser substituidos por objetos do tipo S sem alterar nenhuma das propriedades desejaveis daquele programa."

De forma simplificada: subclasses devem ser substituiveis sem quebrar as expectativas do programa.

Exemplo Inicial: AccountGateway

Implementação original com alto acoplamento:

class AccountGateway {
  async getAccountById(accountId: string) {
    const response = await axios.get(`http://localhost:3000/accounts/${accountId}`);
    return response.data;
  }
}

Desacoplando com Inversao de Dependência

Criamos uma interface HttpClient:

interface HttpClient {
  get(url: string): Promise<any>;
}

class AccountGateway {
  constructor(readonly httpClient: HttpClient) {}

  async getAccountById(accountId: string) {
    const response = await this.httpClient.get(`http://localhost:3000/accounts/${accountId}`);
    return response.data;
  }
}

Implementação do Adapter

class AxiosAdapter implements HttpClient {
  async get(url: string): Promise<any> {
    return axios.get(url);
  }
}

Tres Regras para Subclasses

1. Precondicoes Não Devem Ser Reforçadas

Precondicoes representam os parametros necessarios para a execução do metodo. Se uma subclasse restringe a entrada além das expectativas da classe pai, a substituição quebra.

Exemplo de violacao:

class ProductionAxiosAdapter implements HttpClient {
  async get(url: string): Promise<any> {
    if (url.includes("localhost")) throw new Error("This implementation is production only");
    return axios.get(url);
  }
}

Isso reforca as precondicoes ao rejeitar URLs com localhost, violando o LSP.

Solução: Criar hierarquias independentes como LocalHttpClient e ProductionHttpClient.

2. Poscondicoes Não Devem Ser Enfraquecidas

Poscondicoes (valores de retorno) devem manter as expectativas. O tipo de retorno não deve violar o que o chamador espera.

Cenário problematico:

class FetchAdapter implements HttpClient {
  async get(url: string): Promise<any> {
    const response = await fetch(url);
    return response.json();
  }
}

O AccountGateway espera uma propriedade data na resposta. O Axios fornece isso; o Fetch não, quebrando o contrato.

Solução: Abstrair o detalhe de implementação no adapter:

class AccountGateway {
  constructor(readonly httpClient: HttpClient) {}

  async getAccountById(accountId: string) {
    const response = await this.httpClient.get(`http://localhost:3000/accounts/${accountId}`);
    return response;
  }
}

class AxiosAdapter implements HttpClient {
  async get(url: string): Promise<any> {
    const response = await axios.get(url);
    return response.data;
  }
}

3. Invariantes Devem Ser Preservados

Invariantes são estados internos do objeto protegidos pelo encapsulamento. Subclasses devem manter os contratos comportamentais da classe pai.

Violacao classica -- Retangulo vs. Quadrado:

class Rectangle {
  private x: number;
  private y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  setX(x: number) {
    this.x = x;
  }

  setY(y: number) {
    this.y = y;
  }

  getArea() {
    return this.x * this.y;
  }
}

class Square extends Rectangle {
  setX(x: number) {
    this.x = x;
    this.y = x;
  }

  setY(y: number) {
    this.x = y;
    this.y = y;
  }
}

Quando setX() e chamado em Square, ambas as dimensoes mudam -- violando a invariante do Rectangle de que setX() so modifica x.

Solução: Criar hierarquias de classes independentes apesar das relacoes do mundo real. Preservar contratos comportamentais acima de heranca teorica.

Conclusão

O LSP fundamentalmente diz respeito a respeitar as expectativas do programa e permitir substituição sem degradacao. Excecoes podem ser lancadas -- desde que o programa espere e as trate adequadamente.

Continue aprendendo

Já conhece nossas formações?

Os desenvolvedores que vão se manter no futuro são os que dominam a Inteligência Artificial e são arquitetos de software. Aprenda como ser esse tipo de desenvolvedor.

Cookies e privacidade

Utilizamos cookies para melhorar sua experiência, analisar o tráfego do site e personalizar conteúdo. Você pode aceitar todos, rejeitar ou personalizar suas preferências.