Alura > Cursos de Mobile > Cursos de iOS > Conteúdos de iOS > Primeiras aulas do curso iOS: escrevendo código de qualidade com SOLID em Swift

iOS: escrevendo código de qualidade com SOLID em Swift

Princípio da responsabilidade única - Apresentação

Olá, meu nome é Giovanna Moeller e sou instrutora aqui na Alura.

Audiodescrição: Giovanna é uma mulher branca. Tem cabelo loiro e liso. Veste uma camiseta azul marinho com o escrito Alura e usa óculos de armação de gatinho. Está nos estúdios da Alura; ao fundo, há uma iluminação que vai do roxo para o azul.

Boas-vindas ao curso de Solid com iOS!

Entendendo o Projeto do Curso

Neste curso, temos o projeto Swift Bank. Nele, é possível realizar depósitos, como um exemplo de R$200. Para isso, na tela de boas-vindas no emulador do projeto, clicamos em "depósito" e depois informamos o valor desejado. Logo após inserir o valor, selecionamos o botão "Confirmar depósito".

Após realizar o depósito no Swift Bank, o sistema exibe uma mensagem de confirmação para a pessoa usuária. Além disso, há a possibilidade de efetuar saques, como por exemplo, retirar R$100. Após cada operação de saque, o sistema apresenta uma mensagem indicando o sucesso da operação.

Outra funcionalidade relevante do Swift Bank é a disponibilidade de uma tabela na seção "últimas transações" que registra o histórico de transações.

O que Aprenderemos neste Curso?

Este projeto está em funcionamento e visualmente agradável, porém, ainda há muito código que podemos aprimorar. Vamos realizar uma refatoração utilizando os princípios do SOLID.

Importância do SOLID

  1. Mais fácil de alterar
  2. Introduzir novas funcionalidades
  3. Menos desacoplamento
  4. Mais extensível

É essencial dominar esses princípios para escrever um código que seja facilmente modificável, que permita a introdução de novas funcionalidades com facilidade, que seja menos desacoplado e mais extensível.

Como pessoa desenvolvedora, é importante aplicar esses princípios para atender às exigências do mercado de trabalho, onde a qualidade do código é fundamental.

Pré-requisitos

Para aproveitar ao máximo este curso, é fundamental ter um bom entendimento da linguagem Swift e suas principais funcionalidades relacionadas à programação orientada a objetos.

Espero que tenha gostado da proposta apresentada neste vídeo e aguardo sua participação nos próximos conteúdos!

Princípio da responsabilidade única - Princípio da responsabilidade única (SRP)

Antes de entendermos o conceito do SOLID, vamos analisar um exemplo no Playground.

Analisando o Playground

Suponha que o código a seguir faça parte de um sistema de gerenciamento de pedidos.

SOLID - Single Responsibility Principle (Ruim).xcplaygroundpage

import Foundation

class Order {
    let product: String
    let quantity: Int
    let customerEmail: String

    init(product: String, quantity: Int, customerEmail: String) {
        self.product = product
        self.quantity = quantity
        self.customerEmail = customerEmail
    }
}

// código omitido

Na linha 6, encontramos a classe Order, responsável por representar um pedido, junto com seus atributos, como product (nome do produto), quantity (quantidade do produto) e customerEmail (e-mail do cliente). O construtor está devidamente definido no init().

// código omitido

class OrderManager {
    func createOrder(product: String, quantity: Int, customerEmail: String) {
        // Lógica para criar um pedido
        print("Pedido para \(product), quantidade \(quantity) criado.")
        let order = Order(product: product, quantity: quantity, customerEmail: customerEmail)
        saveOrderToDatabase(order: order)
        sendConfirmationEmail(to: customerEmail)
    }
    
    func saveOrderToDatabase(order: Order) {
        // Salvar pedido no banco de dados
        print("Pedido salvo no banco de dados.")
    }
    
    func sendConfirmationEmail(to email: String) {
        print("Enviando email para \(email)")
    }
}

let orderManager = OrderManager()
orderManager.createOrder(product: "Livro Swift", quantity: 1, customerEmail: "giovanna@gmail.com")

Na linha 18 do código, temos uma classe denominada OrderManager. Dentro dessa classe, estão definidas várias funções, incluindo CreateOrder, que tem a finalidade de criar um pedido e contém a lógica necessária para isso.

Na linha 22, ela executa a criação do pedido. Em seguida, ela invoca a função saveOrderToDatabase() para salvar o pedido no banco de dados. Além disso, há uma função chamada sendConfirmationEmail(), que é responsável por enviar um e-mail de confirmação ao cliente.

