Uma sugestão de design ao usar Classes e Interfaces em Typescript.

Adeildo Neto
tech nuvemshop
Published in
4 min readDec 5, 2020

Typescript é poderoso. Auxilia squads de desenvolvimento a terem um só ”idioma” durante a construção de uma aplicação, afinal todos os objetos terão seus atributos e métodos descritos em algum lugar do código. Com tipagem, tanto o criador ou criadora daquela feature específica quanto alguém que está chegando amanhã no time saberá que a classe X tem os atributos y, z e w. Quem usa typescript consegue puxar mais ainda de uma IDE e de seus alerts ainda em tempo de coding. O uso de POO fica mais claro ainda quando tipamos nossas classes e atributos.

Um dos pontos importantes que vem a tona quando uma equipe vai adotar o typescript como ferramenta é ter uma definição concreta e eficiente de como utilizar esse super-set do JS. Quando utilizar classes? E quando utilizar interfaces? Devemos utilizar os dois? Isso realmente importa?

Baseando-se nessas perguntas e na experiência que eu tive com o time de desenvolvimento que estou inserido, resolvi criar essa sugestão de design de código relacionado à classes e interfaces em typescript que pode acabar também fazendo sentido pra você!

Então, primeiramente respondendo; Isso importa? A resposta é sim. Além dos pontos relacionados a entendimento de código que vou falar mais adiante, escolher classes ou interfaces pode acabar interferindo até no tamanho final do seu projeto compilado.

Vamos pegar como exemplo a classe Person abaixo:

É uma classe simples com atributos primitivos, mas sabemos que typescript não roda no browser. Ele é transpilado para o javascript e esse código sofre alterações. E como esse código ficaria transpilado? Assim:

É notável que não há quase nenhuma diferença. Mas, e se Person não fosse uma classe, e sim uma interface? Teria alguma diferença? Algum ganho?

Um código relativamente menor e sem construtor, visto que podemos dizer que uma interface é, em poucas palavras, um contrato, e assim podemos garantir que qualquer objeto que deva ser um Person terá esses atributos. A grande diferença é que, quando uma interface é transpilada, ela fica assim:

Isso mesmo, não gera código! Ou seja, todas as interfaces que você tem em seu projeto não geram bytes na sua compilação final. Você mesmo pode conferir e fazer outros testes usando o Typescript: TS Playground.

TS Playground — An online editor for exploring TypeScript and JavaScript

Se interfaces não geram código transpilado e eu consigo atingir o objetivo de tipar um objeto, eu não preciso de classes, certo?

Eu diria que sim, ainda assim precisamos de classes. Na minha opinião, quando estamos escrevendo código, principalmente em orientação a objetos, uma coisa que temos que ter em mente e entender é que é nossa responsabilidade criar e manter objetos coerentes com seu modelo de domínio.

Se o objeto Person é a representação de um modelo de negócio com seus próprios limites, regras e estados, por qual motivo ele não seria o próprio guardião e mantenedor desses limites, regras e estados?

É possível argumentar que isso pode dificultar um pouco a possibilidade fazermos object composition(https://stackify.com/oop-concepts-composition/), mas na minha opinião, uma classe ou entidade, não deveria ter essa flexibilidade, pois podemos estar sacrificando entendimento do código ao aumentar a complexidade do objeto e, mais grave ainda, diminuindo entendimento da regra de negócio da aplicação.

E como conseguimos alcançar esse estado de coerência da classe e do objeto? Tomando com exemplo a classe Person que representa, em tese, uma pessoa… Faria sentido uma pessoa ter uma idade menor que zero? Ao meu ver, e de acordo com o modelo de negócio proposto no projeto fictício que essa classe pertence, não. Então, por qual motivo eu iria permitir a criação de uma Person com idade negativa?

Levando em conta a minha sugestão de design, Person ficaria assim:

Com esse código, estamos garantindo, de modo geral, que ao criar uma Person ela vai estar seguindo o modelo de negócio que a aplicação espera de qualquer um desses objetos. Além de seguramente proteger essa borda do sistema, estamos comunicando de forma indireta a qualquer um que estiver utilizando este objeto, que o estado coerente dele passa por este construtor. Ao meu ver, é um grande benefício em detrimento a mais código transpilado no build final da aplicação.

Então, minha sugestão é: Se o objeto faz parte de seu domínio e você precisa manter seu estado válido, vale a pena levantar a possibilidade de ser uma classe. Mas, se é um objeto vindo de uma API externa, por exemplo, onde você e seu time não tem controle de sua coerência e pode apenas validá-lo a nível de serviço, talvez uma interface faça sentido para representá-lo.

--

--

Adeildo Neto
tech nuvemshop

Software Developer at Nuvemshop. Trying to write more.