DEV Community

Cover image for SOLID (Código Limpo: Que Bruxaria é Essa?!?! - Parte 6)
ananopaisdojavascript
ananopaisdojavascript

Posted on

SOLID (Código Limpo: Que Bruxaria é Essa?!?! - Parte 6)

Princípio da Responsabilidade Única (SRP)

Conforme já declarado em "Clean Code", "Nunca deveria haver mais de uma razão para uma classe mudar". É tentador encher uma classe com um monte de funcionalidades, do mesmo modo que você leva apenas uma mala em seu vôo. O problema é que sua classe não será conceitualmente coesa, o que lhe dará muitos motivos para mudar. É importante minimizar a quantidade de vezes que você precisa para mudar uma classe porque se há muitas funcionalidades em uma classe e você precisa modificar um pedaço dela, pode ser difícil de entender como vai afetar outros módulos dependentes na sua base de códigos.

Não é recomendável:

class UserSettings {
  constructor(user) {
    this.user = user;
  }

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

class UserAuth {
  constructor(user) {
    this.user = user;
  }

  verifyCredentials() {
    // ...
  }
}

class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }

  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Princípio Aberto/Fechado (OCP)

Como diz Bertrand Mayer, "entidades de software (classes, módulos, funções, etc.) devem ser abertos para extensão, porém fechados para modificação". O que isso significa? Esse princípio basicamente diz que você deve permitir que usuários acrescentem novas funcionalidades sem mudar o código existente.

Não é recomendável:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    if (this.adapter.name === "ajaxAdapter") {
      return makeAjaxCall(url).then(response => {
        // transform response and return
      });
    } else if (this.adapter.name === "nodeAdapter") {
      return makeHttpCall(url).then(response => {
        // transform response and return
      });
    }
  }
}

function makeAjaxCall(url) {
  // request and return promise
}

function makeHttpCall(url) {
  // request and return promise
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }

  request(url) {
    // request and return promise
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }

  request(url) {
    // request and return promise
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then(response => {
      // transform response and return
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Princípio da Substituição de Liskov (LSP)

Trata-se de um termo assustador para um conceito muito simples. A definição formal é de "Se S é um subtipo de T, então os objetos de tipo T podem ser substituídos por objetos de tipo S (ou seja, objetos de tipo S podem substituir objetos de tipo T) sem alterar nenhuma das propriedades desejadas daquele programa (correção, tarefas realizadas, etc.)". É uma definição ainda mais assustadora. A melhor explicação para esse princípio é que se você tem uma classe mãe e uma classe filha, então a classe base e a classe filha podem ser usadas indistintamente sem obter resultados incorretos. Ainda pode ser confuso, então vamos dar uma olhada na clássica relação quadrado-retângulo. Em termos matemáticos, um quadrado é um retângulo, porém se você o esquematiza usando a relação "é - um" por herança, você se vai se meter em confusão imediatamente.

Não é recomendável:

class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width) {
    this.width = width;
    this.height = width;
  }

  setHeight(height) {
    this.width = height;
    this.height = height;
  }
}

function renderLargeRectangles(rectangles) {
  rectangles.forEach(rectangle => {
    rectangle.setWidth(4);
    rectangle.setHeight(5);
    const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20.
    rectangle.render(area);
  });
}

const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);
Enter fullscreen mode Exit fullscreen mode

É recomendável:

class Shape {
  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }

  getArea() {
    return this.length * this.length;
  }
}

function renderLargeShapes(shapes) {
  shapes.forEach(shape => {
    const area = shape.getArea();
    shape.render(area);
  });
}

const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);
Enter fullscreen mode Exit fullscreen mode

Princípio da Segregação de Interface (ISP)

O JavaScript não tem interfaces, portanto esse princípio não se aplica com tanta rigidez quanto os outros. Entretanto, é importante e relevante até com o sistema de tipos ausente do JavaScript. O ISP diz que "Clientes não devem ser forçados a depender de interfaces que eles não utilizam". Interfaces são contratos implícitos no JavaScript por causa da "tipagem pato". Um bom exemplo para olhar demonstra que esse princípio no JavaScript é para classes que exigem configurações de objetos maiores. Não exigir dos clientes a configuração de grandes quantidades de opções é benéfico, porque na maioria das vezes eles não precisarão de todas as configurações. Deixá-las opcionais ajuda a prevenir uma "interface gorda".

Não é recomendável:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.settings.animationModule.setup();
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  animationModule() {} // Most of the time, we won't need to animate when traversing.
  // ...
});
Enter fullscreen mode Exit fullscreen mode

É recomendável:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.options = settings.options;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.setupOptions();
  }

  setupOptions() {
    if (this.options.animationModule) {
      // ...
    }
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  options: {
    animationModule() {}
  }
});
Enter fullscreen mode Exit fullscreen mode

Princípio da Inversão de Dependência (DIP)

Esse princípio nos diz dois pontos essenciais: 1. Módulos de alto nível não devem depender de módulos de baixo nível. Ambos dependem de abstrações. 2. As abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

Pode ser difícil de entender em princípio, porém se você trabalhou com AngularJS, você já deve ter visto uma implementação desse princípio na forma de Injeção de Dependência (DI). Enquanto que não são princípios idênticos, o DIP mantém os módulos de alto nível sem saber os detalhes de seus módulos de baixo nível e os configura, o que pode ser alcançado por meio da DI. Um grande benefício é que reduz o acoplamento entre módulos. O acoplamento é um padrão de desenvolvimento muito ruim porque deixa o seu código difícil de refatorar.

Conforme dito anteriormente, o JavaScript não tem interfaces, portanto as abstrações dependentes são contratos implícitos. Quer dizer, os métodos e propriedades que um objeto/classe expõe a outro objeto/classe. No exemplo abaixo, o contrato implícito é que qualquer módulo de requisição para um "InventoryTracker" terá um método "requestItems".

Não é recomendável:

class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryTracker {
  constructor(items) {
    this.items = items;

    // BAD: We have created a dependency on a specific request implementation.
    // We should just have requestItems depend on a request method: `request`
    this.requester = new InventoryRequester();
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

const inventoryTracker = new InventoryTracker(["apples", "bananas"]);
inventoryTracker.requestItems();
Enter fullscreen mode Exit fullscreen mode

É recomendável:

class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

class InventoryRequesterV1 {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryRequesterV2 {
  constructor() {
    this.REQ_METHODS = ["WS"];
  }

  requestItem(item) {
    // ...
  }
}

// By constructing our dependencies externally and injecting them, we can easily
// substitute our request module for a fancy new one that uses WebSockets.
const inventoryTracker = new InventoryTracker(
  ["apples", "bananas"],
  new InventoryRequesterV2()
);
inventoryTracker.requestItems();
Enter fullscreen mode Exit fullscreen mode

E aí? Gostaram? Até a próxima tradução! 🤗

Latest comments (0)