3.1 Programação declarativa
3.1.2 Sintaxe declarativa
O paradigma de programação declarativa não especifica um padrão de sintaxe, além de existirem diversas formas de escrever códigos declarativos. No Swift, é possível usar a notação declarativa como a @IBOutlet que atua instanciando as variáveis dentro da classe controlador UIViewController de acordo com as especificações do arquivo storyboard. Outra maneira que enquadra a programação imperativa em programação declarativa, é usando funções implementadas dentro de um objeto retornando à referência do mesmo objeto, como uma função implementada no tipo de dado String que retorna o texto em maiúsculo. Então, usando essa ideia aliada a outros recursos do Swift, o padrão declarativo será moldado em funções que manipulam o objeto e retornam uma cópia do objeto ou simplesmente a referência dele.
A cópia automática dos objetos funciona apenas com variáveis do tipo estruturas e enumeradores com exceção as classes. Para obter o mesmo efeito com as classes, é preciso implementar o protocolo NSCopying que cria uma nova instância do objeto. No entanto, para este trabalho, quando o objeto for uma classe e for implementado um método declarativo no seu escopo, a função deve retornar a referência à classe, gerando redundância, para tornar possível a implementação do fluxo declarativo.
O objetivo principal está em desenvolver uma lógica em que o objeto declarativo será instanciado na primeira linha, enquanto as próximas linhas de código serão para configurar propriedades do objeto manipulado, usando o retorno redundante das funções. Além disso, deve ser possível, dependendo de cada caso, manipular o objeto internamente usando a programação imperativa, suportando operações usando o UIKit ou outras bibliotecas não declarativas.
Conforme a Figura 12, um exemplo de código da biblioteca AlertFactory, é possível criar um fluxo lógico para mostrar alertas no iOS usando o padrão declarativo, como resultado, o alerta é mostrado na tela, como na Figura 13. Usando os métodos do AlertFactory, é possível determinar o título, a mensagem e as ações que o usuário pode interagir. Na linha 9 da Figura 12 é instanciado a classe AlertFactory com o tipo genérico definido como UIAlertController, do UIKit, para apresentar no objeto ViewController. Entre as linhas 10 a 12 são definidos o título, a mensagem e a ação cancelar, que suprime detalhes para dispensar o alerta quando o usuário interagir com o botão. Para consumir o objeto e encerrar as instruções declarativas, é chamado o método present(), responsável por mostrar o alerta na tela (UMOBI, 2019).
Figura 12 - Código exemplo usando a programação declarativa para construir um alerta.
Fonte: Elaborado pelo Autor.
Figura 13 - Alerta construído usando a programação declarativa.
Fonte: Elaborado pelo Autor.
Em comparação com um código usando a classe UIAlertController, conforme a Figura 14, é possível identificar que a estrutura lógica da programação imperativa é organizada de forma diferente. Ao contrário de declarar, deve-se conhecer os métodos e propriedades da classe imperativa, além de seguir uma ordem de execução pré-definida. Nas linhas 8 a 12 são instanciados o alerta com título, mensagem e estilo.
Entre as linhas 14 a 22 é definido e criado a ação cancelar. Por último, usando os métodos do UIKit da UIViewController, o alerta é mostrado, como na Figura 15.
Figura 14 - Código exemplo da UIAlertController para mostrar um alerta.
Fonte: Elaborado pelo Autor.
Figura 15 - Alerta construído usando a programação imperativa.
Fonte: Elaborado pelo Autor.
Com o uso da programação declarativa na criação de alertas no iOS, em comparação com a forma imperativa do UIKit, o algoritmo se torna mais abstrato quanto ao uso das instruções, uma vez que se utiliza os métodos declarativos para determinar as propriedades do objeto ou da aplicação. Um outro benefício está na reutilização de código, pois a implementação imperativa de métodos utilitários que define os alertas pode aumentar ainda mais a complexidade. Nesse caso dos alertas, o primeiro problema ao torná-lo genérico e reutilizável está em abstrair as ações, em que o UIAlertController exige a configuração do título do botão, o estilo e o callback executado no momento da ação, além do programador ter que dispensar o alerta em todas as ações, caso contrário, o alerta continua visível.
Outro problema está na configuração dos objetos que possuem propriedades definidas apenas no momento da sua inicialização, sendo solucionado ao implementar uma estrutura de duas camadas. A primeira camada é a classe declarativa, que salva
todas as propriedades de forma mutável, podendo ser atribuídas várias vezes, e a segunda camada é o objeto final, nesse caso as classes e estruturas imperativas. Um exemplo desse problema é a classe UIAlertController que contém a variável constante preferredStyle definida no momento da inicialização da classe. Com as funções declarativas, o objeto é encapsulado e instanciado posteriormente, no momento que for requisitado a sua leitura, permitindo, de forma redundante, chamar o método with(preferredStyle:) sem gerar erros de compilação e de execução.
O paradigma de programação declarativa tem um fluxo lógico definido conforme a Figura 16. Os objetos declarativos têm início, meio e fim. O primeiro ocorre quando o objeto é criado, informando nenhum ou poucos valores. O segundo são os métodos de configuração diretamente ligados ao contexto do objeto como, no caso dos alertas, sendo declarados as propriedades e comportamentos do objeto. O terceiro, marcado com borda pontilhada, ocorre diretamente ou indiretamente. O encerramento direto é quando o objeto construtor encerra o fluxo declarativo com funções que se auto consomem, usando a notação @discartableResult, ou quando o método retorna vazio, como a função present() do AlertFactory. O encerramento indireto é quando o fluxo declarativo é capturado por outro objeto, variável ou método.
Figura 16 - Fluxo lógico da programação declarativa.
Fonte: Elaborado pelo Autor.
Esse fluxo permite dividir a solução em estágios. O uso dessa lógica para objetos de interface se encaixa com o uso geral de qualquer objeto de interface baseado na UIView. O estágio início deve criar a view internamente com algumas propriedades especificas sendo atribuídas ou nenhuma, dependendo de cada caso.
O estágio meio define as propriedades e comportamentos da UIView. O estágio fim encerra colocando a view na hierarquia do objeto construtor mais próximo ou na view mais próxima.