Entre em contato

Implementando os princípios SOLID no desenvolvimento Android

Líderes de pensamento

Implementando os princípios SOLID no desenvolvimento Android

mm

Escrever software é um ato de criação, e o desenvolvimento Android não é exceção. É mais do que apenas fazer algo funcionar. É sobre projetar aplicativos que podem crescer, se adaptar e permanecer gerenciáveis ​​ao longo do tempo.

Como um desenvolvedor Android que enfrentou inúmeros desafios arquitetônicos, descobri que aderir aos princípios SOLID pode transformar até mesmo as bases de código mais emaranhadas em sistemas limpos. Esses não são princípios abstratos, mas maneiras orientadas a resultados e reproduzíveis de escrever código robusto, escalável e sustentável.

Este artigo fornecerá insights sobre como os princípios SOLID podem ser aplicados ao desenvolvimento Android por meio de exemplos do mundo real, técnicas práticas e experiência da equipe do Meta WhatsApp.

Compreendendo os princípios SOLID

Os princípios SOLID, propostos por Robert C. Martin, são cinco princípios de design para programação orientada a objetos que garantem uma arquitetura de software limpa e eficiente.

  • Princípio da Responsabilidade Única (SRP): Uma classe deve ter apenas um motivo para mudar.
  • Princípio Aberto/Fechado (OCP): Entidades de software devem estar abertas para extensão, mas fechadas para modificação.
  • Princípio de Substituição de Liskov (LSP): Os subtipos devem ser substituíveis por seus tipos base.
  • Princípio de Segregação de Interface (ISP): As interfaces devem ser específicas do cliente e não forçar a implementação de métodos não utilizados.
  • Princípio da Inversão de Dependência (DIP): Módulos de alto nível devem depender de abstrações, não de módulos de baixo nível.

Ao integrar esses princípios ao desenvolvimento do Android, podemos criar aplicativos mais fáceis de dimensionar, testar e manter.

Princípio da Responsabilidade Única (SRP): Simplificação de Responsabilidades

Princípio da Responsabilidade Única é a base para escrever código sustentável. Ele afirma que cada classe deve ter uma única preocupação pela qual assume responsabilidade. Um antipadrão comum é considerar Atividades ou Fragmentos como algumas “classes de Deus” que lidam com responsabilidades começando pela renderização da IU, depois busca de dados, tratamento de erros, etc. Essa abordagem cria um pesadelo de teste e manutenção.

Com o SRP, separe diferentes preocupações em diferentes componentes: por exemplo, em um aplicativo de notícias, crie ou leia notícias.


class NewsRepository {
    fun fetchNews(): List {
        // Handles data fetching logic
    }
}

class NewsViewModel(private val newsRepository: NewsRepository) {
    fun loadNews(): LiveData<List> {
        // Manages UI state and data flow
    }
}

class NewsActivity : AppCompatActivity() {
    // Handles only UI rendering
}

 

Cada classe tem apenas uma responsabilidade; portanto, é fácil testar e modificar sem ter efeitos colaterais.

No desenvolvimento moderno do Android, o SRP é implementado principalmente junto com a arquitetura recomendada usando o Jetpack. Por exemplo, a lógica relacionada à lógica de manipulação de dados pode residir dentro do ViewModel, enquanto as Activities ou Fragments devem se importar apenas com a UI e as interações. A busca de dados pode ser delegada a algum Repositório separado, seja de bancos de dados locais como Room ou camadas de rede como Retrofit. Isso reduz o risco de inchaço das classes de UI, já que cada componente recebe apenas uma responsabilidade. Simultaneamente, seu código será muito mais fácil de testar e dar suporte.

Princípio Aberto/Fechado (OCP): Projetando para Extensão

O Princípio Aberto/Fechado declara que uma classe deve ser aberta para extensão, mas não para modificação. É mais razoável para aplicativos Android, pois eles atualizam e adicionam novos recursos constantemente.

O melhor exemplo de como usar o princípio OCP em aplicativos Android são interfaces e classes abstratas. Por exemplo:


interface PaymentMethod {
    fun processPayment(amount: Double)
}

class CreditCardPayment : PaymentMethod {
    override fun processPayment(amount: Double) {
        // Implementation for credit card payments
    }
}

class PayPalPayment : PaymentMethod {
    override fun processPayment(amount: Double) {
        // Implementation for PayPal payments
    }
}

 

Adicionar novos métodos de pagamento não requer mudanças em classes existentes; requer a criação de novas classes. É aqui que o sistema se torna flexível e pode ser dimensionado.

Em aplicativos criados para dispositivos Android, o Princípio Aberto/Fechado é muito útil quando se trata de alternar recursos e configurações tomadas dinamicamente. Por exemplo, caso seu aplicativo tenha uma interface base AnalyticsTracker que relata eventos para diferentes serviços de análise, Firebase e Mixpanel e rastreadores internos personalizados, cada novo serviço pode ser adicionado como uma classe separada sem alterações no código existente. Isso mantém seu módulo de análise aberto para extensão - você pode adicionar novos rastreadores - mas fechado para modificação: você não reescreve classes existentes toda vez que adiciona um novo serviço.

Princípio de Substituição de Liskov (LSP): Garantindo a Intercambiabilidade

O Princípio de Substituição de Liskov afirma que subclasses devem ser substituíveis por suas classes base, e o comportamento do aplicativo não deve mudar. No Android, esse princípio é fundamental para projetar componentes reutilizáveis ​​e previsíveis.

Por exemplo, um aplicativo de desenho:


abstract class Shape {
    abstract fun calculateArea(): Double
}

class Rectangle(private val width: Double, private val height: Double) : Shape() {
    override fun calculateArea() = width * height
}

class Circle(private val radius: Double) : Shape() {
    override fun calculateArea() = Math.PI * radius * radius
}

 

Ambos retângulo e Círculo pode ser substituído por qualquer outro de forma intercambiável sem falha do sistema, o que significa que o sistema é flexível e segue o LSP.

Considere o Android RecyclerView.Adapter subclasses. Cada subclasse do adaptador se estende de RecyclerView.Adaptador e substitui funções principais como onCreateViewHolder, onBindViewHolder e getItemCount. O Visualização do Reciclador pode usar qualquer subclasse de forma intercambiável, desde que esses métodos sejam implementados corretamente e não quebrem a funcionalidade do seu aplicativo. Aqui, o LSP é mantido, e seu RecyclerView pode ser flexível para substituir qualquer subclasse do adaptador à vontade.

Princípio de Segregação de Interface (ISP): Interfaces Lean e Focadas

Em aplicações maiores, é comum definir interfaces com muita responsabilidade, especialmente em torno de redes ou armazenamento de dados. Em vez disso, divida-as em interfaces menores e mais direcionadas. Por exemplo, uma interface ApiAuth responsável por endpoints de autenticação de usuário deve ser diferente de uma interface ApiPosts responsável por posts de blog ou endpoints de feed social. Essa separação evitará que clientes que precisam apenas dos métodos relacionados a posts sejam forçados a depender e implementar chamadas de autenticação, mantendo assim seu código, bem como a cobertura de teste, mais enxuto.

Princípio de Segregação de Interface significa que, em vez de ter interfaces grandes, várias menores e focadas devem ser usadas. O princípio previne situações em que classes implementam métodos desnecessários.

Por exemplo, em vez de ter uma grande interface representando as ações dos usuários, considere o código Kotlin:


interface Authentication {
    fun login()
    fun logout()
}

interface ProfileManagement {
    fun updateProfile()
    fun deleteAccount()
}

 

As classes que implementam essas interfaces podem se concentrar apenas na funcionalidade necessária, limpando assim o código e tornando-o mais fácil de manter.

Princípio de Inversão de Dependência (DIP): Abstraindo Dependências

O Princípio de Inversão de Dependência promove o desacoplamento ao garantir que módulos de alto nível dependam de abstrações em vez de implementações concretas. Este princípio se alinha perfeitamente com as práticas de desenvolvimento modernas do Android, especialmente com frameworks de injeção de dependência como Dagger e Hilt.

