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!
Top comments (0)