Padrões de Projeto - Padrões Estruturais
Tags: Design Patterns, Desenvolvimento, Adapter, Bridge, Composite, Decorator, Façade, Flyweight, Proxy, C#, dotnet
Neste post, vamos explorar os padrões de projeto estruturais, que se concentram na composição de classes e objetos para formar estruturas maiores. Esses padrões ajudam a garantir que as partes de um sistema sejam organizadas de maneira eficiente e flexível.
Conhecer os padrões estruturais aumenta a nossa capacidade de criar sistemas flexíveis e eficientes, permitindo que as partes do sistema sejam organizadas de maneira a facilitar a manutenção e a evolução ao longo do tempo.
Adapter
O padrão Adapter é realmente autodescritivo. Ele é usado para permitir que classes com interfaces incompatíveis trabalhem juntas. O Adapter atua como um tradutor entre as interfaces, permitindo que objetos de uma classe sejam usados como se fossem de outra classe.
Vamos imaginar que temos um aplicativo que precisa usar uma biblioteca de terceiros para processar pagamentos, mas a interface dessa biblioteca é diferente da interface que nosso aplicativo espera. O Adapter pode ser implementado para adaptar a interface da biblioteca de terceiros à interface que nosso aplicativo espera, permitindo que o código do nosso aplicativo continue funcionando sem grandes alterações.

Primeiro, definimos a interface que o cliente espera usar:
// Interface que o cliente espera usar
public interface ITarget
{
void Request(int data);
}
Em seguida, temos a classe existente que precisa ser adaptada:
// Classe que precisa ser adaptada
public class Adaptee
{
public void SpecificRequest(int data)
{
Console.WriteLine($"Adaptee received: {data}");
}
}
Agora, criamos o Adapter que implementa a interface esperada pelo cliente e traduz as chamadas para a interface da classe existente:
// Adapta a interface do Adaptee para a interface ITarget
public class Adapter : ITarget
{
private readonly Adaptee _adaptee;
public Adapter(Adaptee adaptee)
{
_adaptee = adaptee;
}
public void Request(int data)
{
// Tradução da chamada para a interface da classe adaptada
_adaptee.SpecificRequest(data);
}
}
Com essa implementação, o cliente pode usar a interface ITarget para fazer chamadas, e o Adapter será o responsável por traduzir essas chamadas para a interface da classe Adaptee, permitindo que as duas classes trabalhem juntas sem problemas.
public class Program
{
public static void Main(string[] args)
{
Adaptee adaptee = new Adaptee();
ITarget target = new Adapter(adaptee);
target.Request(42);
}
}
// Output: Adaptee received: 42
Bridge
O padrão Bridge é usado para separar a abstração de sua implementação. Isso é útil quando queremos evitar que a implementação e a abstração estejam fortemente acopladas, permitindo que ambas possam variar independentemente.

No padrão Bridge, temos uma hierarquia de classes para a abstração e outra hierarquia de classes para a implementação. A abstração contém uma referência à implementação, e as duas hierarquias podem ser desenvolvidas independentemente.
Primeiro, definimos a hierarquia de classes para a implementação:
// O Implementor define a interface para as implementações concretas
public interface IImplementor
{
void OperationImpl();
}
// As ConcreteImplementorA e ConcreteImplementorB implementam a interface do Implementor
public class ConcreteImplementorA : IImplementor
{
public void OperationImpl()
{
Console.WriteLine("ConcreteImplementorA: OperationImpl");
}
}
public class ConcreteImplementorB : IImplementor
{
public void OperationImpl()
{
Console.WriteLine("ConcreteImplementorB: OperationImpl");
}
}
Em seguida, definimos a hierarquia de classes para a abstração:
// A Abstraction define a interface para as abstrações e mantém uma referência para um objeto Implementor
public abstract class Abstraction
{
protected IImplementor _implementor;
public Abstraction(IImplementor implementor)
{
_implementor = implementor;
}
public abstract void Operation();
}
// A RefinedAbstraction estende a Abstraction e implementa a operação usando o Implementor
public class RefinedAbstraction : Abstraction
{
public RefinedAbstraction(IImplementor implementor) : base(implementor)
{
}
public override void Operation()
{
Console.WriteLine("RefinedAbstraction: Operation");
_implementor.OperationImpl();
}
}
Com essa estrutura, podemos criar diferentes combinações de abstração e implementação sem que elas estejam acopladas, permitindo que cada uma possa evoluir independentemente.
public class Program
{
public static void Main(string[] args)
{
IImplementor implementorA = new ConcreteImplementorA();
Abstraction abstractionA = new RefinedAbstraction(implementorA);
abstractionA.Operation();
IImplementor implementorB = new ConcreteImplementorB();
Abstraction abstractionB = new RefinedAbstraction(implementorB);
abstractionB.Operation();
}
}
// Output:
// RefinedAbstraction: Operation
// ConcreteImplementorA: OperationImpl
// RefinedAbstraction: Operation
// ConcreteImplementorB: OperationImpl
Composite
O padrão Composite é usado para compor objetos em estruturas de árvore para representar hierarquias parte-todo. Ele permite que os clientes tratem objetos individuais e composições de objetos de maneira uniforme.

No padrão Composite, temos uma interface comum para os objetos individuais (folhas) e para as composições (nós). Isso permite que os clientes possam interagir com ambos de maneira transparente.
Primeiro, definimos a interface comum para os componentes:
// Classe abstrata Component define a interface para os objetos na composição.
public abstract class Component
{
public abstract void Operation();
public virtual void Add(Component component)
{
throw new NotImplementedException();
}
public virtual void Remove(Component component)
{
throw new NotImplementedException();
}
public virtual Component GetChild(int index)
{
throw new NotImplementedException();
}
}
Em seguida, temos a classe Leaf, que representa os objetos individuais na composição:
// Leaf é um objeto final na composição. Ele não tem filhos.
public class Leaf : Component
{
public override void Operation()
{
Console.WriteLine("Leaf operation");
}
}
E a classe Composite, que representa os nós na composição e pode conter outros componentes:
// Composite é um objeto que pode ter filhos. Ele implementa a interface Component e delega as operações para seus filhos.
public class Composite : Component
{
private List<Component> _children = new List<Component>();
public override void Operation()
{
Console.WriteLine("Composite operation");
foreach (var child in _children)
{
child.Operation();
}
}
public override void Add(Component component)
{
_children.Add(component);
}
public override void Remove(Component component)
{
_children.Remove(component);
}
public override Component GetChild(int index)
{
return _children[index];
}
}
Com essa estrutura, podemos criar composições de objetos e tratá-los de maneira uniforme, seja um objeto individual ou uma composição de objetos.
public class Program
{
public static void Main(string[] args)
{
// Criando a estrutura de composição
Composite root = new Composite();
Leaf leaf1 = new Leaf();
Leaf leaf2 = new Leaf();
root.Add(leaf1);
root.Add(leaf2);
Composite subComposite = new Composite();
Leaf leaf3 = new Leaf();
subComposite.Add(leaf3);
root.Add(subComposite);
// Operação na estrutura de composição
root.Operation();
// Output:
// Composite operation
// Leaf operation
// Leaf operation
// Composite operation
// Leaf operation
}
}
Decorator
O padrão Decorator é usado para adicionar responsabilidades adicionais a um objeto dinamicamente. Ele é uma maneira flexível de estender funcionalidades.

No padrão Decorator, temos uma interface comum para os objetos decorados e os decoradores. O decorador contém uma referência ao objeto que está decorando e pode adicionar comportamento antes ou depois de delegar as chamadas para o objeto decorado.
Primeiro, definimos a interface comum para os objetos decorados:
// Component é a interface que define as operações que podem ser decoradas.
public abstract class Component
{
public abstract void Operation();
}
// Decorator é a classe abstrata que implementa a interface Component e tem uma referência para um objeto Component. Ela delega as operações para o objeto Component.
public abstract class Decorator : Component
{
protected Component _component;
public Decorator(Component component)
{
_component = component;
}
public override void Operation()
{
if (_component != null)
{
_component.Operation();
}
}
}
Em seguida, temos a classe ConcreteComponent, que é o objeto que pode ser decorado:
// ConcreteComponent é a classe concreta que implementa a interface Component. Ela é o objeto que será decorado.
public class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("ConcreteComponent operation");
}
}
E a classe ConcreteDecorator, que é o decorador concreto que adiciona comportamento ao objeto decorado:
// ConcreteDecoratorA é uma classe concreta que estende a classe Decorator. Ela adiciona comportamento adicional à operação do objeto Component.
public class ConcreteDecoratorA : Decorator
{
public ConcreteDecoratorA(Component component) : base(component)
{
}
public override void Operation()
{
base.Operation();
Console.WriteLine("ConcreteDecoratorA operation");
}
}
// ConcreteDecoratorB é outra classe concreta que estende a classe Decorator. Ela também adiciona comportamento adicional à operação do objeto Component.
public class ConcreteDecoratorB : Decorator
{
public ConcreteDecoratorB(Component component) : base(component)
{
}
public override void Operation()
{
base.Operation();
Console.WriteLine("ConcreteDecoratorB operation");
}
}
Com essa estrutura, podemos criar objetos decorados dinamicamente, adicionando responsabilidades adicionais sem modificar o código do objeto original.
public class Program
{
public static void Main(string[] args)
{
Component component = new ConcreteComponent();
Component decoratorA = new ConcreteDecoratorA(component);
Component decoratorB = new ConcreteDecoratorB(decoratorA);
decoratorB.Operation();
// Output:
// ConcreteComponent operation
// ConcreteDecoratorA operation
// ConcreteDecoratorB operation
}
}
🔥Quer ver mais exemplos sobre o padrão Decorator? Confira este post: Decorator Pattern: Entendendo o Padrão de Projeto Decorator.
Façade
O padrão Façade é usado para fornecer uma interface unificada para um conjunto de interfaces em um subsistema. Ele define uma fachada que deixa o subsistema mais fácil de usar.

No padrão Façade, temos uma classe Façade que fornece uma interface simplificada para um conjunto de classes em um subsistema. O cliente interage com a Façade em vez de interagir diretamente com as classes do subsistema, o que torna o sistema mais fácil de usar e entender.
Primeiro, definimos as classes do subsistema que a Façade irá simplificar:
public class SubsystemA
{
public void OperationA()
{
Console.WriteLine("SubsystemA operation");
}
}
public class SubsystemB
{
public void OperationB()
{
Console.WriteLine("SubsystemB operation");
var subsystemC = new SubsystemC();
subsystemC.OperationC();
}
}
public class SubsystemC
{
public void OperationC()
{
Console.WriteLine("SubsystemC operation");
}
}
Em seguida, criamos a classe Facade que fornece uma interface simplificada para o subsistema:
public class Facade
{
private SubsystemA _subsystemA;
private SubsystemB _subsystemB;
public Facade()
{
_subsystemA = new SubsystemA();
_subsystemB = new SubsystemB();
}
public void Operation()
{
Console.WriteLine("Facade operation:");
_subsystemA.OperationA();
_subsystemB.OperationB();
}
}
O cliente pode consumir a classe Facade e interagir com o subsistema sem se preocupar com a complexidade interna.
public class Program
{
public static void Main(string[] args)
{
Facade facade = new Facade();
facade.Operation();
// Output:
// Facade operation:
// SubsystemA operation
// SubsystemB operation
// SubsystemC operation
}
}
Flyweight
O padrão Flyweight é usado para minimizar o uso de memória compartilhando o máximo possível de dados entre objetos semelhantes. Ele é útil quando temos um grande número de objetos que compartilham muitos dados comuns.

No padrão Flyweight, temos uma classe Flyweight que representa os objetos compartilhados e uma classe FlyweightFactory que gerencia a criação e o compartilhamento dos objetos Flyweight. Os objetos Flyweight contêm os dados intrínsecos que são compartilhados, enquanto os dados extrínsecos são passados para os objetos Flyweight quando necessário.
Primeiro, definimos a classe Flyweight que representa os objetos compartilhados:
public abstract class Flyweight
{
public abstract void Operation(int extrinsicState);
}
Em seguida, temos a classe ConcreteFlyweight que implementa a classe Flyweight e contém os dados intrínsecos compartilhados:
public class ConcreteFlyweight : Flyweight
{
private string _intrinsicState;
public ConcreteFlyweight(string intrinsicState)
{
_intrinsicState = intrinsicState;
}
public override void Operation(int extrinsicState)
{
Console.WriteLine($"ConcreteFlyweight: Intrinsic State = {_intrinsicState}, Extrinsic State = {extrinsicState}");
}
}
public class UnsharedConcreteFlyweight : Flyweight
{
private string _state;
public UnsharedConcreteFlyweight(string state)
{
_state = state;
}
public override void Operation(int extrinsicState)
{
Console.WriteLine($"UnsharedConcreteFlyweight: State = {_state}, Extrinsic State = {extrinsicState}");
}
}
E a classe FlyweightFactory que gerencia a criação e o compartilhamento dos objetos Flyweight:
public class FlyweightFactory
{
private Dictionary<string, Flyweight> _flyweights = new Dictionary<string, Flyweight>();
public Flyweight GetFlyweight(string key)
{
if (!_flyweights.ContainsKey(key))
{
_flyweights[key] = new ConcreteFlyweight(key);
}
return _flyweights[key];
}
}
Com essa estrutura, podemos criar e compartilhar objetos Flyweight para minimizar o uso de memória, enquanto ainda fornecemos a funcionalidade necessária.
public class Program
{
public static void Main(string[] args)
{
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.GetFlyweight("A");
Flyweight flyweight2 = factory.GetFlyweight("B");
Flyweight flyweight3 = factory.GetFlyweight("A");
Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight("Unshared");
flyweight1.Operation(1);
flyweight2.Operation(2);
flyweight3.Operation(3);
unsharedFlyweight.Operation(4);
// Output:
// ConcreteFlyweight: Intrinsic State = A, Extrinsic State = 1
// ConcreteFlyweight: Intrinsic State = B, Extrinsic State = 2
// ConcreteFlyweight: Intrinsic State = A, Extrinsic State = 3
// UnsharedConcreteFlyweight: State = Unshared, Extrinsic State = 4
}
}
Proxy
O padrão Proxy é usado para fornecer um substituto ou representante de outro objeto para controlar o acesso a ele. Ele é útil quando queremos adicionar uma camada de controle ou proteção ao acessar um objeto.

No padrão Proxy, temos uma classe Proxy que implementa a mesma interface que o objeto real (RealSubject) e contém uma referência para o objeto real. O Proxy pode controlar o acesso ao objeto real, adicionando comportamento adicional antes ou depois de delegar as chamadas para o objeto real.
Primeiro, definimos a interface comum para o objeto real e o proxy:
public interface ISubject
{
void Request();
}
Em seguida, temos a classe RealSubject que implementa a interface ISubject e representa o objeto real:
public class RealSubject : ISubject
{
public void Request()
{
Console.WriteLine("RealSubject: Handling request");
}
}
E a classe Proxy que implementa a interface ISubject e controla o acesso ao RealSubject:
public class Proxy : ISubject
{
private RealSubject? _realSubject;
public void Request()
{
if (_realSubject == null)
{
_realSubject = new RealSubject();
}
_realSubject.Request();
}
}
Com essa estrutura, o cliente pode interagir com o Proxy, que controla o acesso ao RealSubject, permitindo que o RealSubject seja criado apenas quando necessário e adicionando uma camada de controle ao acessar o objeto real.
public class Program
{
public static void Main(string[] args)
{
Proxy proxy = new Proxy();
proxy.Request();
// Output:
// RealSubject: Handling request
}
}
O código fonte completo você encontra no repositório do GitHub
por Flávio José Formis,
23/03/2026
Aqui estão algumas recomendações relacionadas ao conteúdo. Esses itens podem ser comprados na Amazon com o meu link de associado.
Confira minhas recomendações de livros e outros itens na página de recomendações.