Já revisamos o uso de um objeto singleton, mas talvez você ainda não tenha percebido que essa estratégia é um padrão de projeto e um dos mais usados.

Na verdade, existem muitos desses padrões e eles podem ser classificados de acordo com sua finalidade específica.

Classificação de padrões

Tipo de Padrão Aplicativo
criacional Um tipo que resolve o problema de criação de objetos
Estrutural Padrões que nos permitem construir uma hierarquia de classes correta e extensível em nossa arquitetura
Comportamental Esse agrupamento de padrões facilita a interação segura e conveniente entre objetos em um programa.

Normalmente, um padrão é caracterizado pelo problema que resolve. Vamos dar uma olhada em alguns padrões que encontramos com mais frequência ao trabalhar com Java:

Padrão Propósito
solteiro Já estamos familiarizados com esse padrão — nós o usamos para criar e acessar uma classe que não pode ter mais de uma instância.
Iterador Também estamos familiarizados com este. Sabemos que esse padrão nos permite iterar sobre um objeto de coleção sem revelar sua representação interna. É usado com coleções.
Adaptador Esse padrão conecta objetos incompatíveis para que possam trabalhar juntos. Acho que o nome do padrão do adaptador ajuda você a imaginar exatamente o que ele faz. Aqui está um exemplo simples da vida real: um adaptador USB para uma tomada de parede.
Método de modelo

Um padrão de programação comportamental que resolve o problema de integração e permite alterar etapas algorítmicas sem alterar a estrutura de um algoritmo.

Imagine que temos um algoritmo de montagem de carros na forma de uma sequência de etapas de montagem:

Chassi -> Carroceria -> Motor -> Interior da Cabine

Se colocarmos uma estrutura reforçada, um motor mais potente ou um interior com iluminação adicional, não precisamos alterar o algoritmo e a sequência abstrata permanece a mesma.

Decorador Esse padrão cria wrappers para objetos para fornecer funcionalidades úteis. Vamos considerá-lo como parte deste artigo.

Em Java.io, as seguintes classes implementam padrões:

padrão decorador

Vamos imaginar que estamos descrevendo um modelo para o projeto de uma casa.

Em geral, a abordagem é assim:

Inicialmente, temos uma escolha de vários tipos de casas. A configuração mínima é de um andar com telhado. Em seguida, usamos todos os tipos de decoradores para alterar parâmetros adicionais, o que afeta naturalmente o preço da casa.

Criamos uma classe abstrata House:

public abstract class House {
	String info;

	public String getInfo() {
    	return info;
	}

	public abstract int getPrice();
}

Aqui temos 2 métodos:

  • getInfo() retorna informações sobre o nome e características de nossa casa;
  • getPrice() retorna o preço da configuração atual da casa.

Também temos implementações padrão da Casa — tijolo e madeira:

public class BrickHouse extends House {

	public BrickHouse() {
    	info = "Brick House";
	}

	@Override
	public int getPrice() {
    	return 20_000;
	}
}

public class WoodenHouse extends House {

	public WoodenHouse() {
    	info = "Wooden House";
	}

	@Override
	public int getPrice() {
    	return 25_000;
	}
}

Ambas as classes herdam a classe House e substituem seu método de preço, definindo um preço personalizado para uma casa padrão. Definimos o nome no construtor.

Em seguida, precisamos escrever classes de decorador. Essas classes também herdarão a classe House . Para fazer isso, criamos uma classe de decorador abstrato.

É aí que colocaremos a lógica adicional para alterar um objeto. Inicialmente, não haverá lógica adicional e a classe abstrata estará vazia.

abstract class HouseDecorator extends House {
}

Em seguida, criamos implementações de decorador. Vamos criar várias classes que nos permitem adicionar recursos adicionais à casa:

public class SecondFloor extends HouseDecorator {
	House house;

	public SecondFloor(House house) {
    	this.house = house;
	}

	@Override
	public int getPrice() {
    	return house.getPrice() + 20_000;
	}

	@Override
	public String getInfo() {
    	return house.getInfo() + " + second floor";
	}
}
Um decorador que adiciona um segundo andar à nossa casa

O construtor decorador aceita uma casa que iremos "decorar", ou seja, adicionar modificações. E sobrescrevemos os métodos getPrice() e getInfo() , retornando informações sobre a nova casa atualizada com base na antiga.

public class Garage extends HouseDecorator {

	House house;
	public Garage(House house) {
    	this.house = house;
	}

	@Override
	public int getPrice() {
    	return house.getPrice() + 5_000;
	}

	@Override
	public String getInfo() {
    	return house.getInfo() + " + garage";
	}
}
Um decorador que adiciona uma garagem à nossa casa

Agora podemos atualizar nossa casa com decoradores. Para fazer isso, precisamos criar uma casa:

House brickHouse = new BrickHouse();

A seguir, definimos nossocasavariável igual a um novo decorador, passando em nossa casa:

brickHouse = new SecondFloor(brickHouse);

Nossocasavariável agora é uma casa com um segundo andar.

Vejamos os casos de uso envolvendo decoradores:

código de exemplo Saída
House brickHouse = new BrickHouse();

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

casa de tijolos

20000

House brickHouse = new BrickHouse();

  brickHouse = new SecondFloor(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

Casa de tijolos + segundo andar

40000

House brickHouse = new BrickHouse();


  brickHouse = new SecondFloor(brickHouse);
  brickHouse = new Garage(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

Casa de tijolos + segundo andar + garagem

45000

House woodenHouse = new SecondFloor(new Garage(new WoodenHouse()));

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

Casa de madeira + garagem + segundo andar

50000

House woodenHouse = new WoodenHouse();

  House woodenHouseWithGarage = new Garage(woodenHouse);

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

  System.out.println(woodenHouseWithGarage.getInfo());
  System.out.println(woodenHouseWithGarage.getPrice());

casa de madeira

25000

casa de madeira + garagem

30000

Este exemplo ilustra o benefício de atualizar um objeto com um decorador. Então não mudamos ocasa de madeiraobjeto em si, mas em vez disso criou um novo objeto baseado no antigo. Aqui podemos ver que as vantagens vêm com desvantagens: criamos um novo objeto na memória a cada vez, aumentando o consumo de memória.

Veja este diagrama UML do nosso programa:

Um decorador tem uma implementação super simples e altera os objetos dinamicamente, atualizando-os. Os decoradores podem ser reconhecidos por seus construtores, que recebem como parâmetros objetos do mesmo tipo abstrato ou interface da classe atual. Em Java, esse padrão é amplamente utilizado em classes de E/S.

Por exemplo, como já observamos, todas as subclasses de java.io.InputStream , OutputStream , Reader e Writer possuem um construtor que aceita objetos das mesmas classes.