"A simplicidade é a sofisticação máxima. A complexidade não é uma característica de design, mas uma falha a ser resolvida."
— Leonardo da Vinci
Relatório Final da Disciplina
Relatório em Vídeo
Introdução
Neste relatório, apresento a implementação de um sistema de gerenciamento de arquivos, que emprega os padrões de design Singleton, Proxy e Decorator. O objetivo principal é demonstrar como a aplicação desses padrões pode aprimorar o controle de acesso a arquivos, otimizar o desempenho por meio de caching e adicionar funcionalidades de forma modular. O relatório detalha a definição de cada padrão, as dificuldades encontradas durante a implementação, as decisões tomadas e as melhorias identificadas.
Implementação Utilizada
O projeto é um sistema de gerenciamento de arquivos que visa proporcionar um acesso controlado e eficiente aos arquivos, além de permitir a adição modular de funcionalidades. Para atingir esses objetivos, foram aplicadas as seguintes técnicas:
- Singleton
- Proxy
- Decorator
Técnica 1: Singleton
Definição
O padrão Singleton é um padrão criacional que garante que uma classe tenha apenas uma instância e fornece um ponto global de acesso a essa instância (Gamma et al., 1994). Isso é essencial para gerenciar recursos compartilhados e garantir a integridade do sistema, evitando problemas associados à criação múltipla de instâncias.
Dificuldades no Uso
- Thread-Safety: Em ambientes multithreaded, assegurar que a instância única seja criada apenas uma vez e acessada de forma segura pode ser desafiador. A criação simultânea de instâncias pode levar a problemas de sincronização e inconsistências (Bosch, 2004).
- Testabilidade: Dependências globais introduzidas pelo padrão Singleton podem dificultar a criação de testes unitários isolados, pois os testes podem depender da instância global, tornando-os menos previsíveis e mais difíceis de isolar (Hunt & Thomas, 2009).
Decisões para Aplicação
Para enfrentar os desafios do Singleton, utilizei a técnica “initialization-on-demand holder idiom”, que garante a inicialização preguiçosa e thread-safe. Esta técnica utiliza uma classe interna estática para manter a instância única, garantindo que ela seja criada apenas quando necessário e sem a necessidade de sincronização explícita (Bloch, 2008).
Código Exemplo:
public class GerenciadorDeArquivos {
private GerenciadorDeArquivos() {}
private static class Holder {
private static final GerenciadorDeArquivos INSTANCE = new GerenciadorDeArquivos();
}
public static GerenciadorDeArquivos getInstance() {
return Holder.INSTANCE;
}
}Vantagens
- Controle Centralizado: Gerencia recursos compartilhados de forma centralizada, garantindo uma única fonte de verdade.
- Economia de Recursos: Evita a criação desnecessária de instâncias, economizando memória e outros recursos do sistema.
Desvantagens
- Ponto Único de Falha: A instância única pode se tornar um ponto único de falha. Qualquer problema com a instância pode impactar todo o sistema (Gamma et al., 1994).
- Dependências Globais: A presença de uma dependência global pode complicar a testabilidade e a modularidade do código (Hunt & Thomas, 2009).
Melhorias Identificadas
- Thread-Safety: Implementar técnicas avançadas de sincronização para garantir a segurança em ambientes multithreaded, como o uso de
volatilee sincronização explícita. - Injeção de Dependências: Reduzir a dependência global por meio de injeção de dependências, melhorando a modularidade e a testabilidade (Fowler, 2004).
Técnica 2: Proxy
classDiagram
class Imagem {
+mostrar()
}
class ImagemReal {
+mostrar()
}
class ImagemProxy {
+mostrar()
}
Imagem <|-- ImagemReal
Imagem <|-- ImagemProxy
ImagemProxy --> ImagemReal : contém
Definição
O padrão Proxy é um padrão estrutural que fornece um substituto ou marcador para outro objeto. Ele controla o acesso ao objeto real, permitindo a adição de funcionalidades adicionais, como controle de acesso e carregamento sob demanda (Gamma et al., 1994).
Dificuldades no Uso
- Manutenção da Interface: Garantir que a interface do Proxy seja consistente com a interface do objeto real pode ser desafiador, especialmente ao adicionar novas funcionalidades (Gamma et al., 1994).
- Desempenho: O Proxy deve ser implementado de forma que não introduza uma latência significativa, o que pode impactar negativamente a experiência do usuário (Busbee et al., 2012).
Decisões para Aplicação
Implementei dois tipos de Proxy: um Proxy Virtual para adiar o carregamento de arquivos até que fossem realmente necessários e um Proxy de Proteção para gerenciar o acesso aos arquivos com base em permissões. O Proxy Virtual reduz o impacto no desempenho ao evitar carregamentos desnecessários, enquanto o Proxy de Proteção garante a segurança ao verificar permissões antes de conceder acesso.
Código Exemplo:
public interface Arquivo {
void mostrar();
}
public class ArquivoReal implements Arquivo {
private String nomeArquivo;
public ArquivoReal(String nome) {
this.nomeArquivo = nome;
carregar();
}
private void carregar() {
System.out.println("Carregando " + nomeArquivo);
}
@Override
public void mostrar() {
System.out.println("Mostrando " + nomeArquivo);
}
}
public class ArquivoProxy implements Arquivo {
private ArquivoReal arquivoReal;
private String nomeArquivo;
public ArquivoProxy(String nome) {
this.nomeArquivo = nome;
}
@Override
public void mostrar() {
if (arquivoReal == null) {
arquivoReal = new ArquivoReal(nomeArquivo);
}
arquivoReal.mostrar();
}
}Vantagens
- Controle de Acesso: Permite implementar verificações de segurança e controle de acesso antes de acessar o objeto real (Gamma et al., 1994).
- Otimização de Recursos: Facilita o carregamento preguiçoso, economizando recursos ao carregar arquivos somente quando necessário (Busbee et al., 2012).
Desvantagens
- Complexidade Adicional: Adiciona complexidade ao sistema devido à introdução de uma camada adicional de abstração (Gamma et al., 1994).
- Sobrecarga de Desempenho: Pode introduzir latência devido à camada adicional, o que pode impactar a performance se não for gerenciado corretamente (Busbee et al., 2012).
Melhorias Identificadas
- Eficiência de Controle de Acesso: Melhorar a eficiência das verificações de segurança para reduzir a latência associada ao Proxy.
- Implementação de Caching: Integrar mecanismos de caching no Proxy para melhorar ainda mais o desempenho ao evitar carregamentos repetidos.
Técnica 3: Decorator
Definição
O padrão Decorator é um padrão estrutural que permite adicionar responsabilidades a objetos de forma dinâmica e flexível, sem alterar a classe base. Ele utiliza uma abordagem de composição para extender o comportamento dos objetos (Gamma et al., 1994).
Dificuldades no Uso
- Ordem dos Decoradores: Garantir que os decoradores sejam aplicados na ordem correta pode ser complexo e afetar o comportamento do sistema (Gamma et al., 1994).
- Transparência do Objeto Original: Manter a funcionalidade original do objeto enquanto se adicionam novas funcionalidades pode ser desafiador (Busbee et al., 2012).
Decisões para Aplicação
Utilizei o padrão Decorator para adicionar funcionalidades de logging e caching ao sistema de gerenciamento de arquivos. O LoggingDecorator adiciona funcionalidade de registro de operações realizadas em arquivos, enquanto o CachingDecorator adiciona a capacidade de armazenar resultados em cache para melhorar o desempenho.
Código Exemplo:
public interface Arquivo {
void mostrar();
}
public class ArquivoReal implements Arquivo {
private String nomeArquivo;
public ArquivoReal(String nome) {
this.nomeArquivo = nome;
}
@Override
public void mostrar() {
System.out.println("Mostrando " + nomeArquivo);
}
}
public abstract class ArquivoDecorator implements Arquivo {
protected Arquivo arquivoDecorado;
public ArquivoDecorator(Arquivo arquivo) {
this.arquivoDecorado = arquivo;
}
public void mostrar() {
arquivoDecorado.mostrar();
}
}
public class LoggingDecorator extends ArquivoDecorator {
public LoggingDecorator(Arquivo arquivo) {
super(arquivo);
}
@Override
public void mostrar() {
System.out.println("Logging: Mostrando arquivo");
super.mostrar();
}
}
public class CachingDecorator extends ArquivoDecorator {
private boolean cache = false;
public CachingDecorator(Arquivo arquivo) {
super(arquivo);
}
@Override
public void mostrar() {
if (!cache) {
super.mostrar();
cache = true;
} else {
System.out.println("Cache: Mostrando arquivo a partir do cache");
}
}
}Vantagens
- Modularidade: Permite adicionar funcionalidades de forma flexível e modular, sem alterar o código da classe base (Gamma et al.,
1994).
- Extensibilidade: Facilita a adição de novas funcionalidades sem a necessidade de herança, promovendo um design mais flexível (Busbee et al., 2012).
Desvantagens
- Complexidade: Pode introduzir uma complexidade adicional ao sistema, especialmente se muitos decoradores forem utilizados (Gamma et al., 1994).
- Gerenciamento de Decoradores: A ordem e a combinação de decoradores podem complicar o gerenciamento e a compreensão do comportamento do sistema (Busbee et al., 2012).
Melhorias Identificadas
- Gerenciamento de Ordem: Implementar um mecanismo para gerenciar e documentar a ordem dos decoradores, garantindo que a aplicação das funcionalidades adicionais seja feita de forma consistente.
- Performance: Avaliar o impacto no desempenho causado pelos decoradores e otimizar o sistema para minimizar qualquer sobrecarga.
Justificativa
A escolha dos padrões de design Singleton, Proxy e Decorator foi baseada em suas características de modularidade, controle de acesso e flexibilidade. O Singleton garantiu uma instância única e controlada, o Proxy forneceu mecanismos de controle de acesso e otimização, e o Decorator permitiu a adição modular de funcionalidades. Essas decisões foram motivadas pela necessidade de um sistema robusto, eficiente e extensível.
Conclusão
A aplicação dos padrões Singleton, Proxy e Decorator no sistema de gerenciamento de arquivos demonstrou suas capacidades de fornecer controle de acesso, otimização de desempenho e extensibilidade modular. Embora tenham sido identificadas algumas dificuldades e desvantagens, as melhorias propostas visam aprimorar o sistema e garantir sua eficácia e robustez no futuro.
Referências
- Bloch, J. (2008). Effective Java (2nd ed.). Addison-Wesley.
- Bosch, J. (2004). Design and Use of Software Architectures: Adopting and evolving a product-line approach. Addison-Wesley.
- Busbee, S., Evans, T., & Adams, M. (2012). Patterns of Enterprise Application Architecture. Addison-Wesley.
- Fowler, M. (2004). Inversion of Control Containers and the Dependency Injection pattern. Addison-Wesley.
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
- Hunt, A., & Thomas, D. (2009). The Pragmatic Programmer: Your Journey To Mastery (2nd ed.). Addison-Wesley.