Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Padrões de Projetos

Análise de códigos com Padrões de Projeto de Software. Nesse documento veremos 3 exemplos de Padrões de Projeto (Design Patterns):

  1. Singleton (Padrão Criacional)

  2. Bridge (Padrão Estrutural)

  3. Mediator (Padrão Comportamental)


  • Singleton:

É um padrão de projeto criacional que serve para garantir uma única instância de um objeto e ainda proporciar um lugar de acesso global para qualquer parte do código. É possível utilizar o singleton quando seu programa deve ter apenas uma instância da classe a disposição dos clientes. Ele limita a classe a apenas um meio de criação de objeto, anulando todos os outros. E também, quando for preciso retringir o acesso a uma varíavel global, ou seja, só a classe singleton pode substituir a instância criada pela mesma. A implemetação desse padrão é comumente feita por criar um construtor da classe com encapsulamento privado, impedindo que outros objetos usem do operador new, e ainda criar um método estático com encapsulamento público para realizar a instância da classe em um campo estático, retornado sempre a mesma instância em chamadas posteriores.

[Veja um exemplo do Singleton em Python, retirada do site GeeksforGeeks] (

# código retirado do site GeeksforGeeks
class Singleton(object):
def __new__(cls):
if not hasattr(cls, '__instance'):
cls.__instance = super(Singleton, cls).__new__(cls)
return cls.__instance
singleton = Singleton()
new_singleton = Singleton()
print(singleton is new_singleton)
singleton.singl_variable = "Singleton Variable"
print(new_singleton.singl_variable)
)

Observe que o método __new__ é responsável por criar e retornar uma instância da classe Singleton e também verifica a existência de um atributo __instance com o método hasattr() caso não tenha ele cria e armazena em cls.__instance. Se a classe Singleton é instanciada pela primeira vez, na linha 9, a verificação hasattr(cls, '__instance') retorna False, pois o atributo __instance ainda não existe. Quando a classe Singleton for instanciada novamente, a verificação hasattr(cls, '__instance') retorna True. Logo, a mesma instância é retornada, sem criar uma nova. O método print(singleton is new_singleton) está verificando se as variáveis singleton e new_singleton referenciam a mesma instância. Como o padrão Singleton está sendo seguido corretamente, a saída será True. A linha 14 define um atributo singl_variable na instância singleton. O print(new_singleton.singl_variable) vai imprimir "Singleton Variable", pois new_singleton referencia a mesma instância da classe.

  • Abaixo encontra-se o diagrama da classe Singleton no padrão UML.

Diagrama UML da classe Singleton

Fonte: commons.wikimedia.org

Nota: Cabe dizer, que embora o Singleton resolva dois problemas ele infringe o princípio de Responsabilidade Única, dos princípios SOLID.


  • Bridge:

É um padrão de projeto estrutural usado para evitar que um conjunto de classes, ou classe grande, tenha uma ligação permanente entre a abstração e a implementação, dividindo em duas hierarquias independentes entre si. Assim, diminuindo o acoplamento entre elas. Esse padrão é usado quando nos deparamos com problemas de escalabilidade, que consiste em expandir o software de modo eficiente. A solução abordada para o Bridge é de utilizar o relacionamento de composição entre a implementação e abstração, ao invés de herança, extraindo hierarquias de classes. Desse modo, será possível modificar as classes de maneira independente em relação a outra hierarquia. Porque, a classe de abstração não vai se encarregar de todos os trabalhos. O trabalho será delegado ao objeto de implementação.

[Veja um exemplo do padrão Bridge em C++, adpatado do site GeeksforGeeks.] (

// Fonte: site GeeksforGeeks
#include <iostream>
using namespace std;
// Abstraction: Shape
class Shape {
public:
virtual void draw() = 0;
};
// Implementations: Renderer (VectorRenderer and
// RasterRenderer)
class Renderer {
public:
virtual void render() = 0;
};
class VectorRenderer : public Renderer {
public:
void render() override
{
cout << "Rendering as a vector\n";
}
};
class RasterRenderer : public Renderer {
public:
void render() override
{
cout << "Rendering as a raster\n";
}
};
// Concrete Abstractions: Circle and Square
class Circle : public Shape {
public:
Circle(Renderer& renderer)
: renderer(renderer)
{
}
void draw() override{
cout << "Drawing a circle\n";
renderer.render();
}
private:
Renderer& renderer;
};
class Square : public Shape {
public:
Square(Renderer& renderer)
: renderer(renderer)
{
}
void draw() override{
cout << "Drawing a square\n";
renderer.render();
}
private:
Renderer& renderer;
};
int main()
{
VectorRenderer vectorRenderer;
RasterRenderer rasterRenderer;
Circle circle(vectorRenderer);
Square square(rasterRenderer);
circle.draw(); // Output: Drawing a circle Rendering as
// a vector
square.draw(); // Output: Drawing a square Rendering as
// a raster
return 0;
}
)

Observe que o código possui duas classes abstratas: Shape (abstração) que define a interface para os tipos de formas Circle e Square. Ela possui um método virtual puro draw(), que será implementado pelas subclasses. E Renderer(implementação) que define a interface para as formas de renderização, VectorRenderer e RasterRenderer. Essa classe tem o método virtual puro render(), o qual será implementado pelas classes concretas. As classes concretas VectorRenderer e RasterRenderer herdam da implementação Renderer e escrevem o método render(). Já as classes Circle e Square escrevem o código draw() da abstração Square. Ambas as classes estão dependendo de uma referência ou instância de Renderer, passada no método construtor da classe, que vai realizar o processo de renderização específico de cada forma. As instâncias de VectorRenderer e RasterRenderer são então criadas e passadas para as formas de Circle e Square na função main(). Assim, as formas circle.draw() e square.draw() desenham e chamam a renderização de vetor ou raster correspondente.

  • Encontra-se abaixo o diagrama do Bridge no padrão UML.

Diagrama UML do padrão Bridge

Fonte: commons.wikimedia.org

  • Mediator:

É um padrão de projeto comportamental que serve para reduzir as dependências entre objetos, diminuindo o nível de acoplamento entre eles. Ele limita as colaborações dos objetos para se comunicarem apenas com um objeto mediador, o qual é responsável por enviar as mensagens para os demais. O Mediator é usado para casos em que há dificuldade de mudança nas classes, devido ao acoplamento com outras e também em casos que é preciso reutilizar o código mas ele dependente muito de outros componentes. Nesses casos, o padrão diz que os componentes devem colaborar indiretamente uns com os outros, focando a comunicação em única classe mediadora. Desse modo, os componentes estaram alheios aos outros, aumentando a facilidade de mudança e reutilização do código.

[Veja um exemplo do padrão Mediator em Java, retirado do site GeeksforGeeks] (

//Fonte: GeeksforGeeks
public interface Mediator {
// The mediator interface
public void addBuyer(Buyer buyer);
public void findHighestBidder();
}
public class AuctionMediator implements Mediator {
// this class implements the interface and holds
// all the buyers in a Array list.
// We can add buyers and find the highest bidder
private ArrayList buyers;
public AuctionMediator()
{
buyers = new ArrayList<>();
}
@Override
public void addBuyer(Buyer buyer)
{
buyers.add(buyer);
System.out.println(buyer.name + " was added to" +
"the buyers list.");
}
@Override
public void findHighestBidder()
{
int maxBid = 0;
Buyer winner = null;
for (Buyer b : buyers) {
if (b.price > maxBid) {
maxBid = b.price;
winner = b;
}
}
System.out.println("The auction winner is " + winner.name +
". He paid " + winner.price + "$ for the item.");
}
}
public abstract class Buyer {
// this class holds the buyer
protected Mediator mediator;
protected String name;
protected int price;
public Buyer(Mediator med, String name)
{
this.mediator = med;
this.name = name;
}
public abstract void bid(int price);
public abstract void cancelTheBid();
}
public class AuctionBuyer extends Buyer {
// implementation of the bidding process
// There is an option to bid and an option to
// cancel the bidding
public AuctionBuyer(Mediator mediator,
String name)
{
super(mediator, name);
}
@Override
public void bid(int price)
{
this.price = price;
}
@Override
public void cancelTheBid()
{
this.price = -1;
}
}
public class Main {
/* This program illustrate an auction. The AuctionMediator
is responsible for adding the buyers, and after each
buyer bid a certain amount for the item, the mediator
know who won the auction. */
public static void main(String[] args)
{
AuctionMediator med = new AuctionMediator();
Buyer b1 = new AuctionBuyer(med, "Tal Baum");
Buyer b2 = new AuctionBuyer(med, "Elad Shamailov");
Buyer b3 = new AuctionBuyer(med, "John Smith");
// Create and add buyers
med.addBuyer(b1);
med.addBuyer(b2);
med.addBuyer(b3);
System.out.println("Welcome to the auction. Tonight " +
"we are selling a vacation to Vegas." +
" please Bid your offers.");
System.out.println("--------------------------------" +
"---------------");
System.out.println("Waiting for the buyer's offers...");
// Making bids
b1.bid(1800);
b2.bid(2000);
b3.bid(780);
System.out.println("---------------------------------" +
"--------------");
med.findHighestBidder();
b2.cancelTheBid();
System.out.print(b2.name + " Has canceled his bid!, " +
"in that case ");
med.findHighestBidder();
}
}
)

Observe que a interface Mediator está definindo os métodos, addBuyer() e findHighestBidder() que devem ser implementados por qualquer classe mediadora derivada dela. A classe derivada AuctionMediator implementa os métodos da interface Mediator e armazena uma lista de compradores (buyers). A classe abstrata Buyer estabelece os métodos bid(int price) e cancelTheBid() que devem ser implementados por qualquer comprador. Repare que cada comprador tem uma referência a interface Mediator para a interação entre elas, além de armazenar o nome e o valor do lance (price). A classe AuctionBuyer é uma implementação concreta de Buyer. Perceba que Buyer e AuctionMediator interajam de forma desacoplada, ou seja, se comportamento do leilão precisar mudar, é possível modificar AuctionMediator sem impactar o Buyer diretamente. A função main() está criando uma instância do mediador AuctionMediator e também adicionando três compradores (b1, b2 e b3). Cada comprador faz um lance, e o mediador escolhe o lance mais alto. O mediador determina novamente quem tem o lance mais alto entre os compradores restantes após o cancelamento do lance b2.

  • Veja abaixo o diagrama do Mediator no padrão UML. Diagrama UML do Mediator

Fonte: refactoring.guru

  • Referências

Refactoring Guru
https://refactoring.guru/pt-br/design-patterns

https://refactoring.guru/pt-br/design-patterns/singleton

https://refactoring.guru/pt-br/design-patterns/bridge

https://refactoring.guru/pt-br/design-patterns/mediator

GeeksforGeeks
https://www.geeksforgeeks.org/singleton-pattern-in-python-a-complete-guide/

https://www.geeksforgeeks.org/bridge-method-c-design-patterns/

https://www.geeksforgeeks.org/mediator-design-pattern-in-java/

Wikimedia Commons
https://commons.wikimedia.org/wiki/File:Singleton_pattern_uml.png

https://commons.wikimedia.org/wiki/File:Bridge_UML_class_diagram.svg

About

Análise de códigos com Padrões de Projeto de Software

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages