Como utilizar o Type aliases do Kotlin para facilitar o Design de Código
Jordi Henrique Silva
Posted on September 30, 2024
No último ano pude trabalhar em diferentes projetos que utilizaram Kotlin como linguagem principal, nisso aprendi diferentes funcionalidades que impactaram em maior facilidade de escrita de código mais legível e manutenível. Uma das funcionalidades que me chamaram a atenção foi Type aliases que permite a nomeação para outros tipos já existentes, facilitando o uso em qualquer ponto do sistema. Como, por exemplo, quando você tem dados como Email e CPF mapeados como tipo primitivo string, mas que poderiam ter seus próprios tipos dado suas características específicas.
Como uso o Type aliases para melhorar meu código?
Bom imagine que você tenha que representar uma Pessoa no sistema, e a mesma deve ter nome, cpf e email como dados. Abaixo tem um exemplo de como poderia ser modelada a classe Pessoa.
class Person(val name: String, val cpf: String, val email: String)
Este mapeamento já é suficiente para que o sistema funcione, porém, no desenvolvimento do projeto, outros diversos pontos do sistema, iram trabalhar com conceito de email e cpf. Então utilizaremos o type aliases para definir um tipo para cada um dos conceitos.
typealias Email = String
typealias Cpf= String
Agora em qualquer ponto do sistema é possível declarar os tipos de Email e Cpf. Veja como fica a declaração da classe pessoa com os novos tipos.
class Person(val name: String, val cpf: Cpf, val email: Email)
Legal né? Isso abre muitas possibilidades de uso, até mesmo para aplicação de Polimorfismo.
Olhá só o que aconteceu comigo...
Estava realizando a correção de um débito técnico, na situação era um sistema em Spring Boot com Kotlin, e a tarefa era fazer com que uma API de atualização existente fosse mais dinâmica. O que quero dizer com mais dinâmica, é que a API no momento recebia um contrato com N critérios de atualização, porém, conforme a quantidade de critérios crescia, o código tornava-se mais difícil e longo de ser lido. E para piorar, o comportamento da função não era alterado!!
@Service
public class UpdatePersonService(
@Autowired
val manager: EntityManager
){
@Transactional
fun update(personId: Long, updateModel: UpdatePersonModel){
val person = manager.find(Person::class.java, personId)
if(updateModel.hasName){
person.name = updateModel.name
}
if(updateModel.hasCpf){
person.cpf=updateModel.cpf
}
if(updateModel.hasEmail){
person.email=updateModel.email
}
}
}
O código acima ilustra a situação, onde a função update()
tem a responsabilidade de atualizar os dados da entidade Pessoa, cada dado para ser atualizado, deve atender o seu critério, como, por exemplo, para atualizar o cpf, a propriedade hasCpf
deverá ser verdadeira. e assim sucessivamente até o fim dos critérios. O comportamento desta função é garantir que o contrato de atualização entre Person
e UpdatePersonModel
esta sendo cumprido.
Foi aí que vi a oportunidade de combinar as coisas...
Ao observar o código podemos visualizar cada condição com o seguinte contrato de função.
(person: Person, updateModel: UpdatePersonModel) -> Unit
E após visualizá-los, percebi que o código poderia ser refatorado para possibilitar o crescimento de critérios sem causar mais alterações no código da função update()
. Então, o primeiro passo foi transformar cada condição em uma função, e organizá-las em um arquivo chamado UpdatePersonFunction.kt
.
fun updateName(person: Person, updateModel: UpdatePersonModel){
if(updateModel.hasName){
person.name = updateModel.name
}
}
fun updateDocument(person: Person, updateModel: UpdatePersonModel){
if(updateModel.hasDocument){
person.document=updateModel.document
}
}
fun updateAddress(person: Person, updateModel: UpdatePersonModel){
if(updateModel.hasAddress){
person.address=updateModel.address
}
}
Em seguida, utilizei o type aliases do kotlin para definir o contrato da função como tipo UpdatePersonAction
, que representa uma função que receba dois argumentos, um do tipo Person
e outro do tipo UpdatePersonModel
, e não possui retorno.
private typealias UpdatePersonAction = (person: Person, updateModel: UpdatePersonModel) -> Unit
Após a definição do tipo, bastou criar uma coleção de funções do tipo UpdatePersonAction
, e adicionar a referência de cada uma delas.
@Service
public class UpdatePersonService(
@PersistenceContext val manager: EntityManager
){
private val updatePersonFunctions = listOf<UpdatePersonAction>(
::updateName,
::updateDocument,
::updateAddress
)
@Transactional
fun update(personId: Long, updateModel: UpdatePersonModel){
val person = manager.find(Person::class.java, personId);
updatePersonFunctions.forEach(updateAction ->{
updateAction.invoke(person, updateModel)
}
);
}
}
E por fim, alterar o código da função update()
, para percorrer a coleção, e fazer a invocação da cada função com os argumentos. Desta forma, às funções que atenderem seus critérios serão aplicadas e as demais não.
Espero que este artigo sirva de alguma inspiração para você explorar novas funções e cada vez mais escrever códigos que sejam fáceis de serem lidos e entendidos.
E você já utilizou o type aliases em algum projeto? Deixa nos cometários como foi sua experiência!
Posted on September 30, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.