Dessa forma, a classe OrderManager possui três funções distintas para gerenciar o processo de pedidos.

Entendendo o problema

Você consegue identificar algum problema neste código? Suponha que precisamos modificar a lógica de salvar pedidos no banco de dados. Nesse caso, seria preciso fazer alterações diretamente na OrderManager. O mesmo se aplica à necessidade de modificar a lógica de envio de e-mails. Mais uma vez, teríamos que alterar diretamente essa mesma classe.

Portanto, a OrderManager está sobrecarregada com diversas responsabilidades: gerenciamento de pedidos (createOrder), persistência de dados no saveOrderToDatabase (ou seja, salvar no banco de dados) e também envio de e-mails (sendConfirmationEmail). Para solucionar esse problema, é necessário dividir essas responsabilidades em classes menores e mais especializadas.

Vamos resolver esse problema.

Separando em classes menores

Abaixo da OrderManager, criaremos uma nova classe denominada OrderPersistenceManager. Essa classe será responsável pela persistência de dados, ou seja, pelo processo de salvar no banco de dados ou em qualquer outro local adequado.

Para fazer isso, movemos a função saveOrderToDatabase da OrderManager para a OrderPersistenceManager, utilizando o "Command + X" para realizar essa transferência.

// código omitido

class OrderPersistenceManager {
    func saveOrderToDatabase(order: Order) {
        // Salvar pedido no banco de dados
        print("Pedido salvo no banco de dados.")
    }

}

// código omitido

Procedemos criando uma outra classe que chamaremos de EmailService, onde incluiremos a função sendConfirmationEmail. Para isso, faremos uma cópia (Ctrl + C) e em seguida colamos (Ctrl + V).

// código omitido

class EmailService {
    func sendConfirmationEmail(to email: String) {
            print("Enviando email para \(email)")
    }
}

// código omitido

Após a criação das duas classes distintas com suas responsabilidades específicas, é necessário injetar essas dependências na nossa classe OrderManager. Para isso, vamos inicializar essas instâncias no nosso OrderManager. Antes da função CreateOrder, vamos declarar dois atributos privados: orderPersistenceManager do tipo OrderPersistenceManager e emailService do tipo EmailService.

Em seguida, precisamos criar o construtor usando init(), e o Xcode se encarregará de realizar essa configuração automaticamente para nós.

// código omitido

class OrderManager {
    
    private var orderPersistenceManager: OrderPersistenceManager
    private var emailService: EmailService
    
    init(orderPersistenceManager: OrderPersistenceManager, emailService:
          EmailService) {
          self.orderPersistenceManager = orderPersistenceManager
          self.emailService = emailService

    func createOrder(product: String, quantity: Int, customerEmail: String) {

    // código omitido

    }

// código omitido

Na linha 32 e 33, vamos realizar duas ações importantes. Primeiro, comentaremos as linhas do saveOrderToDatabase e sendConfirmationEmail para deixar a explicação mais clara.

Em seguida, chamamos o método orderPersistenceManager.SaveOrderToDatabase(), passando o nosso pedido (a Order). Após isso, chamaremos o método emailService.sendConfirmationEmail(to: customerEmail). Podemos remover a exceção entre essas linhas.

// código omitido

class OrderManager {
    func createOrder(product: String, quantity: Int, customerEmail: String) {
        // Lógica para criar um pedido
        print("Pedido para \(product), quantidade \(quantity) criado.")
        let order = Order(product: product, quantity: quantity, customerEmail: customerEmail)
        //saveOrderToDatabase(order: order)
        //sendConfirmationEmail(to: customerEmail)
          orderPersistenceManager.saveOrderToDatabase(order: order)
          emailService.sendConfirmationEmail(to: customerEmail)
    }

// código omitido

No uso, é importante passar as dependências corretamente. Primeiro, criamos um persistenceManager antes do nosso OrderManager, definindo-o como uma instância de OrderPersistenceManager. Em seguida, criaremos um let EmailService como uma instância de EmailService.

// código omitido

let persistenceManager = OrderPersistenceManager()
let emailService = EmailService()

// código omitido

E então, passamos as dependências na linha 54, durante a instanciação do nosso OrderManager. Se houver um erro de "Missing arguments for parameters 'orderPersistenceManager', 'emailService' in call", podemos clicar em "Fix" no canto direito da mensagem para permitir que o Xcode nos auxilie nessa correção.

Começamos definindo o orderPersistenceManager para gerenciar nossa persistência com persistenceManager e, em seguida, configuramos o emailService para lidar com nossos serviços de e-mail.

// código omitido

let orderManager = OrderManager(orderPersistenceManager: persistenceManager, emailService: emailService)
orderManager.createOrder(product: "Livro Swift", quantity: 1, customerEmail: "giovanna@gmail.com")

// código omitido

Ao executar o código clicando no ícone de play na parte inferior esquerda, verificamos no Playground.

Pedido para Livro Swift, quantidade 1 criado

Pedido salvo no banco de dados

Enviando email para giovanna2gmail.com

Agora que está tudo funcionando conforme o esperado, as classes estão estruturadas com responsabilidades únicas.

Conclusão

Parabéns pela compreensão do primeiro princípio do SOLID: o princípio da responsabilidade única (SRP). Isso implica que cada classe deve ter apenas uma razão para existir e uma razão para mudar, ou seja, deve possuir apenas uma responsabilidade específica. Isso resulta em um código mais fácil de manter e de modificar.

Até mais!

Princípio da responsabilidade única - Conhecendo o projeto Swift Bank

Conhecendo o código do Projeto

Agora que compreendemos mais sobre o princípio de responsabilidade única no Playground, vamos conhecer nosso aplicativo iOS que foi construído com UI Kit e com Vue Code, que nós vamos refatorar também para seguir os princípios SOLID.

Vamos seguir uma dinâmica organizada para entender melhor: primeiro, examinaremos um exemplo no Playground. Em seguida, aplicaremos os conceitos aprendidos ao refatorar um código de um aplicativo iOS chamado Swift Bank.

O Swift Bank é uma aplicação simples que simula funções bancárias básicas, como depósito, saque e visualização das últimas transações.

No simulador, clicamos em "Depósito" e na tela seguinte inserimos o valor R$100. Após confirmar clicando em "Confirmar depósito", recebemos um alerta confirmando o depósito e um botão "Ok".

Depósito efetuado

Você depositou R$100 com sucesso

OK

Clicamos em "Ok".

Ao voltar clicando em "Home" no canto superior esquerdo, observamos que as telas estão interconectadas usando o padrão Delegate. A tabela em "Últimas transações" exibe o depósito de R$100, incluindo data e hora da transação.

Primeiro, faremos um saque de R$50 clicando em "Saque" na parte superior direita da tabela e digitando "50" no campo "Faça um saque". Confirmaremos o saque selecionando o botão "Confirmar saque" e em seguida receberemos a confirmação de que o saque foi realizado com sucesso.

Saque efetuado

Você sacou R$50,00 com sucesso

OK

Ao retornarmos à nossa tela clicando em "Home" na parte superior esquerda, perceberemos que o saldo da nossa conta foi atualizado, e também veremos o registro do saque nas últimas transações. Além disso, realizaremos uma verificação para garantir que caso a pessoa queira sacar um valor maior do que o saldo disponível, isso não será possível.

Para isso, selecionamos "Saque" e digitamos o valor de R$500. Logo após, clicamos em "Confirmar saque". Obtemos a seguinte mensagem:

Erro ao sacar

Você não possui saldo o suficiente.

Vamos entender esse código.

Entendendo o código

Dentro da pasta Services, temos a classe BankAccount responsável por gerenciar os dados relacionados às operações bancárias.

BankAccount

import Foundation

enum BankOperation {
    case withdraw, deposit
}

protocol BankingServiceDelegate: AnyObject {
    func didPerformOperation()
}

protocol AccountServices {
    func performOperation(operation: BankOperation, amount: Double) -> Bool
    func requestLoan(amount: Double)
    func calculateInterestRate()
}

// código omitido

Nessa classe, encontramos uma enumeração chamada BankOperation, que lista os tipos de operações possíveis, tais como withdraw (saque) e deposit (depósito).

Além disso, contamos com o protocolo BankingServiceDelegate, que utiliza o padrão Delegate para facilitar a comunicação entre diferentes componentes. Por exemplo, quando realizamos um depósito na tela de depósito, essa tela comunica a HomeViewController, que é a tela inicial, sobre a operação realizada através do método didPerformOperation().

Nosso sistema inclui um protocolo denominado AccountServices, o qual define os serviços disponíveis para uma conta. Esses serviços incluem a realização de operações, especificadas pelo parâmetro Operation do tipo BankOperation. Adicionar uma nova operação requer a inclusão de uma nova entrada no Enum correspondente.

Além disso, a função performOperation() é responsável por executar as operações e recebe o parâmetro amount como um valor do tipo Double, representando o valor.

Outra funcionalidade é o pedido de empréstimo, executado pela função requestLoan, que também requer o parâmetro amount para especificar o valor solicitado. Adicionalmente, temos a função CalculateInterestRate, utilizada para determinar a taxa de juros aplicável.

Os métodos mencionados, RequestLoan e calculateInterestRate, ainda não estão implementados no sistema. Ao examinarmos a classe BankAccount, que segue o protocolo AccountServices, percebemos que as linhas 56 e 60 não contém implementações para esses métodos.

// código omitido

func requestLoan(amount: Double) {
    // Pedir um empréstimo

}

func calculateInterestRate() {
    // Calcular taxa de juros

}

// código omitido

Se fossemos realizar essas implementações, seria feito nesse ponto do código.

// código omitido

class BankAccount: AccountServices {
    var balance: Double = 0.0
    var accountNumber: String
    var transactionsHistory: [String] = []
    
    init(accountNumber: String) {
        self.accountNumber = accountNumber
    }

// código omitido

A classe BankAccount possui três propriedades principais: balance, que representa o saldo da conta e é inicializada em zero; accountNumber, que é o número da conta; e transactionHistory, um Array de Strings que registra as transações realizadas.

// código omitido

    func performOperation(operation: BankOperation, amount: Double) -> Bool {
        switch operation {
        case .withdraw:
            if amount <= balance {
                balance -= amount
                sendNotification(message: "Saque no valor de \(amount.formatCurrency()) realizado!")
                transactionHistory.insert(message: "Saque no valor de \(amount.formatCurrency())", at: 0)
                return true
            }
            return false
        case .deposit:
            balance += amount
            sendNotification(message: "Depósito no valor de \(amount.formatCurrency()) realizado!")
            transactionHistory.insert(message: "Depósito no valor de \(amount.formatCurrency())", at: 0)
            return true
        }
    }
        
// código omitido

Dentro do método performOperation, definido na linha 33, há uma verificação para determinar o tipo de operação: se é um saque ou um depósito. No caso de ser um saque, são realizadas verificações adicionais para garantir que a transação seja válida, retornando false se não for possível realizar o saque e true se for possível.

Também dispomos de um método chamado SendNotification, o qual está definido na linha 51 do código. Contudo, esse método não executa qualquer ação real. Esta aplicação foi desenvolvida com o propósito de demonstração e ensino, não possui implementação funcional ativa.

Entretanto, a função SendNotification está presente, sendo capaz de enviar notificações por e-mail, SMS, entre outros meios. Adicionalmente, ela registra no Array de TransactionHistory a operação realizada e seu respectivo valor.

Dentro da pasta Extensions, guardamos algumas extensões que apenas gerenciam a interface do usuário, então não vamos entrar em detalhes sobre elas.

Em seguida, na ViewControllers, temos a HomeViewController, que representa a tela inicial do aplicativo. Nesta tela, instanciamos o BankAccount, passando um número de conta fictício que criamos.

Temos elementos como o texto de boas-vindas, o saldo disponível e o accountCardView, que é a primeira parte da tela com um fundo branco. Nesse accountCardView, encontramos os botões de depósito e saque, os quais são gerenciados pela View OperationView, localizada na pasta Views.

Na tela de Depósito (DepositViewController), a pessoa usuária aciona o método didTapConfirmDepositButton() para efetuar o depósito, que é descrito de forma clara e informativa. Esse método notifica sobre o sucesso ou falha do depósito através de alertas, seguindo a lógica fundamental do aplicativo.

É essencial reservar um tempo para analisar e compreender completamente o código, pois nossa próxima etapa é refatorá-lo conforme os princípios SOLID.

Ao examinar a classe BankAccount no arquivo correspondente, é possível identificar inconsistências e oportunidades de melhoria.

Conclusão e Próximos Passos

Com esse entendimento do projeto, iniciaremos a refatoração, começando pelo primeiro princípio do SOLID, que é o de Responsabilidade Única.

Sobre o curso iOS: escrevendo código de qualidade com SOLID em Swift

O curso iOS: escrevendo código de qualidade com SOLID em Swift possui 105 minutos de vídeos, em um total de 47 atividades. Gostou? Conheça nossos outros cursos de iOS em Mobile, ou leia nossos artigos de Mobile.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda iOS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas