Além de satisfazer seus clientes ou a empresa para qual você trabalha é importante você como desenvolvedor ficar feliz com seu código. Por isso, utilizar os Design Patterns no desenvolvimento Android pode ser essencial para escrever um código limpo e claro.
Além de você, outros desenvolvedores poderão herdar seu código no futuro e nem sempre deixar vários comentários é a melhor opção para facilitar o entendimento do mesmo. Até mesmo para você.
Os Design Patterns são soluções reutilizáveis para problemas comuns no desenvolvimento de software e aqui você vai ter uma visão geral de alguns deles.
Visão Geral
A utilização de um Design Pattern minimiza o tempo gasto de entendimento de um projeto como a procura de por dependências entre funcionalidades, o que cada parte de código faz e como continuar o desenvolvimento.
É possível deixar seu projeto Android de uma forma que fique reutilizável, legível e reconhecível, facilitando a manutenção e evolução futura.
Os Design Patterns podem ser divididos em algumas categorias conforme abaixo:
- Criação: Como você cria objetos
- Estrutural: Como você compõe objetos
- Comportamental: Como você coordena as interações de objetos
Talvez você já esteja utilizando um ou vários desses padrões sem mesmo saber, mas no futuro você irá agradecer sua decisão de seguir um padrão bem definido.
Nos tópicos a seguir, vamos ver alguns Design Patterns de cada categoria e como eles se aplicam ao desenvolvimento Android.
Design Patterns de Criação
Em algum momento do desenvolvimento do seu aplicativo você irá precisar criar um objeto complexo, e isso pode ser facilitado pelo Design Pattern de Criação.
Copiar e colar o mesmo código toda vez que precisar de uma instância de um objeto, nunca é a melhor solução, em vez disso, usamos os Design Patterns de Criação para tornar a criação de objetos simples e fácil.
Builder
Sempre que você vai construir algo você precisa de um checklist ou um passo a passo para não esquecer de alguma coisa no meio do caminho.
A forma mais fácil, é ter alguma coisa que te oriente, um manual ou algo que construa o que você quer para você.
O Design Pattern Builder separa a construção de um objeto complexo de sua representação. Desta forma, o mesmo processo de construção pode criar diferentes tipos de objetos.
No Android, o Design Pattern Builder é usado, por exemplo, na criação de uma Dialog usando AlertDialog.Builder:
AlertDialog.Builder(getActivity()).setMessage("Mensagem") .setTitle("Titulo") .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // Chamadas } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // Chamadas } }).create();
Esse Builder indica o passo a passo e permite que você especifique apenas as partes do seu AlertDialog que são importantes para você.
Injeção de Dependência
A Injeção de Dependência fornece todos os objetos necessários quando você instancia um novo objeto. O novo objeto não precisa construir ou personalizar os objetos em si.
No Android, talvez você precise acessar os mesmos objetos complexos de vários pontos do aplicativo, como um cliente de rede, um carregador de imagens ou SharedPreferences para armazenamento local. Você pode injetar esses objetos em suas Activities e Fragments e acessá-los imediatamente.
O Dagger é o framework de injeção de dependência mais popular para Android e foi desenvolvido em colaboração entre o Google e a Square. Você simplesmente anota uma classe com @Module e a preenche com métodos @Provides:
@Module class DripCoffeeModule { @Provides static Heater provideHeater() { return new ElectricHeater(); } @Provides static Pump providePump(Thermosiphon pump) { return pump; } }
O módulo acima cria e configura todos os objetos necessários. Como uma prática recomendada adicional em aplicativos maiores, você pode até criar vários módulos separados por função.
Em seguida, você cria uma Interface para listar seus módulos e as classes que você vai injetar:
@Component(modules = DripCoffeeModule.class) interface CoffeeShop { CoffeeMaker maker(); }
A Interface faz a ligação das dependências (os módulos) com a injeção dos objetos.
Finalmente, você usa a anotação @Inject para solicitar a dependência onde quer que você precise:
class CoffeeMaker { @Inject Heater heater; @Inject Pump pump; ... }
Esta é uma visão geral simplificada, mas você pode ler a documentação do Dagger para ver detalhes de implementação. Esse Design Pattern pode parecer complicado e “mágico” no início, mas seu uso pode ajudar a simplificar suas classes.
Singleton
O Design Pattern Singleton especifica que apenas uma única instância de uma classe deve existir com um ponto de acesso global. Isso funciona bem ao modelar objetos do mundo real tendo apenas uma instância.
public class SingletonClass { private static SingletonClass sSoleInstance; private SingletonClass(){} //Construtor privado. public static SingletonClass getInstance(){ if (sSoleInstance == null){ // Se não tiver uma instancia criada, cria uma nova. sSoleInstance = new SingletonClass(); } return sSoleInstance; } }
Adicionando um construtor privado, apenas a própria classe pode criar um objeto de si mesma. Então utilizamos um método estático para acessar essa instância única.
Quando você precisa acessar meodos do objeto Singleton, basta fazer uma chamada da seguinte forma:
public class SingletonTester { public static void main(String[] args) { //Instancia SingletonClass instance1 = SingletonClass.getInstance(); System.out.println("Instance 1 hash:" + instance1.hashCode()); } }
O Singleton é provavelmente o Design Pattern mais fácil de entender inicialmente, mas pode ser perigosamente fácil de usar em excesso. Como é acessível a partir de vários objetos, o Singleton pode sofrer efeitos colaterais inesperados que são difíceis de rastrear.
Design Patterns Estruturais
Este tipo de Design Pattern, ajuda o desenvolvedor a lembrar o que uma classe faz dentro do projeto.
Você sem dúvida vai gostar muito dos Design Patterns Estruturais, pois ele ajudaa a organizar suas classes e objetos em conjuntos familiares que executam tarefas semelhantes.
Existem dois que são muito vistos no Android, Adapter e Facade.
Adapter
Como o nosso já diz “Adaptador”, esse Design Pattern permite que duas classes incompatíveis trabalhem juntas convertendo a Interface de uma classe em outra Interface que o cliente espera.
Baseado na lógica de negócios do seu aplicativo, sendo um produto ou um usuário, usamos um Adapter para adequar os dados dentro de uma RecyclerView e mostra-los na tela.
Nessa situação, você pode usar uma subclasse de RecyclerView.Adapter e implementar os métodos necessários para que tudo funcione:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { private String[] mDataset; public MyAdapter(String[] myDataset) { mDataset = myDataset; } public static class MyViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public MyViewHolder(TextView v) { super(v); mTextView = v; } } @Override public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { TextView v = (TextView) LayoutInflater.from(parent.getContext()) .inflate(R.layout.my_text_view, parent, false); MyViewHolder vh = new MyViewHolder(v); return vh; } @Override public void onBindViewHolder(MyViewHolder holder, int position) { holder.mTextView.setText(mDataset[position]); } @Override public int getItemCount() { return mDataset.length; } }
A RecyclerView não sabe o que é um Produto ou Usuário, o trabalho do Adapter é manipular os dados e vincular os dados a lista com o ViewHolder.
Facade
O Desgin Pattern Facade fornece uma Interface de nível superior que facilita o uso de um conjunto de outras Interfaces.
Se sua Activity precisar de uma lista de repositórios do Github, ela poderá fazer a chamada para essa lista sem entender o funcionamento interno. Além de manter seu código limpo e conciso, isso permite que você faça as alterações necessárias na implementação da API sem causar impacto no resto do projeto.
O Retrofit da Square é uma biblioteca Android que ajuda a implementar o Design pattern de Facade. Você cria uma interface para fornecer os dados da API para classes do cliente da seguinte forma:
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); }
O cliente simplesmente precisa chamar listRepos() para receber uma lista de respositórios no retorno de chamada. É claro e limpo.
Isso permite que você faça todos os tipos de personalizações abaixo sem afetar o cliente. Por exemplo, você pode especificar um desserializador personalizado de JSON e sua Activity nem precisa entender o que está acontecendo:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .build(); GitHubService service = retrofit.create(GitHubService.class);
Observe o uso do GsonConverterFactory, trabalhando nos bastidores como um desserializador JSON. Com o Retrofit, você pode personalizar ainda mais as operações com o Interceptor e o OkHttpClient para controlar o comportamento de armazenamento em cache, sem que o cliente saiba o que está acontecendo.
Quanto menos cada objeto souber sobre o que está acontecendo nos bastidores, mais fácil será para você gerenciar as alterações no aplicativo. Esse padrão pode ser usado em muitos outros contextos.
Design Patterns Comportamentais
Este tipo de Design Pattern padroniza a responsabilidade de cada classe dentro do seu projeto.
Eles permitem atribuir responsabilidade para diferentes funções do aplicativo. Você pode usá-los para navegar na estrutura e arquitetura do projeto.
Esses Design Patterns podem variar em escopo, desde o relacionamento entre dois objetos até a arquitetura inteira do seu aplicativo. Em muitos casos, os vários Design Patterns Comportamentais são usados juntos no mesmo aplicativo.
Command
Este Design Pattern permite que você emita solicitações sem conhecer o receptor. Você encapsula uma solicitação como um objeto e envia-a. Decidir como completar a solicitação é um mecanismo totalmente separado.
O EventBus da Greenrobot é um framework Android popular que suporta esse padrão da seguinte maneira.
Defina um Evento.
public class MessageEvent { public final String message; public MessageEvent(String message) { this.message = message; } }
Implemente os assinatentes irão receber as solicitações. Estes são definidos com a anotação @Subscribe.
@Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show(); }
Os assinantes também precisam ser registrados e cancelados. Somente enquanto os assinantes estiverem registrados, eles receberão eventos.
No Android, em Activities e Fragments, você normalmente deve registrar de acordo com seu ciclo de vida. Na maioria dos casos, onStart/onStop funciona bem:
@Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { EventBus.getDefault().unregister(this); super.onStop(); }
Envie um evento de qualquer parte do seu código. Todos os assinantes atualmente registrados que correspondem ao tipo de evento receberão.
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
Já que muito desse Design Pattern funciona em tempo de execução, você pode ter um pequeno problema ao rastrear problemas, a menos que tenha uma boa cobertura de teste. Ainda assim, um fluxo de comandos bem projetado equilibra a legibilidade e deve ser fácil o bastante para dar manutenção.
Oberver
O Design Pattern Observer define uma dependência um-para-muitos entre objetos. Quando um objeto muda de estado, todos os seus dependentes são notificados e atualizados automaticamente.
Este é um Design Pattern versátil. Você pode usá-lo para operações de tempo indeterminado, como chamadas de API. Você também pode usá-lo para responder à entrada do usuário.
O framework RxAndroid permitirá que você implemente esse Design Pattern em todo o seu aplicativo:
Observable.just(dados) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(/* an Observer */);
Você define objetos observáveis que irão retornar valores. Esses valores podem ser retornados tudo de uma vez, como um fluxo contínuo, ou a qualquer momento.
Os objetos do assinante receberão esses valores quando chegarem. Por exemplo, você pode abrir uma assinatura quando fizer uma chamada de API, receber a resposta do servidor e retornar algo.
Model View Controller
O Model View Controller, também conhecido como MVC, é o Design Pattern atual mais usado em várias plataformas. É particularmente fácil definir seu projeto dessa maneira no Android.
Ele é dividido em três partes:
- Model: Suas classes de dados. Se você tiver objetos Usuário ou Produto, eles “modelam” o mundo real.
- View: Suas classes visuais. Tudo o que o usuário vê se enquadra nessa categoria.
- Controller: A ligação entre os dois anteriores. Ele atualiza a View, recebe a entrada do usuário e faz alterações no Model.
Dividir seu código entre essas três categorias ajudará muito a tornar seu código desacoplado e reutilizável.
No momento que você precisar uma nova tela ao aplicativo utilizando os dados que já existem, basta reutilizar os mesmos Models e apenas alterar as Views.
Além disso, mover o máximo possível de layout e lógica de recursos para o XML do Android mantém sua camada de View limpa e organizada.
Model View ViewModel
Esse Design Pattern de arquitetura infelizmente é semelhante ao padrão MVC. Os componentes Model e View são os mesmos. O objeto ViewModel é a ligação entre as duas camadas, mas opera de maneira diferente do componente Controller.
O ViewModel expõe comandos para a View e a vincula ao Model. Quando o Model é atualizado, as Views correspondentes também são atualizadas por meio da vinculação de dados. Da mesma forma, à medida que o usuário interage com a View, as ligações funcionam na direção oposta para atualizar automaticamente o Model. Este Design Pattern reativo remove muito código acoplado.
O MVVM está crescendo em popularidade, mas ainda é uma arquitetura nova. Recentemente, o Google introduziu esse Design Pattern como parte de sua biblioteca de componentes de arquitetura.
Clean Architecture
A Clean Architecture existe em um nível de abstração mais alto do que os outros MVC e MVVM. Ele descreve a arquitetura geral do aplicativo com várias camadas: objetos de negócios, casos de uso, armazenamento de dados e interface do usuário.
É semelhante à arquitetura do MVC e o MVVM e os mesmos existem dentro da Clean Architecture na apresentação externa e nas camadas da interface do usuário.
A definição original da Clean Architecture está aqui, e muitos exemplos podem ser encontrados para Android.
Conclusão
Embora seja ótimo manter-se atualizado com as APIs mais recentes, manter seus aplicativos atualizados pode dar um certo trabalho. Investir nos Desgin Patterns de software desde o início melhorará seu retorno sobre o tempo de desenvolvimento. Você começará a perceber que você faz mais com menos esforço.
Espero que este artigo sirva como ponto de partida para sua pesquisa sobre Design Patterns para Android.
Se você tiver dúvidas ou comentários, não deixe de comentar abaixo.