Sei sulla pagina 1di 15

Abstract Factory e Decorator

Alberto Merciai: 5421314

Abstract
In questo tema si descrive una combinazione di Abstract Factory e Decorator in modo tale
da aggiungere dinamicamente responsabilit`a addizionali ad un oggetto,fornito attraverso una
interfaccia per la creazione di famiglie di oggetti connessi o dipendenti tra loro, senza specificare
le classi concrete di questi.

Lo scenario ipotetico

Supponiamo di avere molti prodotti, collegati o dipendenti tra di loro, e suddividere nella stessa
linea di prodotto quelli che hanno caratteristiche simili.Supponiamo ora di volerli aggiungere
dinamicamente delle responsabilit`
a addizionali,per questo pu`o essere utile combinare il Decorator
con lAbstract Factory.

Struttura

Partecipanti:

AbstractFactory: dichiara una interfaccia per le operazioni che creano oggetti prodotto
astratto.

ConcreteFactory: implementa le operazioni che creano prodotti concreti che possono


essere decorati.

AbstractProduct: definisce una interfaccia conforme a quella di AbstractComponent


per un tipo di prodotto concreto che pu`o essere decorato.

ConcreteProductComponent: definisce un prodotto che pu`o essere decorato, creato


attraverso la corrispondente ConcretFactory, ed implementa linterfaccia AbstractProduct.

AbstractComponent: definisce linterfaccia per oggetti ai quali possono essere aggiunte


dinamicamente responsabilit`
a.

AbstractDecorator: mantiene un riferimento ad un oggetto AbstractComponent e definisce


una interfaccia conforme a quella di AbstractComponent.

ConcreteDecorator: aggiunge responsabilit`a al component.

Client: usa solo le interfacce dichiarate da AbstractFactory, AbstractProduct , AbstractComponent.

Collaborazioni
Normalmente una singola istanza di ConcreteFactory `e creata al tempo di esecuzione.Questa
fabbrica concreta crea prodotti che hanno una particolare caratteristica ed ai quali possono
essere aggiunte responsabilit`
a in maniera dinamica.Per creare oggetti prodotto differenti,i
clients dovrebbero utilizzare una fabbrica concreta diversa.
AbstractFactory rinvia la creazione di oggetti prodotto alle sottoclassi ConcreteFactory.
AbstractDecorator forworda le richieste al suo oggetto AbstractComponent.Questo pu`o
eseguire opzionalmente delle operazioni addizionali prima e dopo aver fatto il forward della
richiesta.

Descrizione Esempio

Il client pu`
o scegliere tra le due ConcreteFactory(forni) disponibili,in entrambi i forni sono
prodotti della schiacciata e della pizza.I due forni sono caratterizzati dal tipo di farina utilizzata
per produrre i rispettivi prodotti(pizza o schiacciata): farina integrale o farina bianca. Ogni
prodotto `e caratterizzato da un costo di base(price) e da una sua descrizione(description).Si ha
inoltre la possibilit`
a, di farcire i prodotti con qualsiasi combinazione delle tre farciture disponibili ovvero: Prosciutto,funghi e mozzarella (HamTopping , MushroomTopping, MozzarellaTopping).Le tre farciture hanno un costo aggiuntivo che deve essere addizionato al prezzo di base
del prodotto e una propria descrizione che deve essere aggiunta a quella del prodotto di base.
Il Client vuole infine un resoconto sulla descizione del prodotto e sul costo finale, in base alla
farcitura scelta e al corrispettivo forno(ConcreteFactory) dove il tutto `e prodotto.

6
6.1

Soluzione col pattern Abstract Factory combinato con il


pattern Decorator
Abstract Factory

le classi BakeryWithWholewheatFlour e BakeryWithWhiteFlour sono istanziate una alla volta e


