Singleton Pattern

Singleton – ensure a class only has one instance, and provide a global point of access to it – Page 177, Chapter 5, Head First Design Patterns

Em português, a Singleton Pattern assegura que a classe só tem uma instância e fornece o acesso global à mesma.

Singleton Pattern
Singleton Pattern

Amostra de código da classe Singleton

/**
 * para usar Singleton Pattern, é necessário:
 */
public class ChocolateBoiler {

    private boolean empty;
    private boolean boiled;
    /**
     * uma variável privada e estática:
     */
    private static ChocolateBoiler uniqueInstance;

    /**
     * um construtor privado
     */
    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    /**
     * um método estático e público para retornar a única instância da classe
     * usar a keyword synchronized para ser thread-safe
     */
    public static synchronized ChocolateBoiler getInstance() {
        if (uniqueInstance == null) {
            System.out.println("Creating unique instance of Chocolate Boiler");
            uniqueInstance = new ChocolateBoiler();
        }
        System.out.println("Returning instance of Chocolate Boiler");
        return uniqueInstance;
    }

    public void fill() {
        if (isEmpty()) {
            empty = false;
            boiled = false;
        }
    }

    public void drain() {
        if (!isEmpty() && isBoiled()) {
            empty = true;
        }
    }

    public void boil() {
        if (!isEmpty() && !isBoiled()) {
            boiled = true;
        }
    }

    public boolean isEmpty() {
        return empty;
    }

    public boolean isBoiled() {
        return boiled;
    }
}

Para saber quando se deve usar a Singleton Pattern, é quando se pretende utilizar só e  só uma instância duma classe que deve ser acessível para os clientes dum ponto de acesso bem conhecido.

As conclusões que eu retiro do estudo da Singleton Pattern são:

  • Acesso controlado à única instância: porque a classe Singleton encapsula a sua própria instância (única) e fornece controlo rígido de acesso (quando e como) aos clientes;
  • Espaço de nome reduzido: a Singleton Pattern é uma melhoria em relação às variáveis globais. Assim já não é necessário definir as variáveis globais que guardam as únicas instâncias.
  • Permite refinamento de operações e de representação: A classe Singleton poderá ser subclasse e é mais fácil de programar uma aplicação com uma instância duma classe estendida.

Abstract Factory Pattern

Abstract Factory – provides an interface for creating families of related or dependent objects without specifying their concrete classes  – Page 156, Chapter 4, Head First Design Patterns

Em português, a Abstract Factory Pattern fornece uma interface para criar familias de objectos relacionados ou dependentes sem especificar as suas classes concretas.

Abstract Factory Pattern
Abstract Factory Pattern

Para compreender melhor a interpretação do diagrama de classes, considere as seguintes exposições:

  • Client:  usa só as interfaces declaradas pelas classes  AbstractFactory e AbstractProduct.
  • Abstract Factory: declara uma interface para operações que cria objectos  AbstractProduct.
  • Abstract Product:  declara uma interface para um objecto de tipo ConcreteProduct.
  • Concrete Factory: implementa as operações para criar objectos ConcreteProduct.
  • ConcretProduct: Define um objecto ConcreteProduct a ser criado pela ConcreteFactory correspondente.

Amostra de código da classe Client:

public class NYPizzaStore{

    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        if(item.equals("cheese")){
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");
        }else if(item.equals("veggie")){
            pizza = new VeggiePizza(ingredientFactory);
            pizza.setName("New York Style Veggie Pizza");
        }else if(item.equals("clam")){
            pizza = new ClamPizza(ingredientFactory);
            pizza.setName("New York Style Clam Pizza");
        }else if(item.equals("pepperoni")){
            pizza = new PepperoniPizza(ingredientFactory);
            pizza.setName("New York style Pepperoni Pizza");
        }
        return pizza;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        System.out.println("--- Making a " + pizza.getName() + " ---");
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    
}

Amostra de código da classe “AbstractFactory”.
Nota: Ou pode ser uma interface ou pode ser uma classe abstracta com os métodos abstractos.

public interface PizzaIngredientFactory {
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClam();
}

Amostra de código da classe “AbstractProduct”.
Nota: Ou pode ser uma interface ou pode ser uma classe abstracta com o(s) métodos abstracto(s).

public interface Dough {
    public String toString();
}

Amostra de código da classe “ConcreteFactory”.

public class NYPizzaIngredientFactory implements PizzaIngredientFactory{

    public Dough createDough() {
        return new ThinCrustDough();
    }

    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    public Veggies[] createVeggies() {
        Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom(), new RedPepper()};
        return veggies;
    }

    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    public Clams createClam() {
        return new FreshClams();
    }
    
}

Amostra de código da classe “ConcreteProduct”.

public class ThinCrustDough implements Dough {

    public String toString() {
        return "Thin Crust Dough";
    }
    
}

Se está com dúvida de saber quando se aplica Abstact Factory Pattern, não se preocupe! Pois Abstact Factory Pattern deve-se usar quando:

  1. um sistema deve ser independente na maneira como os seus produtos são criados, compostos e representados;
  2. um sistema deve ser configurado com uma das muitas famílias de produtos;
  3. uma família dos objectos de produtos é desenhada para ser usada juntamente e quando precisar de reforçar esta restrição;
  4. quer fornecer uma biblioteca de classes dos produtos e só quer revelar apenas as suas interfaces e não as suas implementações.

As conclusões retiradas deste estudo da Abstact Factory Pattern são:

  • isola as classes concretas: ajuda controlar as classes de objectos que a aplicação cria. Pois a classe Abstract Factory encapsula a responsabilidade e o processo de criação de objectos produtos, isolando, assim, as implementações da classe Client.
  • facilita a troca de produtos nas famílias: a classe Concrete Factory aparece só uma vez na aplicação, isto é, quando é instanciada. Isto faz com que seja mais fácil de mudar a Concrete Factory. E, simplesmente, pode usar as diferentes configurações de produto através da mudança da Concrete Factory porque ao ser criada uma família de produtos, a família de produtos muda numa só vez.
  • promove a consistência entre produtos:se verificar a interface da classe AbstractFactory, verifica-se que os objectos Produto foram feitos para trabalhar juntos.
  • suportar novos tipos de produtos não é fácil:Porquê? Por causa da interface AbstractFactory que define um conjunto de produtos e não há possibilidade de colocar novos produtos. Para ultrapassar este desafio, é necessário modificar a classe AbstractFactory o que implicará mudanças nas suas subclasses.

Factory Method Pattern

Factory Method Patterh – defines an interface for creating an object, but lets subclasses decide which class to instantatie – Page 134, Chapter 4, Head First Design Patterns

Em português, a Factory Method Pattern define uma interface para criar um objecto mas deixa as subclasses decidir qual é a classe para instanciar.

Factory Method Pattern
Factory Method Pattern

 

Para compreender melhor a interpretação do diagrama de classes, considere as seguintes exposições:

  • Creator: é uma classe que contém implementações em todos os métodos para manipular os objectos Product exceptuando o método abstracto “FactoryMethod()” . Por consequência, o método abstracto “FactoryMethod()” é o que todas as subclasses Creator (ConcreteCreator) devem implementar.
  • ConcreteCreator: implementa o método “Factory Method”, que é o método que realmente produz os objectos Product. É responsável por criar um ou mais ConcreteProduct. É única classe que tem conhecimento de como criar ConcretProduct.
  • Product: define a interface do objectos que o “Factory Method” cria.
  • ConcretProduct: implementa a interface Product.

Amostra de código da classe Creator:


/**
 *
 * Classe PizzaStore é uma classe Creator
 * de acordo com o diagrama de classe acima desenhado
 */
public abstract class PizzaStore {

    /**
     * Método createPizza é um Factory Method
     * de acordo com o diagrama de classe acima desenhado
     */
    abstract Pizza createPizza(String item);

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        System.out.println("---Making a " + pizza.getName() + " ---");
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

}

Amostra de código da classe ConcreteCreator:

public class ChicagoPizzaStore extends PizzaStore {

