<h2>Entendendo Variância: O Problema Fundamental</h2>
<p>Variância é um conceito que descreve como tipos genéricos se relacionam com seus subtipos. Quando você trabalha com programação orientada a objetos, herança e tipos genéricos em TypeScript, surgem questões cruciais: se <code>Cachorro</code> é um subtipo de <code>Animal</code>, então <code>Array<Cachorro></code> é um subtipo de <code>Array<Animal></code>? A resposta não é tão simples quanto parece, e é aqui que variância entra em cena.</p>
<p>O problema emerge quando tentamos atribuir um tipo genérico mais específico a uma variável de um tipo genérico mais amplo. Sem um sistema de variância bem definido, você poderia enfrentar erros em tempo de execução. Por exemplo, se você pudesse adicionar um <code>Gato</code> a um <code>Array<Animal></code> declarado como <code>Array<Cachorro></code>, violaria a segurança de tipos. TypeScript resolve isso através de covariance, contravariance e bivariance — regras que definem quando uma atribuição genérica é segura.</p>
<h2>Covariance: Mantendo a Hierarquia de Tipos</h2>
<h3>O Conceito de Covariance</h3>
<p>Covariance significa que se <code>T</code> é um subtipo de <code>U</code>, então <code>Generic<T></code> é um subtipo de <code>Generic<U></code>. Em outras palavras, a relação de herança é <em>preservada</em> no tipo genérico. Isso funciona bem para posições de <strong>saída</strong> (quando você lê dados) porque é seguro retornar algo mais específico do que o esperado.</p>
<pre><code class="language-typescript"></code></pre>
<p>Neste exemplo, <code>Cachorro[]</code> é atribuído a <code>Animal[]</code>. Isso é seguro porque quando você acessa elementos através da referência <code>Animal[]</code>, espera comportamentos de <code>Animal</code>. Como <code>Cachorro</code> é um subtipo e implementa corretamente todos os comportamentos, não há problema.</p>
<pre><code class="language-typescript"></code></pre>
<h3>Limitações e Riscos</h3>
<p>Covariance é segura para leitura, mas perigosa para escrita. Se você tentar adicionar elementos a um array declarado com covariance, pode enfrentar problemas:</p>
<pre><code class="language-typescript">const animais: Animal[] = [new Animal()];
const cachorros: Cachorro[] = animais as Cachorro[]; // ❌ Casting perigoso
cachorros.push(new Gato()); // TypeScript permite, mas é um erro lógico!
// Agora você tem um Gato em um array declarado como Cachorro[]</code></pre>
<h2>Contravariance: Invertendo a Hierarquia</h2>
<h3>O Conceito de Contravariance</h3>
<p>Contravariance é o oposto: se <code>T</code> é um subtipo de <code>U</code>, então <code>Generic<U></code> é um subtipo de <code>Generic<T></code>. A relação de herança é <em>invertida</em> no tipo genérico. Isso é seguro em posições de <strong>entrada</strong> (quando você escreve dados) porque é seguro aceitar algo mais geral do que o esperado.</p>
<pre><code class="language-typescript"></code></pre>
<p>Esse código é seguro porque o <code>manipuladorAnimal</code> aceita qualquer <code>Animal</code>, incluindo um <code>Cachorro</code>. Não há risco de tentar acessar propriedades específicas de <code>Cachorro</code> que não existam em <code>Animal</code>.</p>
<pre><code class="language-typescript"></code></pre>
<h3>Compreendendo a Segurança</h3>
<p>Contravariance é segura porque você está <em>aceitando</em> um tipo mais geral. Quem chama a função envia um <code>Cachorro</code> específico, mas o handler sabe tratar qualquer <code>Animal</code>, então funciona. O problema nunca ocorre porque você não pode chamar métodos específicos de <code>Cachorro</code> dentro de um handler que aceita <code>Animal</code>.</p>
<h2>Bivariance: A Flexibilidade Perigosa</h2>
<h3>O Que é Bivariance</h3>
<p>Bivariance significa que um tipo genérico aceita tanto covariance quanto contravariance — não há distinção clara. Se <code>T</code> é um subtipo de <code>U</code>, você pode usar <code>Generic<T></code> onde <code>Generic<U></code> é esperado, e vice-versa. Essa flexibilidade é prática, mas compromete a segurança de tipos.</p>
<pre><code class="language-typescript"></code></pre>
<h3>Quando Bivariance Causa Problemas</h3>
<p>A bivariance de TypeScript é principalmente uma decisão de design para manter compatibilidade e praticidade. Arrays em TypeScript são bivariant, o que significa que você pode atribuir <code>Cachorro[]</code> a <code>Animal[]</code> e vice-versa, mas com riscos:</p>
<pre><code class="language-typescript"></code></pre>
<p>TypeScript permite isso sem erro em tempo de compilação porque considera arrays como bidirecionais. Esse é um tradeoff entre segurança e praticidade que você deve conhecer.</p>
<pre><code class="language-typescript">// Como TypeScript resolve: permite leitura e escrita com o mesmo tipo
function processar(lista: Cachorro[]): void {
lista.forEach(c => c.fazerSom());
}
const listaMista: Animal[] = [new Cachorro(), new Gato()];
processar(listaMista as Cachorro[]); // ❌ Requer casting - perigo!</code></pre>
<h2>Variância em Funções e Métodos</h2>
<h3>Posição do Tipo Genérico Importa</h3>
<p>Em TypeScript, onde você usa um tipo genérico determina a variância aplicável. Posições de entrada (parâmetros) seguem contravariance, posições de saída (retornos) seguem covariance.</p>
<pre><code class="language-typescript"></code></pre>
<pre><code class="language-typescript"></code></pre>
<h3>Métodos de Callback com Múltiplas Posições</h3>
<pre><code class="language-typescript">interface Transformador<T, U> {
transformar(item: T): U;
}
const transformarParaCachorro: Transformador<Animal, Cachorro> = {
transformar(animal: Animal): Cachorro {
return new Cachorro(); // Simplificado para exemplo
}
};
// Entrada: contravariance (Animal é mais geral que Cachorro)
// Saída: covariance (Cachorro é mais específico que Animal)
// Isso é invariant como um todo - deve ser exato ou necessita casting
const transformador: Transformador<Cachorro, Animal> = transformarParaCachorro as any;</code></pre>
<h2>Conclusão</h2>
<p>Neste artigo, você aprendeu que <strong>variância define como tipos genéricos se relacionam com hierarquias de herança</strong>. Covariance preserva a hierarquia e é segura para leitura, contravariance a inverte e é segura para escrita, e bivariance oferece flexibilidade com riscos. O ponto crítico é reconhecer que <strong>TypeScript permite bivariance em arrays e propriedades por design prático, mas isso requer sua vigilância</strong>. Na prática, compreender quando aplicar cada conceito ajuda a escrever código type-safe: use tipos de retorno para aproveitar covariance, parâmetros para contravariance, e evite casts perigosos quando possível.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/type-compatibility.html#function-parameter-bivariance" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Type Variance</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/generics.html" target="_blank" rel="noopener noreferrer">Advanced TypeScript - Generics and Constraints</a></li>
<li><a href="https://egghead.io/courses/advanced-typescript" target="_blank" rel="noopener noreferrer">egghead.io - Understanding TypeScript's type system</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Glossary/Covariance_and_contravariance" target="_blank" rel="noopener noreferrer">Mdn Web Docs - Covariance and Contravariance</a></li>
<li><a href="https://effectivetypescript.com/" target="_blank" rel="noopener noreferrer">Effective TypeScript - Items 26-28 sobre Variance</a></li>
</ul>
<p><!-- FIM --></p>