Kotlin para devs Java e outros : Funções de alta ordem e extensões para práticas de SOLID e minimizar o uso de padrões de projeto
Na década de 1990, os pesquisadores Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides publicaram seu best-seller sobre padrões de projeto (Design Patterns) para as linguagens C++ e Smalltalk. Esses padrões redefiniram a maneira de construir software, proporcionando uma abordagem prática e flexível para resolver problemas comuns de design e estabelecendo as bases para as boas práticas que hoje associamos aos princípios SOLID. Embora os padrões tenham sido fundamentais na criação de software reutilizável, seu uso pode acarretar complexidade excessiva e overengineering, especialmente quando os desenvolvedores tentam resolver problemas inexistentes ou improváveis.
Atualmente, existe uma demanda crescente por código mais conciso, com soluções que eliminem abstrações e estruturas intermediárias sempre que possível. Linguagens como Kotlin foram projetadas com essa premissa em mente, oferecendo recursos poderosos — como funções de alta ordem e extensões — que permitem estender funcionalidades de maneira intuitiva, dispensando a necessidade de padrões de projeto formais, como o criacional Factory Method, o comportamental Strategy e os estruturais Adapter e Decorator.
Nesse contexto, as funções de alta ordem são funções que podem receber outras funções como parâmetros e também retorná-las como resultado. Já as funções de extensão permitem adicionar novas funcionalidades a classes já existentes sem modificar seu código diretamente ou precisar de herança. Isso é útil para "estender" uma classe com novos métodos, que passam a funcionar como se fossem métodos da própria classe. Esses recursos apoiam o Princípio da Responsabilidade Única e o Princípio do Aberto-Fechado — os 'S' e 'O' de SOLID. O primeiro é respeitado porque novas funcionalidades podem ser adicionadas a uma classe sem alterar seu código original, mantendo-a focada em sua responsabilidade principal, e o segundo, ao permitir que as classes sejam estendidas sem modificação, segue o princípio de que as classes devem ser abertas para extensão e fechadas para alteração.
A seguir, seguem dois exemplos de como esses recursos respeitam estes princípios sem usar diretamente padrões de projeto:
1) Cálculo de mensalidade para estudante de ensino médio e estudante de ensino fundamental de uma escola particular.
- Resolução com Java e padrão Strategy.
// Interface Strategy
public interface MensalidadeStrategy {
double aplicaMensalidade(double preco);
}
//Classes de implementação para as estratégias
public class EstudanteMedio implements MensalidadeStrategy {
@Override
public double aplicaMensalidade(double preco) {
return preco * 1.07;
}
}
public class EstudanteFundamental implements MensalidadeStrategy {
@Override
public double aplicaMensalidade(double preco) {
return preco * 1.02;
}
}
// Classe principal
public class Principal {
public static void main(String[] args) {
EstudanteMedio estudanteMedio = new EstudanteMedio();
EstudanteFundamental estudanteFundamental = new EstudanteFundamental();
System.out.println("Mensalidade do estudante de nível médio : R$ " + estudanteMedio.aplicaMensalidade(1200.00));
System.out.println("Mensalidade do estudante de nível fundamental : R$ " + estudanteFundamental.aplicaMensalidade(1200.00));
}
}
// Saídas
// Mensalidade do estudante de nível médio : R$ 1284.0
// Mensalidade do estudante de nível fundamental : R$ 1224.0
- Resolução com Kotlin e funções de alta ordem e extensões.
// Interface Strategy
fun interface MensalidadeStrategy {
fun aplicaMensalidade(preco : Double) : Double
}
// Funções de alta ordem
val estudanteMedio = MensalidadeStrategy { preco -> preco * 1.07 }
val estudanteFundamental = MensalidadeStrategy { preco -> preco * 1.02 }
// Função de alta ordem para calcular o preço
fun Double.calcularMensalidade(mensalidadeStrategy: MensalidadeStrategy) : Double {
return mensalidadeStrategy.aplicaMensalidade(this)
}
// Função principal
fun main() {
val preco = 1200.00
println("Mensalidade do estudante de nível médio : R$ ${preco.calcularMensalidade(estudanteMedio)}")
println("Mensalidade do estudante de nível fundamental : R$ ${preco.calcularMensalidade(estudanteFundamental)}")
}
// Saídas
// Mensalidade do estudante de nível médio : R$ 1284.0
// Mensalidade do estudante de nível fundamental : R$ 1224.0
2) Definição de portas seguras para envio de emails via respectivos protocolos de rede.
- Resolução com Java e Factory Method.
// Interface de envio de emails
interface EnvioEmail {
void enviaEmail();
}
// Classes de implementação
class EnvioEmailSMTP implements EnvioEmail {
@Override
public void enviaEmail() {
System.out.println("Enviando email via porta 587.");
}
}
class EnvioEmailPOP3 implements EnvioEmail {
@Override
public void enviaEmail() {
System.out.println("Enviando email via porta 993.");
}
}
class EnvioEmailIMAP implements EnvioEmail {
@Override
public void enviaEmail() {
System.out.println("Enviando email via porta 995.");
}
}
// Classe principal que encapsula a fábrica
public class Main {
public static EnvioEmail fazEmail(String protocolo) {
switch (protocolo.toUpperCase()) {
case "SMTP":
return new EnvioEmailSMTP();
case "POP3":
return new EnvioEmailPOP3();
case "IMAP":
return new EnvioEmailIMAP();
default:
throw new IllegalArgumentException("Protocolo inválido.");
}
}
// Função principal
public static void main(String[] args) {
String protocolo = "SMTP";
EnvioEmail email = fazEmail(protocolo);
email.enviaEmail();
}
}
// Saída
// Enviando email via porta 587
- Resolução com Kotlin e função de extensão.
// Interface de envio de emails
interface EnvioEmail {
fun enviaEmail()
}
// Classes de implementação
class EnvioEmailSMTP : EnvioEmail {
override fun enviaEmail() = println("Enviando email via porta 587.")
}
class EnvioEmailPOP3 : EnvioEmail {
override fun enviaEmail() = println("Enviando email via porta 993.")
}
class EnvioEmailIMAP : EnvioEmail {
override fun enviaEmail() = println("Enviando email via porta 995.")
}
// Função de extensão para envio de emails, essa função atua como uma fábrica, mas não está encapsulada em uma classe ou interface específica como no padrão.
fun String.fazEmail() : EnvioEmail = when (this.uppercase()) {
"SMTP" -> EnvioEmailSMTP()
"POP3" -> EnvioEmailPOP3()
"IMAP" -> EnvioEmailIMAP()
else -> {throw IllegalArgumentException("Protocolo inválido.")}
}
// Função principal
fun main() {
val protocol = "SMTP"
val email = protocol.fazEmail()
email.enviaEmail()
}
// Saída
// Enviando email via porta 587
Em resumo, esses recursos no Kotlin são excelente maneira de respeitar alguns princípios SOLID sem necessariamente usar padrões formais de projeto de modo direto, além de aumentar a concisão e manter a legibilidade do código. Para desenvolvedores Java, vale muito a pena usar código Kotlin, haja vista sua interoperabilidade com a linguagem e como forma de aprendizagem, recomendável os livros - Kotlin em Ação, de Svetlana Isakova e Dmitry Jeremov - e - Kotlin para Desenvolvedores Android, de Antonio Leiva -.