    Pizza createPizza(String item) {
        if (item.equals("cheese")) {
            return new ChicagoStyleCheesePizza();
        } else if (item.equals("veggie")) {
            return new ChicagoStyleVeggiePizza();
        } else if (item.equals("clam")) {
            return new ChicagoStyleClamPizza();
        } else if (item.equals("pepperoni")) {
            return new ChicagoStylePepperoniPizza();
        } else {
            return null;
        }
    }

}

Amostra de código da classe Product:

public abstract class Pizza {

    String name;
    String dough;
    String sauce;
    ArrayList toppings = new ArrayList();

    void prepare() {
        System.out.println("Preparing " + name);
        System.out.println("Tossing dough...");
        System.out.println("Adding sauce...");
        System.out.println("Adding toppings...");
        for (int i = 0; i < toppings.size(); i++) {
            System.out.println(" " + toppings.get(i));
        }
    }

    void bake() {
        System.out.println("Bake for 25 minutes at 350");
    }

    void cut() {
        System.out.println("Cutting the pizza into diagonal slices");
    }

    void box() {
        System.out.println("Place pizza in official PizzaStore box");
    }

    public String getName() {
        return name;
    }

    
    public String toString() {
        StringBuffer display = new StringBuffer();
        display.append("---" + name + "---\n");
        display.append(dough + "\n");
        display.append(sauce + "\n");
        for (int i = 0; i < toppings.size(); i++) {
            display.append((String) toppings.get(i) + "\n");
        }
        return display.toString();
    }
}

Amostra de código da classe ConcreteProduct:

class ChicagoStyleCheesePizza extends Pizza {

    public ChicagoStyleCheesePizza() {
        name = "Chicago Style Deep Dish Cheese Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";

        toppings.add("Shredded Mozzarella Cheese");
    }

    void cut() {
        System.out.println("Cutting the pizza into square slices");
    }

}

Deve aplicar Factory Method Pattern quando:

  1. uma classe não pode antecipar as classes de objectos que devem criar;
  2. uma classe quer as suas subclasses especifiquem os objectos a serem criados;
  3. as classes delegam a responsabilidade para uma das muitas classes Helper e quando pretende localizar o conhecimento da mesma.

A consequência da Factory Method Pattern é fornecer métodos que são declarados na classe abstracta mas contém uma implementação vazia ou por defeito (conhecido também por hook em inglês).

Decorator Pattern

Decorator – attaches additional responsabilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending funcionality – Page 91, Chapter 3, Head First Design Patterns

Em português, a Decorator Pattern simplifica a complexidade da herança, ao anexar mais responsabilidades ao determinado objecto dinamicamente.

Decorator Pattern
Decorator Pattern

Para compreender melhor a interpretação do diagrama de classes, considere as seguintes exposições:

  • Component: define a interface para os objectos que podem ter responsabilidades adicionadas dinamicamente. (Tanto como pode ser uma interface ou uma classe abstracta);
  • Decorator: mantém a referência para um objecto de tipo Component e define uma interface que ajusta à interface do objecto Component;
  • ConcreteDecorator: adiciona as responsabilidades ao Component;

Uma amostra de código da classe “Component”:

public abstract class Beverage {
    String description = "Unknown Beverage";
    
    public String getDescription(){
        return description;
    }
    
    public abstract double cost();

}

Uma amostra de código da classe “Decorator”

public abstract class CondimentDecorator extends Beverage{
    
    public abstract String getDescription();
}

Uma amostra de código da classe “ConcreteComponent”

public class DarkRoast extends Beverage{
    
    public DarkRoast(){
        description = "Dark Roast Coffee";
    }

    @Override
    public double cost() {
        return .99;
    }

}

Uma amostra de código da classe “ConcreteDecorator”

public class Whip extends CondimentDecorator {

    Beverage beverage;

    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return beverage.getDescription();
    }

    public double cost() {
        return .10 + beverage.cost();
    }

}

Após do estudo desta design pattern, as conclusões que eu obtenho são:

  1. Fornece mais flexibilidade do que a herança;
  2. Um objecto decorator e os seus objectos component não são semelhantes;
  3. Cria vários objectos pequenos.