rappresentano le ConcreteFactory attraverso le quali i ConcreteProductComponent (pizze integrali oppure schiacciate e pizze di farina bianca) sono prodotti.Le classi WhiteFlourPizza, WhiteFlourSchiacciata, WholemealFlourPizza , WholemealFlourSchiacciata rappresentano gli oggetti
ConcreteProductComponent creati dalle ConcreteFactory ai quali possono essere aggiunti responsabilit`
a(costo della farcitura e descrizione di questa).

6.2

Decorator

Una volta istanziato un oggetto, questo pu`o essere decorato con delle farciture rappresentate
dalle classi MozzarellaTopping , HamTopping , MushroomTopping che aggiungono responsabilit`a
(prezzo e descrizioni delle farciture del prodotto). Il Client una volta istanziato il prodotto attraverso una delle due ConcreteFactory seleziona la farcitura invocando il metodo static decoratesProduct della classe Client, al quale sono passati come argomenti le farciture(tramite valori
booleani) e loggetto da decorare; arrivati a questo punto ritorna un sottotipo dellinterfaccia
AbstractComponent, la quale possiede un metodo per ottenere il costo del prodotto decorato
(getPrice()) e la descrizione di questo (getDescription()).

Struttura

Partecipanti

AbstractFactory: dichiara una interfaccia per operazioni che creano pizza createPizza()
o schiacciata createSchiacciata().

BakeryWithWhiteFlour ,BakeryWithWholewheatFlour(ConcreteFactory): implementano le operazioni dellinterfaccia AbstractFactory per creare oggetti ConcreteProductComponent che possono essere decorati dinamicamente;mi servo di queste per istanziare gli oggetti da decorare.

AbstractPizza, AbstractSchiacciata (AbstractProduct): dichiarano le interfacce


per un tipo di prodotto, nel caso specifico pizza o schiacciata.

WholemealFlourPizza, WholemealFlourSchiacciata, WhiteFlourPizza, WhiteFlourSchiacciata (ConcreteProduct): definiscono


gli oggetti prodotto che devono essere creati dalla corrispondente ConcreteFactory: BakeryWithWhiteFlour crea gli oggetti prodotto(WhiteFlourPizza , WhiteFlourSchiacciata)
caratterizzati dal campo type = white Flour, BakeryWithWholewheatFlour crea gli
oggetti prodotto (WholemealFlourPizza , WholemealFlourSchiacciata) caratterizzati da
campo type = wholemeal Flour.

AbstractComponent: definisce linterfaccia per gli oggetti ai quali possono essere aggiunte dinamicamente responsabilit`a. Contiente i metodi non implementati getPrice() e
getDescription() dei quali mi servo per aggiungere responsabilit`a ai miei oggetti prodotto.

ToppingDecorator (AbstractDecorator): mantiene un riferimento ad un oggetto


AbstractComponent e definisce una interfaccia conforme a quella di AbstractComponent.

HamTopping , MozzarellaTopping , MushroomTopping (ConcreteDecorator):


aggiungono dinamicamente responsabilit`a ai prodotti forniti dalle factory, incrementando
il costo e la descrizione di questo.

Client si serve del metodo statico decoratesProduct() per aggiungere responsabilit`a alloggetto
creato dalla factory e utilizza le interfacce dichiarate attraverso le classi AbstractFactory,
AbstractPizza, AbstractSchiacciata, AbstractComponent.

Collaborazioni:
normalmente una singola istanza della ConcreteFactory(BakeryWithWholewheatFlour ,
BakeryWithWhiteFlour) `e utilizzata al tempo di esecuzione; Mi servo di questa per la
creazione dei prodotti WhiteFlourPizza , WhiteFlourSchiacciata oppure WholemealFlourPizza
,WholemealFlourSchiacciata a seconda della factory che istanzio.Ogni ConcreteFactory crea
prodotti con determinate caratteristiche.
la classe AbstractTopping fa forward dei metodi getPrice() e getDescription() al suo oggetto
AbstractComponent.

10

Sequence Diagram

Il sottostante Sequence diagram mostra un possibile scenario a runtime.Si suppone infatti,che


