Introdução aos princípios SOLID aqui no blog da TreinaWeb trouxe uma breve introdução aos princípios SOLID. Nesta breve introdução, o foco era na definição do que são os princípios SOLID, abordando quais são os cinco princípios envolvidos e as vantagens que eles podem trazer quando adotados corretamente em um projeto no que diz respeito à qualidade de manutenibilidade do código.
A partir deste artigo, vamos analisar cada um dos princípios SOLID de maneira mais detalhada. Este artigo irá focar no SRP: o Single Responsability Principle.
Os princípios SOLID são cinco princípios de design de código orientado a objeto que basicamente tem os seguintes objetivos:
SOLID é um acrônimo para cada um dos cinco princípios que fazem parte desse grupo:
O SRP é um princípio definido por Robert C. Martin da seguinte maneira:
A class should have only one reason to change.
Ou seja:
Uma classe deveria ter uma única razão para ser alterada.
Quando criamos uma classe e/ou um método, esta estrutura precisa ter uma única responsabilidade, muito clara e bem definida. Isso centraliza as responsabilidades e pontos de mudança em lugares centrais e fáceis de serem identificados, tornando a manutenção do código muito mais simples. Lembre-se sempre do seguinte pensamento: o que meu trecho de código é passível de sofrer alterações drásticas no decorrer do tempo? Se sim, ele é um “eixo de mudança” e, portanto, deveria estar em uma estrutura à parte.
Vamos a um exemplo: vamos considerar um trecho de código Java em uma classe que é responsável por extrair registros a partir de uma tabela de clientes e exibi-los para o usuário. Poderíamos escrever assim:
// Imports omitidos somente para clareza
public class Cliente {
private int id;
private String nome;
// Gets e sets omitidos por clareza
}
public class ListaClientes {
public void listarClientes() throws Exception {
List<Cliente> clientes = new ArrayList<Cliente>();
try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/srp", "root", "root")) {
Statement st = con.createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM clientes");
while (rs.next()){
Cliente cliente = new Cliente();
cliente.setId(rs.getInt("id"));
cliente.setNome(rs.getInt("nome"));
clientes.add(cliente)
}
rs.close();
st.close();
}
clientes.forEach(cliente -> {
System.out.println(cliente.toString()); // Considerando que "toString()" está devidamente sobrescrito
});
}
}
Veja a classe ListaClientes
… Ela está extremamente sobrecarregada, pois ela é responsável por:
Cliente
do resultset); A nossa classe ListaClientes
passa a ser chamada de God class, ou classe Deus, pois ela está fazendo tudo… Ela está concentrando todos os pontos de mudança em um único local:
Ou seja: se nossa classe concentra muitos pontos de mudança, ela também está acumulando muitas responsabilidades.
Os impactos disso são terríveis:
ListaClientes
tem um grau de legibilidade baixíssimo; ListaClientes
é complexa para ser testada adequadamente, principalmente se estivermos utilizando testes automatizados. Por exemplo: você pode ter um caso de teste que testa a exibição dos clientes na interface. Porém, no código que criamos, este caso de teste pode simplesmente falhar porque houve um erro na hora de abrir a conexão com o banco de dados. Nese caso, teríamos um falso negativo: o teste falha não porque os clientes não são exibidos, mas sim porque não foi possível abrir a conexão com o banco de dados. Isso acontece porque nosso método e nossa classe possuem um alto acoplamento, ou seja: suas funções estão todas “agarradas” umas nas outras; listarClientes()
;Desse modo, reparamos que a classe acima claramente viola o Single Responsability Principle.
Se quisermos melhorar o nosso código de listagem de clientes, precisamos pensar em separar as responsabilidades, ou seja: os pontos de mudança.
Um possível ponto de mudança é a conexão com o banco de dados… O IP do servidor ou as credenciais de conexão podem ser alteradas em algum momento. Por isso, vamos isolar esta responsabilidade em uma classe especializada em criação de conexões.
public class FabricaConexao {
public static Connection criarConexao() throws SQLException {
Connection conexao = DriverManager.getConnection("jdbc:mysql://localhost:3306/srp", "root", "root");
return conexao;
}
}
Esta classe respeita o SRP: ela possui uma única responsabilidade, concentrando um único ponto de mudança: a conexão com o banco de dados.
Outra responsabilidade clara é a leitura dos clientes a partir da tabela correspondente. Podemos também isolar este processo em uma classe separada. Nesse caso, é comum vermos a adoção de um design pattern chamado DAO - Data Access Object. Trata-se de uma classe para justamente lidar com as fontes de dados de objetos na aplicação. No nosso caso, temos uma tabela do MySQL como fonte de dados para obtenção de objetos Cliente
. Essa classe poderia ser implementada da seguinte maneira:
public class ClienteDAO {
public ArrayList<Cliente> lerClientes() throws Exception {
List<Cliente> clientes = new ArrayList<Cliente>();
try (Connection conexao = FabricaConexao.criarConexao()) {
Statement comando = conexao.createStatement();
ResultSet rs = comando.executeQuery("SELECT * FROM clientes");
while (rs.next()) {
contatos.add(this.extrairClienteDoResultSet(rs));
}
}
return clientes;
}
private Cliente extrairClienteDoResultSet(ResultSet rs) {
Contato contato = new Contato();
contato.setId(rs.getInt("id"));
contato.setNome(rs.getString("nome"));
return contato;
}
}
Veja que nossa classe ClienteDAO
também possui uma única responsabilidade: ler clientes a partir de uma tabela clientes
no banco de dados, utilizando a conexão criada pela classe FabricaConexao
criada anteriormente. Veja que até separamos a responsabilidade de análise do ResultSet
para extração do cliente em um método à parte, método este que pode ser reutilizado por qualquer outro método do ClienteDAO
sem prejuízos. Caso alguma coluna da tabela de clientes
tenha seu nome alterado, bastará a nós alterar o método extrairClienteDoResultSet()
: automaticamente, todos os métodos de ClienteDAO
já saberão a nova forma de extrair os clientes do banco de dados.
Agora, temos mais uma responsabilidade: a interface, para exibição dos clientes:
public class UIExibicaoClientes {
public void listarClientes() {
try {
ClienteDAO clienteDAO = new ClienteDAO();
ArrayList<Cliente> clientes = clienteDAO.lerClientes();
clientes.forEach(cliente -> {
System.out.println(cliente.toString());
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
Veja que, agora, nosso código é muito mais claro, conciso e coeso. As responsabilidades também estão muito bem separadas: se tivermos que lidar com conexões, mexemos só na classe FabricaConexao
; se precisarmos alterar a maneira como os clientes são lidos do banco de dados, mexemos só em ClienteDAO
; se precisarmos alterar a maneira como os clientes são exibidos, mexemos só em UIExibicaoClientes
. Veja também que o grau de reusabilidade do nosso código aumentou drasticamente: as classes FabricaConexao
e ClienteDAO
também podem ser utilizadas por qualquer outro ponto em nosso projeto que precise da lista de clientes do banco de dados ou precise lidar diretamente com conexões ao banco de dados. Por fim, nosso novo código é bem mais fácil de ser testado, já que agora consigo testar cada responsabilidade de maneira isolada uma das outras. Nossas classes agora estão mais próximas do Princípio da Responsabilidade Única.
Podemos resumir o SRP nos seguintes tópicos:
Líder de Conteúdo/Inovação. Pós-graduado em Projeto e Desenvolvimento de Aplicações Web. Certificado Microsoft MCSD (Web).
Todos os artigosVeja neste artigo boas práticas que você deve ter com seu código.
Veremos nesse artigo o padrão de projeto Strategy em PHP, aplicaremos refatoração para implementá-lo...
Neste primeiro artigo da série sobre SOLID, vamos entender o porquê de eles terem sido definidos, al...
Manter os objetos isolados é o objetivo do Mediator Pattern. No ASP.NET Core a sua implementação é f...
Conheça o padrão arquitetural Porto! Voltado para aplicações back-end, ele permite escrever monolito...
Veja nesse artigo como um jogo digital é criado e os principais conceitos envolvidos.
No Android é possível modificar a aparência de um botão (tamanho, cor, borda, alinhamento etc) via X...
Aprenda a definir como o CSS calcula as dimensões dos seus elementos e evite que seu layout quebre i...
Aprenda um pouco mais sobre o CSS Grid Layout, agora com suporte completo pelo Firefox e Chrome.
Imutabilidade é uma característica forte nas linguagens funcionais, onde a alteração de estado não é...
Você já teve dificuldade com a organização e escalabilidade do seu CSS? Veja como a arquitetura ITCS...
JSON Schema é uma especificação para validação de documentos JSON. A ideia é parecida com a de um XS...