Por exemplo:


class UserRepository @Inject constructor(private val apiService: ApiService) {
    fun fetchUserData() {
        // Fetches user data from an abstraction
    }
}

 

Aqui, Repositório do Usuário depende da abstração APIService, tornando-o flexível e testável. Essa abordagem nos permite substituir a implementação, como usar um serviço simulado durante o teste.

Frameworks como Hilt, Dagger e Koin facilitam a injeção de dependências ao fornecer uma maneira de fornecer dependências para componentes Android, eliminando a necessidade de instanciá-los diretamente. Em um repositório, por exemplo, em vez de instanciar uma implementação Retrofit, você injetará uma abstração, por exemplo, uma interface ApiService. Dessa forma, você poderia facilmente alternar a implementação de rede, por exemplo, um serviço simulado na memória para testes locais, e não precisaria alterar nada no código do seu repositório. Em aplicativos da vida real, você pode descobrir que as classes são anotadas com @Inject ou @Provides para fornecer essas abstrações, tornando seu aplicativo modular e amigável para testes.

Benefícios práticos dos princípios SOLID

A adoção dos princípios SOLID no desenvolvimento Android produz benefícios tangíveis:

  1. Testabilidade aprimorada: Classes e interfaces focadas facilitam a escrita de testes unitários.
  2. Manutenibilidade Aprimorada: A separação clara de preocupações simplifica a depuração e as atualizações.
  3. Escalabilidade: Os designs modulares permitem adições de recursos sem interrupções.
  4. Colaboração: Um código bem estruturado facilita o trabalho em equipe e reduz o tempo de integração de novos desenvolvedores.
  5. Otimização de performance: Arquiteturas enxutas e eficientes minimizam o processamento desnecessário e o uso de memória.

Aplicações do mundo real

Em aplicativos ricos em recursos, como aplicativos de comércio eletrônico ou redes sociais, a aplicação dos princípios SOLID pode reduzir muito o risco de regressões sempre que um novo recurso ou serviço é adicionado. Por exemplo, se um novo requisito exigir um fluxo de compra no aplicativo, você pode introduzir um módulo separado que implementará as interfaces necessárias (Pagamento, Analytics) sem tocar nos módulos existentes. Esse tipo de abordagem modular, impulsionada pelo SOLID, permite que seu aplicativo Android se adapte rapidamente às demandas do mercado e evita que a base de código se transforme em espaguete ao longo do tempo.

Ao trabalhar em um projeto grande que requer a colaboração de muitos desenvolvedores, é altamente recomendável manter uma base de código complexa com princípios SOLID. Por exemplo, separar a busca de dados, a lógica de negócios e o tratamento da IU no módulo de bate-papo ajudou a reduzir a chance de regressões ao dimensionar o código com novos recursos. Da mesma forma, a aplicação do DIP foi crucial para abstrair as operações de rede, sendo, portanto, capaz de mudar com quase nenhuma interrupção entre os clientes da rede.

Conclusão

Mais do que um guia teórico, os princípios do SOLID são, na verdade, a filosofia prática para criar software resiliente, adaptável e sustentável. No mundo em rápida evolução do desenvolvimento Android, com requisitos mudando quase tão frequentemente quanto as tecnologias, a adesão a esses princípios fornece uma base firme na qual o sucesso pode ser fundado.

Um bom código não é apenas sobre fazer algo funcionar — é sobre criar um sistema que pode continuar a funcionar e crescer com as necessidades em evolução. Ao adotar os princípios SOLID, você não apenas escreverá um código melhor, mas também criará aplicativos que são uma alegria para desenvolver, escalar e manter.

Farhana é uma desenvolvedora especialista em aplicativos móveis que entregou com sucesso vários aplicativos móveis do zero. Ela conduziu treinamento de desenvolvimento Android para oficiais de ICT do governo no Butão, orientou muitos novos participantes e liderou equipes para alcançar o sucesso juntas.