il client abbia gi`
a creato una ConcreteFactory(BakeryWithWhiteFlour) ed operi su questa attraverso un riferimento alla propria interfaccia.Il client opera con i prodotti restituiti dalla factory
attraverso un riferimento alla rispettiva classe astratta(AbstractPizza,AbstractSchiacciata). Il
client opera con il prodotto decorato restituito dal metodo decorateProduct() mediante un riferimento allinterfaccia AbstractComponent.

11

ObjectDiagram

il sequente Object diagram mostra la configurazione degli oggetti in una possibile decorazione.

12

Descrizione del codice

AbstractFactory: Interfaccia che dichiara i metodi che consentono alle factory di costruire
prodotti: createPizza(),createSchiacciata()
public interface AbstractFactory {
public AbstractPizza createPizza();
public AbstractSchiacciata createSchiacciata();
}

BakeryWithWhiteFlour:implementa i metodi di AbstractFactory per creare prodotti


(ConcreteFactory).I prodotti creati con questa hanno la caratteristica di avere un campo type
= white Flour (i prodotti di questa sono fatti con farina bianca) e attraverso il metodo createPizza() creo un oggetto WhiteFlourPizza mentre con il metodo createSchiacciata() creo un
oggetto WhiteFlourSchiacciata.Poich`e non ha stato questa classe `e implementata come un Singleton.
public class BakeryWithWhiteFlour implements AbstractFactory{
public static final BakeryWithWhiteFlour istance = new BakeryWithWhiteFlour();
private BakeryWithWhiteFlour(){}
@Override
public AbstractPizza createPizza() {
return new WhiteFlourPizza();
}
@Override
public AbstractSchiacciata createSchiacciata() {
return new WhiteFlourSchiacciata();
}
}

BakeryWithWholewheatFlour:implementa i metodi di AbstractFactory per creare


prodotti.E una ConcreteFactory, i prodotti creati con questa hanno la caratteristica che il campo
type = wholemeal Flour (i prodotti di questa sono fatti con farina integrale). attraverso il
metodo createPizza() creo un oggetto WolemealFlourPizza mentre con il metodo createSchiacciata() creo un oggetto WholemealFlourSchiacciata. Poich`e non ha stato questa classe `e implementata come un Singleton.
public class BakeryWithWholewheatFlour implements AbstractFactory {
public static final BakeryWithWholewheatFlour istance= new
BakeryWithWholewheatFlour();
private BakeryWithWholewheatFlour(){}
@Override
public AbstractPizza createPizza() {
return new WholemealFlourPizza();
}
@Override
public AbstractSchiacciata createSchiacciata() {
return new WholemealFlourSchiacciata();}}

AbstractPizza:dichiara linterfaccia conforme a quella di abstract component per i prodotti


pizza ed in questa `e presente un campo productNoun che indica il tipo di prodotto;implementando
linterfaccia AbstractComponent, i prodotti concreti che estendono questa classe fanno uso del
costruttore di questa che inizializza il campo productNoun = pizza.Al suo interno sono presenti
i metodi astratti getPrice() e getDescription() conformi allinterfaccia AbstractComponent e il
metodo getProductNoun() che restituisce la stringa productNoun contenente il tipo di prodotto.
public abstract class AbstractPizza implements AbstractComponent{
private String productNoun;
public AbstractPizza(){
productNoun = "pizza";
}
public String getProductNoun(){
return productNoun;
}
public abstract String getDescription();
public abstract double getPrice();
}

AbstractSchiacciata:dichiara linterfaccia conforme a quella di AbstractComponent


per i prodotti schiacciata ed in questa `e presente un campo productNoun che indica il tipo
di prodotto;implementando linterfaccia AbstractComponent, i prodotti concreti che estendono
questa classe fanno uso del costruttore di questa che inizializza il campo productNoun = schiacciata.Al suo interno sono presenti i metodi astratti getPrice() e getDescription() conformi
allinterfaccia AbstractComponent e il metodo getProductNoun() che restituisce la stringa productNoun contenente il tipo di prodotto.
public abstract class AbstractSchiacciata implements AbstractComponent{
private String productNoun;
public AbstractSchiacciata(){
productNoun = "schiacciata";
}
public String getProductNoun(){
return this.productNoun;
}
public abstract String getDescription();
public abstract double getPrice();
}

WhiteFlourPizza: Questa classe rappresenta il prodotto pizza fatta con farina bianca
ed estende la classe astratta AbstractPizza sia implementando il metodo astratto getPrice() che
restituisce il prezzo base di questo prodotto sia con il metodo getDescription() che fa uso di
getProductNoun() per dare una descrizione di questo prodotto. In questa `e presente un campo
type che indica il tipo di farina della pizza e un campo price che sta ad indicare il prezzo del
prodotto di base. A questo prodotto possono essere aggiunte dinamicamente responsabilit`a.
public class WhiteFlourPizza extends AbstractPizza {
private String type;
private double price;
public WhiteFlourPizza() {
super();
price = 4.99;
type = "white Flour";
}
public String getDescription(){
return type+"\t"+super.getProductNoun()+"\t with: \t";
}
@Override
public double getPrice() {
return this.price;
}
}

WholemealFlourPizza: Questa classe rappresenta il prodotto pizza fatta con farina


integrale ed estende la classe astratta AbstractPizza sia implementando il metodo astratto getPrice() che restituisce il prezzo base di questo prodotto sia con il metodo getDescription() che fa
uso di getProductNoun() per dare una descrizione di questo prodotto. In questa `e presente un
campo type che indica il tipo di farina della pizza e un campo price che sta ad indicare il prezzo
del prodotto di base. A questo prodotto possono essere aggiunte dinamicamente responsabilit`a.
public class WholemealFlourPizza extends AbstractPizza {
private String type;
private double price;
public WholemealFlourPizza() {
super();
price = 5.99;
type = "wholemeal Flour ";
}
public String getDescription(){
return type+"\t"+super.getProductNoun()+"\t with: \t";
}
@Override
public double getPrice() {
return this.price;
}
}

WholemealFlourSchiacciata: Questa classe rappresenta il prodotto schiacciata fatta


con farina integrale ed estende la classe astratta AbstractSchiacciata sia implementando il metodo
astratto getPrice() che restituisce il prezzo base di questo prodotto sia con il metodo getDescription() che fa uso di getProductNoun() per dare una descrizione di questo prodotto. In questa `e
presente un campo type che indica il tipo di farina della pizza e un campo price che sta ad indicare il prezzo del prodotto di base. A questo prodotto possono essere aggiunte dinamicamente
responsabilit`
a.
public class WholemealFlourSchiacciata extends AbstractSchiacciata {
private String type;
private double price;
public WholemealFlourSchiacciata() {
super();
price = 3.00;
type = "wholemeal flour";
}
@Override
public double getPrice() {
return this.price;
}
@Override
public String getDescription() {
return type+"\t"+super.getProductNoun()+"\t with: \t";
}
}

WhiteFlourSchiacciata: Questa classe rappresenta il prodotto schiacciata fatta con


farina bianca ed estende la classe astratta AbstractSchiacciata sia implementando il metodo
astratto getPrice() che restituisce il prezzo base di questo prodotto sia con il metodo getDescription() che fa uso di getProductNoun() per dare una descrizione di questo prodotto. In questa `e
presente un campo type che indica il tipo di farina della pizza e un campo price che sta ad indicare il prezzo del prodotto di base. A questo prodotto possono essere aggiunte dinamicamente
responsabilit`
a.
public class WhiteFlourSchiacciata extends AbstractSchiacciata {
private String type;
private double price;
public WhiteFlourSchiacciata() {
super();
price = 3.25;
type = "white Flour";
}
@Override
public double getPrice() {
return this.price;
}
@Override
public String getDescription() {
return type+"\t"+super.getProductNoun()+"\t with: \t";}}

AbstractComponent: definisce linterfaccia per gli oggetti ai quali possono essere aggiunte resposabilit`
a in maniera dinamica. Ai prodotti della factory pu`o essere aggiunto il costo
della farcitura e la sua descrizione.
public interface AbstractComponent {
public double getPrice();
public String getDescription();
}

AbstractDecorator: mantiene un riferimento al prodotto che deve essere decorato e fa forward dei metodi getPrice() e getDescription() di questo.inoltre definisce una interfaccia conforme
a quella di AbstractComponent.
public abstract class ToppingDecorator implements AbstractComponent {
private AbstractComponent component;
public ToppingDecorator (AbstractComponent componentToDecorate){
component = componentToDecorate;
}
public double getPrice(){
return component.getPrice();
}
public String getDescription(){
return component.getDescription();
}
}

MozzarellaTopping: decora il prodotto aggiungendo la farcitura di mozzarella alla pizza


o la schiacciata prodotta da una delle due factory, incrementando il prezzo del prodotto di base
con il costo della mozzarella e con la descrizione di questa.
public class MozzarellaTopping extends ToppingDecorator{
private double price;
private String topping;
public MozzarellaTopping(AbstractComponent component){
super(component);
price = 0.99;
topping = "Mozzarella";
}
public double getPrice(){
return super.getPrice()+this.price;
}
@Override
public String getDescription() {
return super.getDescription()+"\t"+topping;
}
}

10

HamTopping: decora il prodotto aggiungendo la farcitura di Prosciutto alla pizza o la schiacciata prodotta da una delle due factory, incrementando il prezzo del prodotto di base con il
costo della prosciutto e con la descrizione di questo.
public class HamTopping extends ToppingDecorator {
private double price;
private String topping;
public HamTopping (AbstractComponent component){
super(component);
price = 2.5;
topping = "Ham";
}
public double getPrice(){
return super.getPrice()+this.price;
}
@Override
public String getDescription() {
return super.getDescription()+"\t"+topping;
}
}

MushroomTopping: decora il prodotto aggiungendo la farcitura di funghi alla pizza o


la schiacciata prodotta da una delle due factory, incrementando il prezzo del prodotto di base
con il costo dei funghi e con la descrizione di questi.
public class MushroomTopping extends ToppingDecorator{
private double price;
private String topping;
public MushroomTopping(AbstractComponent component){
super(component);
price = 1.5;
topping = "Mushroom";
}
public double getPrice(){
return super.getPrice()+this.price;
}
@Override
public String getDescription() {
return super.getDescription()+"\t"+topping;
}
}

11

Client:Combina i due pattern Abstract Factorye Decorator ed istanzia una delle due factory
BakeryWithWhiteFlour o BakeryWithWholewheatFlour dalla quale ottiene un prodotto pizza
o schiacciata da decorare(farcire);Si serve del metodo decorateProduct() al quale `e passato il
prodotto da decorare e il tipo di farcitura (attraverso delle variabili booleane),restituendo il
prodotto fornito decorato.
public class Client {
private static boolean mushroom = true;
private static boolean ham = true;
private static boolean mozzarella = true;
public static void main(String[] args) {
// creo una fabbrica di prodotti integrali
AbstractFactory bakeryWithWholewheatFlour = BakeryWithWholewheatFlour.istance;
//creo un prodotto dalla fabbrica di prodotti integrali,(cambiando fabbrica
ottengo prodotti con costo e tipo di farina diverso)
AbstractSchiacciata schiacciataToDecorate =
bakeryWithWholewheatFlour.createSchiacciata();
//stampo a video le caratteristiche del prodotto creato prima della decorazione
System.out.println("\n before decoration\n");
System.out.println("product description: "+schiacciataToDecorate.getDescription());
System.out.println("total price:"+schiacciataToDecorate.getPrice()+"");
//a seconda degli argomenti che passo alla funzione sottostante decoro il mio
prodotto
AbstractComponent decoratedProduct = decorateProduct(schiacciataToDecorate,
mushroom, ham, mozzarella);
//stampa la descrizione del prodotto e il suo prezzo dopo la decorazione
System.out.println("\n after decoration\n");
System.out.println("product description: "+decoratedProduct.getDescription());
System.out.println("total price:"+decoratedProduct.getPrice()+"");
}

