Padrões de Projeto em C++: quando e por quê
\n\nVocê já reescreveu o código várias vezes porque uma nova funcionalidade quebrava a arquitetura antiga? Ou passou horas debugando, tentando entender quem e por que alterou uma variável global? Familiar? Então bem-vindo ao mundo dos padrões de projeto.
\n\nPadrões não são uma pílula mágica. São soluções testadas por veteranos para problemas típicos. Eles não tornarão seu código perfeito, mas ajudarão você a falar a mesma língua que outros desenvolvedores e evitar as armadilhas nas quais milhares de programadores já caíram antes de você.
\n\nNeste artigo, vamos analisar os três principais grupos de padrões em C++: criacionais, estruturais e comportamentais. Vamos descobrir quando aplicá-los e quando é melhor evitá-los. Vamos lá!
\n\nPadrões Criacionais: controle sobre a criação de objetos
\n\nOs padrões criacionais são responsáveis pela criação flexível de objetos. Eles ocultam a lógica de instanciação e tornam o sistema independente de como seus componentes são criados.
\n\nSingleton
\n\nUm clássico. Garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela. Parece tentador, mas use com sabedoria — o Singleton frequentemente se torna um antipadrão se crescer até o tamanho de um "objeto-deus".
\n\nQuando usar: logger, gerenciador de configuração, pool de threads (thread pool), trabalho com hardware.
\n\nExemplo em C++ (thread-safe, C++11 e superior):
\n\nclass Logger {\npublic:\n static Logger& getInstance() {\n static Logger instance; // Inicializado uma vez, mesmo em multithreading\n return instance;\n }\n\n void log(const std::string& msg) {\n std::cout << "[LOG]: " << msg << std::endl;\n }\n\n // Proibindo cópia e atribuição\n Logger(const Logger&) = delete;\n Logger& operator=(const Logger&) = delete;\n\nprivate:\n Logger() = default; // Construtor privado\n ~Logger() = default;\n};\n\n// Uso\nLogger::getInstance().log("Sistema iniciado");\n\nFactory Method (Método Fábrica)
\n\nDefine uma interface para criar um objeto, mas permite que subclasses alterem o tipo do objeto criado. É ideal quando você não sabe de antemão qual classe será necessária.
\n\nQuando usar: parsing de diferentes formatos (JSON, XML, YAML), criação de elementos de UI para diferentes SOs, trabalho com diferentes bancos de dados.
\n\nExemplo:
\n\n// Produto abstrato\nclass Document {\npublic:\n virtual void open() = 0;\n virtual ~Document() = default;\n};\n\n// Produtos concretos\nclass PDFDocument : public Document {\n void open() override { std::cout << "Abrindo PDF" << std::endl; }\n};\n\nclass WordDocument : public Document {\n void open() override { std::cout << "Abrindo Word" << std::endl; }\n};\n\n// Fábrica\nclass Application {\npublic:\n virtual Document* createDocument() = 0; // Método Fábrica\n virtual ~Application() = default;\n\n void newDocument() {\n Document* doc = createDocument();\n doc->open();\n delete doc; // Em código real — smart pointers\n }\n};\n\nclass PDFApp : public Application {\n Document* createDocument() override { return new PDFDocument(); }\n};\n\n// Uso\nPDFApp app;\napp.newDocument(); // Abrindo PDF\n\nPadrões Estruturais: montando o quebra-cabeça
\n\nEsses padrões ajudam a organizar classes e objetos em estruturas maiores, mantendo flexibilidade e eficiência.
\n\nAdapter (Adaptador)
\n\nPermite que objetos com interfaces incompatíveis trabalhem juntos. Imagine que você tem uma biblioteca antiga para trabalhar com XML, mas um novo módulo espera JSON. O Adapter é seu salvador.
\n\nQuando usar: