Home > GOF Pattern, java > GoF Patterns: Flyweight

GoF Patterns: Flyweight

7 marzo 2011

Translate in English with Google Translate
In questo articolo tratterò il pattern Flyweight

Motivazione

Si tratta di un pattern strutturale basato su oggetti che viene utilizzato per ottimizzare l’utilizzo delle risorse ed evitare la presenza di oggetti duplicati, spesso viene associato a richieste di performance in quanto il suo utilizzo può migliorare le prestazioni di una applicazione.

In particolare in Java, ogni qual volta che viene utilizzato l’operatore new, la JVM alloca nell’heap uno spazio in memoria di 32 bit  ed una eccessiva generazione di oggetti può saturare le risorse del sistema che, anche se elevate, sono pur sempre limitate. Molto spesso la generazione di nuovi oggetti non è motivata da reali esigenze mentre invece è dovuta a superficialità  oppure a errata analisi degli impatti prestazionali. Da qui l’esigenza di riutilizzare gli oggetti precedentemente creati ai fini del loro riutilizzo.

L’esigenza di adottare questo pattern può nascere da vari fattori:

  1. evitare la generazione di un elevato numero di oggetti
  2. evitare di creare oggetti che richiedono molta memoria
  3. evitare di creare oggetti che richiedono molto impegno computazionale
  4. Disponibilità limitate di risorse: di calcolo, di memoria, di spazio ecc

Ogni oggetto presenta uno stato e tale stato è costituito da elementi costanti ed elementi variabili. Gli elementi costanti caratterizzano l’oggetto e sono indipendenti dai fattori contingenti mentre gli elementi variabili sono spesso collegati ad essi.

Possiamo fare una distinzione tra stato interno o intrinseco ed stato esterno o estrinseco:

  • Lo stato interno fa riferimento alle caratteristiche di un oggetto indipendenti dal contesto di utilizzo e che vengono sempre mantenuti.
  • Lo stato esterno fa riferimento alle caratteristiche dell’oggetto dipendenti del contesto di utilizzo e che possono cambiare senza che le caratteristiche dell’oggetto mutino.

Considerando che lo stato interno è destinato a perdurare, possiamo condividerlo in modo da evitare la generazione di un oggetto duplicato con il medesimo stato, a differenza dello stato esterno che non è da condividere in quanto è soggetto a facili cambiamenti.

Quindi al fine della implementazione di un oggetto Flyweight occorre prima di tutto definire lo stato interno per poi definire le proprietà della classe che avranno il compito di mantenerne tale stato. A differenza dello stato esterno che non deve essere mantenuto nell’oggetto, pertanto non indicheremo delle proprietà atte a memorizzarle, in quanto tali proprietà non devono essere condivise.

Facciamo un esempio di stato interno ed esterno:

Visto e considerato che a me piace essere pratico e fare degli esempi… ne ho preparato uno.
Ho realizzato una classe che visualizza una tabella popolata con i dati utente:

  1. Il numero di colonne: è uno stato interno caratterizzante della tabella.
  2. I valori delle colonne: sono uno stato esterno che dipendono dai parametri di contesto che vengono passati alla nostra funzione.
package patterns.flyweight;

public class State {

    //stato interno: columns
    static int columns = 5;

    //stato esterno: args
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++) {
            if ( ((i+1) % columns)  == 0 ) {
                System.out.println(args[i]);
            } else {
                System.out.print(args[i] + " ");
            }
        }
    }

}

Compiliamo ed eseguiamo la classe passando i valori della tabella

$JAVA_HOME/bin/java patterns.flyweight.State  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

Verrà creata una tabella di max 5 colonne con il seguente output:

1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16

L’esempio ha solo l’obiettivo di mettere in evidenza la distinzione tra lo stato interno e lo stato esterno, il problema del caching dell’oggetto non è stato evidenziato ma ciò verrà fatto nell’esempio successivo, in quanto un oggetto Flyweight deve essere conservato in cache per poter essere riutilizzato per una successiva richiesta.

Partecipanti e Struttura

Questo pattern è composto dai seguenti partecipanti:

  1. Flyweight: dichiara una interfaccia attraverso la quale le classi concrete possono definire lo stato estrinseco
  2. ConcreteFlyweight: implementano l’interfaccia Flyweight e definiscono gli eventuali stati intrinsechi
  3. FlyweightFactory: crea e gestisce oggetti Flyweight. Rende disponibile al Client gli oggetti precedentemente creati oppure se non esiste ne crea uno nuovo e lo mantiene in cache.
  4. Client: mantiene una referenza agli oggetti Flyweight e definisce lo stato estrinseco degli oggetti da creare.

Possiamo schematizzare in UML usando il Class Diagram:

Flyweight Pattern

Flyweight Pattern

Conseguenze

Tale pattern presenta i seguenti vantaggi/svantaggi:

  1. riduce il numero di oggetti: sfruttando la condivisione, vengono ridotte le instanze degli oggetti da creare.
  2. riduce l’utilizzo di memoria: riducendo il numero di oggetti viene preservata maggiore memoria.