public static AbstractComponent decorateProduct(AbstractComponent


productToDecorate,boolean mushroom,boolean ham, boolean mozzarella){
if(mushroom & ham & mozzarella) {return
decoratesProductWithMushroomMozzarellaHam(productToDecorate); }
else if(!mushroom & ham & mozzarella)
{return decoratesProductWithHamMozzarella(productToDecorate);}
else if(mushroom & !ham & mozzarella){return
decoratesProductWithMushroomMozzarella(productToDecorate);}
else if(mushroom & ham & !mozzarella){return
decoratesProductWithHamMushroom(productToDecorate);}
else if(!mushroom & !ham & mozzarella){return
decoratesProductWithMozzarella(productToDecorate);}
else if(!mushroom & ham & !mozzarella){return
decoratesProductWithHam(productToDecorate);}

12

else if(mushroom & !ham & !mozzarella){return


decoratesProductWithMushroom(productToDecorate);}
else return productToDecorate;
}
private static AbstractComponent decoratesProductWithHamMushroom(AbstractComponent
productToDecorate ){
return new HamTopping(new MushroomTopping(productToDecorate));
}
private static AbstractComponent decoratesProductWithHamMozzarella(AbstractComponent
productToDecorate) {
return new MozzarellaTopping(new HamTopping(productToDecorate));
}
private static AbstractComponent decoratesProductWithMushroom(AbstractComponent
productToDecorate) {
return new MushroomTopping(productToDecorate);
}
private static AbstractComponent decoratesProductWithHam(AbstractComponent
productToDecorate) {
return new HamTopping(productToDecorate);
}
private static AbstractComponent decoratesProductWithMozzarella(AbstractComponent
productToDecorate) {
return new MozzarellaTopping(productToDecorate);
}
private static AbstractComponent
decoratesProductWithMushroomMozzarella(AbstractComponent productToDecorate) {
return new MushroomTopping(new MozzarellaTopping(productToDecorate));
}
private static AbstractComponent
decoratesProductWithMushroomMozzarellaHam(AbstractComponent productToDecorate) {
return new MozzarellaTopping(new MushroomTopping(new
HamTopping(productToDecorate)));
}}

