C# - Violações dos princípios SOLID - V
Hoje vamos continuar mostrando as violações dos principíos SOLID que fazem parte do dia a dia de todo o desenvolvedor. |
Continuando o artigo anterior veremos a violação do princípio SOLID DIP.
O princípio SOLID DIP (Dependency Inversion Principle) é um dos cinco princípios SOLID, que se refere à inversão de dependência. O objetivo desse princípio é garantir que as classes de um sistema sejam altamente coesas e fracamente acopladas.
A ideia central do DIP é que os módulos de um sistema devem depender de abstrações e não de implementações concretas. Isso significa que as classes de nível superior não devem depender diretamente das classes de nível inferior, mas sim de interfaces ou classes abstratas que representam essas classes de nível inferior.
Isso permite que as classes de nível superior sejam independentes das classes de nível inferior, tornando o sistema mais flexível e fácil de manter. Além disso, a inversão de dependência também permite a fácil substituição de uma classe de nível inferior por outra, desde que ambas implementem a mesma interface ou classe abstrata.
Violação do princípio DIP
Por exemplo, imagine uma classe chamada ProcessaPedido que processa pedidos em um sistema de comércio eletrônico. Essa classe pode depender diretamente da classe DatabaseConnection para se conectar ao banco de dados e recuperar informações do pedido. Isso violaria o princípio DIP, pois ProcessaPedido estaria dependendo de uma implementação concreta, em vez de uma abstração.
Uma forma de corrigir essa violação seria criar uma interface ou classe abstrata para representar a conexão com o banco de dados, e fazer com que DatabaseConnection implemente essa abstração. Em seguida, ProcessaPedido dependeria dessa abstração em vez da implementação concreta. Isso permite que ProcessaPedido seja mais flexível e possa trabalhar com outras implementações de conexão de banco de dados no futuro.
A seguir temos o código que mostra essa violação do padrão:
// Implementação concreta da conexão com o banco de dados
public class DatabaseConnection
{
public void Connect()
{
// Código para estabelecer conexão com o banco de dados
}
public void Disconnect()
{
// Código para desconectar do banco de dados
}
// Implementação dos métodos para recuperar e salvar dados
// no banco de dados
}
// Classe ProcessarPedido que depende diretamente da implementação
// concreta DatabaseConnection
public class ProcessarPedido
{
private DatabaseConnection _databaseConnection;
public ProcessarPedido()
{
_databaseConnection = new DatabaseConnection();
}
public void Processar(Pedido pedido)
{
// Código para processar o pedido utilizando a conexão
// com o banco de dados
_databaseConnection.Connect();
// ...
_databaseConnection.Disconnect();
}
}
|
Agora vejamos como fica o código acima aderente ao padrão DIP:
// Abstração para a conexão com o banco de dados public interface IDatabaseConnection { void Connect(); void Disconnect(); // Métodos para recuperar e salvar dados no banco de dados }
// Implementação concreta da conexão com o banco de dados
public void Disconnect()
// Implementação dos métodos para
recuperar e salvar
// Classe ProcessarPedido que depende da abstração
IDatabaseConnection
public ProcessarPedido(IDatabaseConnection
databaseConnection)
public void Processar(Pedido pedido) |
Nesse exemplo, a classe ProcessarPedido depende da abstração IDatabaseConnection em vez da implementação concreta DatabaseConnection. Essa dependência é injetada por meio do construtor da classe, que recebe um objeto IDatabaseConnection como parâmetro.
Dessa forma, é possível passar diferentes implementações de IDatabaseConnection para ProcessarPedido, sem que a classe precise ser modificada. Isso torna o código mais flexível e fácil de manter.
É importante ressaltar que o princípio DIP não deve ser aplicado de forma dogmática, mas sim como uma orientação para melhorar a qualidade do código. Em alguns casos, pode ser necessário violar o DIP para atingir outros objetivos, como a desempenho. Nesses casos, é importante avaliar os prós e contras e escolher a melhor abordagem para cada situação específica.
Padrões que podem violar o princípio
É importante lembrar que esses padrões de projeto não são inerentemente ruins ou problemáticos, mas a forma como eles são implementados pode resultar em violações do princípio DIP.
Para evitar essas violações, é importante sempre depender de abstrações em vez de implementações concretas e utilizar a injeção de dependência para injetar as dependências de uma classe.
Exemplo de violação do princípio pelo padrão Template Method
Suponha
que temos uma aplicação de gerenciamento de estoque que contém duas
classes: Product e Order. A classe
Product representa um produto no
estoque e a classe Order representa uma
ordem de compra.
public class Product
{
public string Name { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
public class Order
{
private readonly List<Product> _products = new List<Product>();
public void AddProduct(Product product)
{
_products.Add(product);
}
public decimal CalculateTotalPrice()
{
decimal totalPrice = 0;
foreach (var product in _products)
{
totalPrice += product.Price;
}
return totalPrice;
}
public void PrintOrderDetails()
{
Console.WriteLine("Order details:");
foreach (var product in _products)
{
Console.WriteLine($"Product: {product.Name}, Quantity: {product.Quantity},
Price: {product.Price}");
}
Console.WriteLine($"Total Price: {CalculateTotalPrice()}");
}
}
|
Agora, suponha que queremos adicionar uma nova funcionalidade à nossa aplicação, que é gerar um relatório de estoque. Para isso, podemos criar uma classe StockReport que usa o padrão Template Method para gerar o relatório.
A classe StockReport define um esqueleto de algoritmo e deixa algumas etapas para serem implementadas pelas subclasses.
public abstract class StockReport
{
private readonly List<Product> _products = new List<Product>();
public void GenerateReport()
{
LoadProducts();
SortProducts();
FormatReport();
PrintReport();
}
protected abstract void LoadProducts();
protected virtual void SortProducts()
{
_products.Sort((p1, p2) => p1.Name.CompareTo(p2.Name));
}
protected abstract void FormatReport();
protected virtual void PrintReport()
{
Console.WriteLine("Stock Report:");
foreach (var product in _products)
{
Console.WriteLine($"Product: {product.Name}, Quantity:
{product.Quantity}, Price: {product.Price}");
}
}
protected void AddProduct(Product product)
{
_products.Add(product);
}
}
public class InMemoryStockReport : StockReport
{
private readonly List<Product> _productsInMemory;
public InMemoryStockReport(List<Product> productsInMemory)
{
_productsInMemory = productsInMemory;
}
protected override void LoadProducts()
{
foreach (var product in _productsInMemory)
{
AddProduct(product);
}
}
protected override void FormatReport() { var report = new StringBuilder(); report.AppendLine("Stock Report:"); foreach (var product in _products) { report.AppendLine($"Product: {product.Name}, Quantity: {product.Quantity}, Price: {product.Price}"); } File.WriteAllText("stock_report.txt", report.ToString()); } } |
Agora, podemos criar uma instância da classe InMemoryStockReport e gerar o relatório:
var
products =
new
List<Product> { new Product { Name = "Product A", Quantity = 10, Price = 5.99M }, new Product { Name = "Product B", Quantity = 5, Price = 10.99M }, new Product { Name = "Product C", Quantity = 2, Price = 19.99M }, }; var stockReport = new InMemoryStockReport(products); Console.ReadKey(); |
Se continuarmos implementando a funcionalidade para gerar o relatório de estoque dentro da classe InMemoryStockReport, estaremos violando o Princípio da Inversão de Dependência (DIP), porque a classe InMemoryStockReport dependerá diretamente da implementação concreta da classe Product.
Observe que a classe InMemoryStockReport usa diretamente a classe Product para formatar o relatório e gravá-lo em um arquivo. Isso significa que, se a classe Product mudar, a classe InMemoryStockReport precisará ser alterada também. Essa dependência direta é uma violação do Princípio da Inversão de Dependência (DIP).
Para corrigir essa violação, podemos usar uma interface IProduct para representar um produto e injetá-la na classe InMemoryStockReport por meio do construtor. Dessa forma, a classe InMemoryStockReport dependerá apenas da interface IProduct, em vez de depender diretamente da implementação concreta da classe Product.
using System.Text;
public interface IProduct
{
string Name { get; }
int Quantity { get; }
decimal Price { get; }
}
public class Product : IProduct
{
public string Name { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
public class InMemoryStockReport : StockReport
{
private readonly List<IProduct> _productsInMemory;
public InMemoryStockReport(List<IProduct> productsInMemory)
{
_productsInMemory = productsInMemory;
}
protected override void LoadProducts()
{
foreach (var product in _productsInMemory)
{
AddProduct(product);
}
}
protected override void FormatReport()
{
var report = new StringBuilder();
report.AppendLine("Stock Report:");
foreach (var product in _products)
{
report.AppendLine($"Product: {product.Name}, Quantity: {product.Quantity},
Price: {product.Price}");
}
File.WriteAllText("stock_report.txt", report.ToString());
}
}
public abstract class StockReport
{
protected readonly List<IProduct> _products = new List<IProduct>();
protected abstract void LoadProducts();
protected abstract void FormatReport();
public void Generate()
{
LoadProducts();
FormatReport();
}
protected void AddProduct(IProduct product)
{
_products.Add(product);
}
}
|
Observe que a classe abstrata StockReport agora depende apenas da interface IProduct e a classe InMemoryStockReport implementa essa interface.
Isso significa que a classe InMemoryStockReport pode ser facilmente substituída por outra classe que também implementa a interface IProduct, sem afetar a classe StockReport.
E estamos conversados ...
Disse Jesus : "Eu sou o bom Pastor; o bom Pastor dá a sua vida pelas
ovelhas."
João 10:11
Referências:
LINQ - Gerando um Produto Cartesiano - Macoratti
C# - Salvando e Lendo informações em um arquivo XML - Macoratti