Refatorar usando interfaces
Na lição anterior, você aprendeu sobre os desafios do código firmemente acoplado e como ele viola o Princípio Aberto/Fechado. Agora, vamos refatorar o exemplo Library e BorrowableBook para tornar o sistema mais flexível e fácil de manter.
Aqui está o código original que demonstra o problema:
public class BorrowableBook
{
public string Title { get; set; }
public bool IsAvailable { get; private set; } = true;
public BorrowableBook(string title)
{
Title = title;
}
public void Borrow()
{
if (IsAvailable)
{
IsAvailable = false;
Console.WriteLine($"You have borrowed \"{Title}\".");
}
else
{
Console.WriteLine($"\"{Title}\" is already borrowed.");
}
}
}
public class Library
{
private BorrowableBook _book;
public Library(BorrowableBook book)
{
_book = book;
}
public void BorrowBook()
{
if (_book.IsAvailable)
{
_book.Borrow();
}
else
{
Console.WriteLine("The book is not available.");
}
}
}
Esse design une firmemente a Library classe à BorrowableBook classe, dificultando a adição de novos tipos de itens emprestáveis (por exemplo, DVDs) sem modificar a Library classe. Para corrigir o acoplamento apertado, introduzimos uma interface para dissociar o comportamento de empréstimo.
Introdução às interfaces
Uma interface define um contrato de comportamento sem detalhar sua implementação. A introdução de uma interface abstrai a funcionalidade de empréstimo e aumenta a flexibilidade do sistema.
Aqui está a IBorrowable interface:
public interface IBorrowable
{
bool IsAvailable { get; }
void Borrow();
}
Esta interface introduz dois membros:
-
IsAvailable: Uma propriedade que indica se o item está disponível para empréstimo. -
Borrow: Um método para emprestar o item.
Ao definir essa interface, separamos o comportamento de empréstimo de qualquer implementação específica.
Refatoração da classe BorrowableBook
Em seguida, atualizamos a BorrowableBook classe para implementar a IBorrowable interface:
public class BorrowableBook : IBorrowable
{
public string Title { get; set; }
public bool IsAvailable { get; private set; } = true;
public BorrowableBook(string title)
{
Title = title;
}
public void Borrow()
{
if (IsAvailable)
{
IsAvailable = false;
Console.WriteLine($"You have borrowed \"{Title}\".");
}
else
{
Console.WriteLine($"\"{Title}\" is already borrowed.");
}
}
}
Agora, a BorrowableBook classe adere à IBorrowable interface, tornando-a intercambiável com outras classes que implementam a mesma interface.
Refatoração da classe Library
Também atualizamos a Library classe para depender da IBorrowable interface em vez da classe concreta BorrowableBook :
public class Library
{
private IBorrowable _item;
public Library(IBorrowable item)
{
_item = item;
}
public void BorrowItem()
{
if (_item.IsAvailable)
{
_item.Borrow();
}
else
{
Console.WriteLine("The item is not available.");
}
}
}
Agora, a Library classe pode trabalhar com qualquer objeto que implemente a IBorrowable interface, tornando-a mais flexível e fácil de estender.
Usando a injeção de dependência
Imagine que está a configurar um sistema de entretenimento doméstico. Em vez de ligar permanentemente uma marca específica de colunas ao seu sistema estéreo, utiliza tomadas de colunas que podem aceitar vários tipos de fichas compatíveis. Este design permite-lhe substituir ou atualizar facilmente os altifalantes sem ter de alterar todo o sistema estéreo.
No software, a injeção de dependência funciona de forma semelhante. Ele permite que uma classe dependa de uma interface abstrata em vez de uma implementação específica. A injeção de dependência torna o sistema mais flexível e fácil de manter porque você pode "conectar" diferentes implementações sem modificar a própria classe.
O construtor em programação é como o técnico que conecta as tomadas de alto-falante durante a configuração do seu sistema estéreo. Para a Library classe, o construtor (public Library(IBorrowable item)) é onde a dependência é fornecida. O construtor permite que a classe trabalhe Library com qualquer implementação compatível, como BorrowableBook ou BorrowableDVD, sem a necessidade de alterar sua estrutura interna. Assim como a tomada de alto-falante permite flexibilidade na escolha de diferentes alto-falantes, o construtor facilita a flexibilidade no uso de vários itens emprestáveis.
Veja como funciona no código:
public class Library
{
private IBorrowable _item;
public Library(IBorrowable item) // Dependency is injected here
{
_item = item;
}
public void BorrowItem()
{
if (_item.IsAvailable)
{
_item.Borrow();
}
else
{
Console.WriteLine("The item is not available.");
}
}
}
Neste exemplo:
- O construtor é onde a "conexão" (a
IBorrowabledependência) é feita, semelhante a como o plugue do alto-falante se conecta ao "jack". - A
IBorrowableinterface funciona como o "conector estéreo", definindo o ponto de conexão padrão. - O
BorrowableBookeBorrowableDVDsão como diferentes tipos de alto-falantes estéreo com conectores exclusivos que se conectam ao "jack".
Usando a injeção de dependência, a Library classe pode trabalhar com qualquer implementação do IBorrowable. Esta abordagem prevê:
-
Flexibilidade: Você pode facilmente alternar ou adicionar novas implementações sem modificar a
Libraryclasse. - Testabilidade: Você pode "integrar" implementações simuladas para fins de teste.
-
Manutenabilidade: A
Libraryclasse não precisa saber os detalhes da implementação específica, facilitando a extensão e a manutenção.
Este design garante que a classe não está mais firmemente acoplada Library a implementações específicas, tornando o sistema mais modular e adaptável.
Adicionar novos itens emprestáveis
Com a interface em vigor, podemos facilmente adicionar novos tipos de itens emprestáveis sem modificar a Library classe. Por exemplo, aqui está uma BorrowableDVD classe:
public class BorrowableDVD : IBorrowable
{
public string Title { get; set; }
public bool IsAvailable { get; private set; } = true;
public BorrowableDVD(string title)
{
Title = title;
}
public void Borrow()
{
if (IsAvailable)
{
IsAvailable = false;
Console.WriteLine($"You have borrowed the DVD \"{Title}\".");
}
else
{
Console.WriteLine($"The DVD \"{Title}\" is already borrowed.");
}
}
}
A BorrowableDVD classe implementa a mesma IBorrowable interface, para que possa ser usada perfeitamente com a Library classe.
Testando o sistema
Aqui está um programa para demonstrar o sistema refatorado:
using System;
class Program
{
static void Main()
{
// Create borrowable items
IBorrowable book = new BorrowableBook("Adventure Works Cycles");
IBorrowable dvd = new BorrowableDVD("Graphic Design Institute");
// Create libraries
Library bookLibrary = new Library(book);
Library dvdLibrary = new Library(dvd);
// Borrow items
bookLibrary.BorrowItem();
bookLibrary.BorrowItem(); // Try borrowing again
Console.WriteLine();
dvdLibrary.BorrowItem();
dvdLibrary.BorrowItem(); // Try borrowing again
}
}
A saída demonstra a flexibilidade do sistema refatorado:
You have borrowed "Adventure Works Cycles".
"Adventure Works Cycles" is already borrowed.
You have borrowed the DVD "Graphic Design Institute".
The DVD "Graphic Design Institute" is already borrowed.
Este exemplo refatorado demonstra como as interfaces reduzem dependências e melhoram a modularidade:
-
Separação de preocupações: A
IBorrowableinterface isola o comportamento de empréstimo, garantindo que aLibraryclasse não dependa de implementações específicas. -
Flexibilidade melhorada: Você pode adicionar novos tipos de itens emprestáveis (por exemplo, DVDs) sem modificar a
Libraryclasse. - Manutenção simplificada: O sistema é mais fácil de entender, testar e estender porque as responsabilidades são claramente divididas.