12.1

console

13

13

Test con JUnit

di seguito sono illustrati 5 test che mostrano il funzionamento del programma:


public class ProgramTest {
private BakeryWithWholewheatFlour bakeryWithWholewheatFlour;
private AbstractPizza wholeMealFlourPizza;
private AbstractComponent decoratedProduct;

private
private
private
private

double
String
double
double

priceExpected;
typeOfProductExpected;
productPrice;
toppingPrice;

@Before
public void setUp(){
// istanzio una delle 2 fabbriche di schiacciata e pizza
bakeryWithWholewheatFlour = BakeryWithWholewheatFlour.istance ;
//una pizza con determinate caratteristiche(prezzo e tipo di farina)
factory
wholeMealFlourPizza = bakeryWithWholewheatFlour.createPizza();
}

13.1

creata dalla

test 1

@Test
public void productIstantiationTest1(){
//primo test di instanzazione prodotto tramite la factory
priceExpected = 5.99;
assertEquals(wholeMealFlourPizza.getPrice(),priceExpected,0.0);
}

13.2

test 2

@Test
public void productIstantiationTest2(){
//secondo test di instanzazione prodotto tramite la factory
typeOfProductExpected = "pizza";
assertEquals(wholeMealFlourPizza.getProductNoun(),typeOfProductExpected);
}

14

13.3

test 3

@Test
public void productDecorationTest1(){
//ill metodo statico decoratesProduct aggiunge responsabilit alloggetto
decorandolo e lo restituisce
//sotto uno dei tre tipi delle decorazioni, per questo lo passo ad un riferimento
del tipo dellinterfaccia delle decorazioni
decoratedProduct = Client.decorateProduct(wholeMealFlourPizza, true, true, true);
productPrice = 5.99;
toppingPrice = 2.5 + 0.99 + 1.5;
priceExpected = productPrice + toppingPrice;
assertEquals(decoratedProduct.getPrice(),10.98,0.0);
}

13.4

test 4

@Test
public void productDecorationTest2(){
//testo la decorazione singola delloggetto (nel caso specifico la pizza creata
tramite la factory)
decoratedProduct = new HamTopping(wholeMealFlourPizza);
productPrice = 5.99;
toppingPrice = 2.5;
priceExpected = productPrice + toppingPrice;
assertEquals(decoratedProduct.getPrice(),priceExpected,0.0);
}

13.5

test 5

@Test
public void productDecorationTest3(){
//posso omettere la decorazione
decoratedProduct = wholeMealFlourPizza;
productPrice = 5.99;
toppingPrice = 0;
priceExpected = productPrice + toppingPrice;
assertEquals(decoratedProduct.getPrice(),priceExpected,0.0);
}

15