Implementazione

Come esempio pensiamo al caso in cui vogliamo creare una applicazione di word processing

Dalle 26 lettere dell’alfabeto è possibile creare un numero elevato di parole. Se per ogni lettera decidessimo di creare un oggetto, ci troveremmo nella situazione in cui andremmo a creare troppi oggetti, allocando troppa memoria. Per evitare questo inconveniente decidiamo di creare 26 oggetti, ognuno rappresenta una lettera dell’alfabeto (a, b, c ,d ecc) che indica il suo stato interno, mentre invece altre sue caratteristiche ( la dimensione, il colore, la posizione ecc ) rappresentano il suo stato esterno.

Usando il Class Diagram UML schematizziamo lo sviluppo in questo modo:

Flyweight Character

Flyweight Character

L’interfaccia Flyweight dei caratteri è la seguente:

package patterns.flyweight;

public interface FlyweightCharacter {
    public Character operation();
    public void setColor(String color);
}

La classe concreta dei caratteri è la seguente:

package patterns.flyweight;

public class ConcreteFlyweightCharacter implements FlyweightCharacter {

    Character character = null;

    //stato interno
    public ConcreteFlyweightCharacter(Character character) {
        this.character = Character.toUpperCase( character );
    }

    public Character operation() {
        return character;
    }

    //stato esterno
    public void setColor(String color) {
        System.out.println("Colore: " + color);
    }
}

La Factory che gestisce la creazione, la cache degli oggetti Flyweight e l’interazione con il Client è la seguente:

package patterns.flyweight;

import java.util.Hashtable;

public class FlyweightFactory {

    private static Hashtable<Character, FlyweightCharacter> cache = new Hashtable<Character, FlyweightCharacter>();

    public static FlyweightCharacter getFlyweight(Character character) {
        FlyweightCharacter flyweight = null;
        if (cache.containsKey(character)) {
            flyweight = cache.get(character);
        } else {
            flyweight = new ConcreteFlyweightCharacter(character);
            cache.put(character, flyweight);
        }
        return flyweight;
    }
}

Il Client richiede gli oggetti Flyweight interfacciandosi con la FlyweightFactory:

package patterns.flyweight;

public class Client {

    public static void main(String[] args) {

        FlyweightCharacter a1 = FlyweightFactory.getFlyweight('a');
        System.out.println( "HashCode: " + a1.hashCode() );
        System.out.println( "Value: " + a1.operation() );
        a1.setColor("red");

        FlyweightCharacter a2 = FlyweightFactory.getFlyweight('a');
        System.out.println( "HashCode: " + a2.hashCode() );
        System.out.println( "Value: " + a2.operation() );
        a2.setColor("blue");

    }
}

L’esecuzione del Client visualizza l’hashcode che indica che stiamo trattando la stessa instanza dell’oggetto FlyweightCharacter

$JAVA_HOME/bin/java patterns.flyweight.Client
HashCode: 4384790
Value: A
Colore: red
HashCode: 4384790
Value: A
Colore: blu
Categorie:GOF Pattern, java
  1. 24 marzo 2011 alle 7:18 PM

    Grazie mille! Molto chiaro e facile da seguire.
    Ottimo per ripassare velocemente i patterns prima di un colloquio di lavoro!
    Ti devo una bira!😉

  2. Francesco
    24 luglio 2013 alle 6:40 PM

    il metodo hashCode() non esiste da nessuna parte nel codice della classe Flyweight: esso infatti appartiene alla Hashtable API Java. Come fai a richiamarlo su a1 e a2 !?
    [ è assente anche l’ipotizzato metodo setDimension(), nonostante l’output mostri il contrario (da dove esce Dimension?), ma si intuisce che è come setColor() ]

    • 25 luglio 2013 alle 1:35 PM

      tutte le classi ereditano dalla classe Object, anche se non espressamente indicato, pertanto se il metodo hashCode() non risulta nella classe istanziata, la JVM risale la catena di ereditarietà e se nessuna classe effettua l’overwrite del metodo allora viene invocato il metodo hashCode() della classe Object.
      Nella classe Object il metodo hashCode() è definito in questo modo:
      public native int hashCode();
      La documentazione, uno stralcio, riporta che il numero di ritorno viene determinato in questo modo:

      This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java programming language.

      Quindi, se non viene effettuato l’overwrite del metodo hashCode() e venga stabilito diversamente, a parità di numero di ritorno dalla classe Object, si sta trattando sempre lo stesso oggetto java.
      Ecco svelato il trucco, il motivo del risultato ed il significato della sua invocazione.🙂
      Mentre invece in merito al metodo setDimension(), che come dicevi giustamente non esiste, ho riportato un output sbagliato frutto di una prova, che ho corretto.
      Ho approfittato dell’occasione per aggiornare le immagini relativi ai Class Diagram UML.
      Grazie.

  3. 8 maggio 2014 alle 11:00 AM

    Inoltre, credo che sia stata un svista, ma il pattern dovrebbe essere di tipo comportamentale e non strutturale.

  1. No trackbacks yet.
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: