Neste ponto, você provavelmente já encontrou padrões de projeto. Por exemplo, singleton .

Vamos relembrar o que são padrões, por que são necessários e o que são padrões criacionais (singleton é um exemplo). Também estudaremos um novo padrão: o método de fábrica.

No desenvolvimento de software, um padrão de design é uma construção arquitetônica repetível que representa uma solução para um problema de design em algum contexto recorrente.

Normalmente, um padrão não é uma solução final que pode ser convertida diretamente em código. É apenas um modelo de solução para um problema que pode ser usado em várias situações.

Padrões criacionais são padrões de design que lidam com o processo de criação de objetos. Eles permitem criar um sistema independente do método usado para criar, compor e apresentar objetos.

Um método de fábrica é um padrão de design criacional que define uma interface comum para criar objetos em uma classe pai, dando a seus descendentes a capacidade de criar esses objetos. No momento da criação, os descendentes podem determinar qual classe criar.

Qual problema o padrão resolve?

Imagine que você decide criar um programa de delivery. Inicialmente, você contratará entregadores com carros e usará umCarroobjeto para representar um veículo de entrega no programa. Os correios entregam pacotes do ponto A ao ponto B e assim por diante. Mole-mole.

O programa está ganhando popularidade. Sua empresa está crescendo e você deseja expandir para novos mercados. Por exemplo, você pode começar também entregando comida e frete. Nesse caso, os alimentos podem ser entregues por mensageiros a pé, em patinetes e bicicletas, mas caminhões são necessários para o frete.

Agora você precisa acompanhar várias coisas (quando, para quem, o que e quanto será entregue), incluindo quanto cada correio pode carregar. Os novos modos de transporte têm diferentes velocidades e capacidades. Então você percebe que a maioria das entidades em seu programa está fortemente ligada aoCarroaula. Você percebe que, para fazer seu programa funcionar com outros métodos de entrega, terá que reescrever a base de código existente e fazer isso novamente toda vez que adicionar um novo veículo.

O resultado é um código horrendo cheio de declarações condicionais que executam diferentes ações dependendo do tipo de transporte.

A solução

O padrão de método de fábrica sugere a criação de objetos chamando um método de fábrica especial em vez de usar diretamente o operador new . As subclasses da classe que possui o método fábrica podem modificar os objetos criados dos veículos específicos. À primeira vista, isso pode parecer inútil: simplesmente movemos a chamada do construtor de um lugar no programa para outro. Mas agora você pode substituir o método de fábrica em uma subclasse para alterar o tipo de transporte que está sendo criado.

Vejamos o diagrama de classes para esta abordagem:

Para que este sistema funcione, todos os objetos retornados devem ter uma interface comum. As subclasses poderão produzir objetos de diferentes classes que implementam essa interface.

Por exemplo, as classes Truck e Car implementam a interface CourierTransport com um método de entrega . Cada uma dessas classes implementa o método de uma maneira diferente: caminhões entregam carga, enquanto carros entregam comida, pacotes e assim por diante. O método fábrica na classe TruckCreator retorna um objeto caminhão e a classe CarCreator retorna um objeto carro.

Para o cliente do método fábrica, não há diferença entre esses objetos, pois os tratará como uma espécie de CourierTransport abstrato . O cliente se importará muito com o fato de o objeto ter um método de entrega, mas como exatamente esse método funciona não é importante.

Implementação em Java:


public interface CourierTransport {
	void deliver();
}
public class Car implements CourierTransport {
	@Override
	public void deliver() {
    		System.out.println("The package is being delivered by car");
	}
}
public class Truck implements CourierTransport {
	@Override
	public void deliver() {
    		System.out.println("The freight is being delivered by truck");
	}
}
public abstract class CourierTransportCreator {
	public abstract CourierTransport createTransport();
}
public class CarCreator extends CourierTransportCreator {
	@Override
	public CourierTransport createTransport() {
    		return new Car();
	}
}
public class TruckCreator extends CourierTransportCreator {
	@Override
	public CourierTransport createTransport() {
    		return new Truck();
	}
}
 
public class Delivery {
	private String address;
	private CourierTransport courierTransport;
 
	public void Delivery() {
	}
 
	public Delivery(String address, CourierTransport courierTransport) {
    	this.address = address;
    	this.courierTransport = courierTransport;
	}
 
	public CourierTransport getCourierTransport() {
    		return courierTransport;
	}
 
	public void setCourierTransport(CourierTransport courierTransport) {
    		this.courierTransport = courierTransport;
	}
 
	public String getAddress() {
    		return address;
	}
 
	public void setAddress(String address) {
    		this.address = address;
	}
}
public static void main(String[] args) {
    	// Accept a new type of order from the database (pseudocode)
    	String type = database.getTypeOfDeliver();
 
    	Delivery delivery = new Delivery();
    	
    	// Set the transport for delivery
        delivery.setCourierTransport(getCourierTransportByType(type));
    	
    	// Make the delivery
        delivery.getCourierTransport().deliver();
 
	}
 
	public static CourierTransport getCourierTransportByType(String type) {
    	switch (type) {
        	case "CarDelivery":
            	return new CarCreator().createTransport();
        	case "TruckDelivery":
            	return new TruckCreator().createTransport();
        	default:
            	throw new RuntimeException();
	    }
	}
    

Se quisermos criar um novo objeto de entrega, o programa cria automaticamente um objeto de transporte apropriado.

Quando devemos aplicar esse padrão?

1. Quando você não conhece antecipadamente os tipos e dependências dos objetos com os quais seu código precisa trabalhar.

O método de fábrica separa o código para produzir formas de transporte do código que usa o transporte. Como resultado, o código para criar objetos pode ser estendido sem tocar no restante do código.

Por exemplo, para adicionar suporte a um novo tipo de transporte, você precisa criar uma nova subclasse e definir nela um método de fábrica que retorne uma instância do novo transporte.

2. Quando você deseja economizar recursos do sistema reutilizando objetos existentes em vez de criar novos.

Esse problema geralmente ocorre ao trabalhar com objetos com uso intensivo de recursos, como conexões de banco de dados, sistemas de arquivos, etc.

Pense nas etapas que você precisa seguir para reutilizar objetos existentes:

  1. Primeiro, você precisa criar um repositório compartilhado para armazenar todos os objetos que criar.

  2. Ao solicitar um novo objeto, você precisa consultar o repositório e verificar se ele contém um objeto disponível.

  3. Devolva o objeto ao código do cliente.

  4. Mas se não houver objetos disponíveis, crie um novo e adicione-o ao repositório.

Todo esse código precisa ser colocado em algum lugar que não sobrecarregue o código do cliente. O local mais conveniente seria o construtor, pois só precisamos de todas essas verificações ao criar objetos. Infelizmente, um construtor sempre cria um novo objeto — ele não pode retornar um objeto existente.

Isso significa que é necessário outro método que possa retornar objetos novos e existentes. Este será o método de fábrica.

3. Quando você deseja permitir que os usuários estendam partes de sua estrutura ou biblioteca.

Os usuários podem estender suas classes de estrutura por meio de herança. Mas como fazer o framework criar objetos dessas novas classes ao invés das classes padrão?

A solução é permitir que os usuários estendam não apenas os componentes, mas também as classes que criam esses componentes. E para isso, as classes criadoras devem possuir métodos de criação específicos que possam ser definidos.

Vantagens

  • Separa uma classe de classes de transporte específicas.
  • Mantém o código para criar formas de transporte em um só lugar, facilitando a manutenção do código.
  • Simplifica a adição de novos meios de transporte ao programa.
  • Implementa o princípio aberto-fechado.

Desvantagens

Pode levar a grandes hierarquias de classes paralelas, pois cada classe de produto deve ter sua própria subclasse criadora.

vamos resumir

Você aprendeu sobre o padrão de método de fábrica e viu uma implementação possível. Esse padrão é frequentemente usado em várias bibliotecas que fornecem objetos para criar outros objetos.

Use o padrão de método de fábrica quando quiser adicionar facilmente novos objetos de subclasses de classes existentes para interagir com sua lógica de negócios principal e não sobrecarregar seu código devido a diferentes contextos.