design patterns

Patterns



Un pattern identifica e assegna un nome a strutture e comportamenti ricorrenti

Incanala la conoscenza di un'architettura ottenuta da esperti attraverso le loro esperienze di design

Patterns

Col tempo pattern utili si evolvono in features dei linguaggi

  • Assembly language patterns: closed subroutines, control structures, portano alla nascita di C e FORTRAN

  • Information hiding patterns: incapsulamento, linguaggi orientati agli oggetti (Ada, C++, Java)

  • Meta-programming patterns -> C++ templates

  • Iterator patterns -> C++ range based for loops

  • Monitor Object -> Java synchronized block

Patterns


Lo scopo dei pattern

  • Riutilizzo di design e architetture ricorrenti

  • Vocabolario comune che aiuta i team a comunicare in maniera efficiente riguardo il design, evitando considerazioni su componenti di basso livello nelle fasi di strutturazione dell'architettura

Patterns


... sviluppiamo una classe che implementa l'interfaccia X, e che ottiene come parametro un'implementazione di Y, chiamando i suoi metodi in maniera da creare un ponte tra le due interfacce.
Patter unaware team

... sviluppiamo un adapter da Y a X.

Pattern aware team

Patterns


Nell'ambito dello sviluppo software si distinguono due tipologie di pattern:

  • Design patterns: forniscono una soluzione a un problema con contesto ridotto, in maniera molto specifica

  • Architectural patterns: consistono in una visione d'insieme e in un processo per la risoluzione di problemi di software design in un particolare dominio

Patterns ARE SOCIAL




Pattern descritti senza riferimenti ad alcun altro pattern
sono molto rari

La maggior parte dei pattern è un componente compatibile, o sostitutivo, di un contesto in cui cooperano numerosi pattern

Patterns ARE SOCIAL

PATTERN COMPLEMENTS


Sono pattern che si presentano in coppie, sia in termini di cooperazione che di contrapposizione

  • Cooperazione: Factory Method - Disposal Method

  • Contrapposizione: Iterator - Batch Method

Patterns ARE SOCIAL

PATTERN COMPOUNDS


Alcuni pattern possono essere utilizzati insieme, presi come singola decisione in risposta a un design ricorrente

Batch Iterator

Patterns are social

PATTERN SEQUENCES


Catena di pattern in cui ogni predecessore fornisce il contesto in cui viene applicato il pattern successivo.

Un pattern viene aggiunto alla sequenza per aumentare i vantaggi o ammortizzare gli effetti collaterali del precedente.

    Patterns are social

    PATTERN SEQUENCES

    Ad esempio

    • Strategy: fornisce gli agganci a dei metodi contenenti parti configurabili di un algoritmo

    • Abstract Factory: permette il raggruppamento di strategie semanticamente compatibili

    • Component Configurator: configurazione dinamica dei componenti e delle loro connessioni

    Patterns are social

    PATTERN LANGUAGES


    Una network in cui i pattern sono costruiti uno sull'altro, per aiutare lo sviluppatore a generare un sistema documentando successive scelte di design, trasformazioni e alternative

    Un pattern language può essere visto come l'intersezione di più pattern sequences, fornendo diramazioni per applicare diverse scelte di design in base alle necessità

    Patterns are social

    PATTERN LANGUAGES

    • Individuazione dei problemi chiave e del loro ordine di risoluzione

    • Come gestire le dipendenze tra i vari problemi che vanno affrontati

    • Come risolvere ciascun singolo problema efficaciemente nel suo contesto

    • Quali alternative esistono per la risoluzione di ciascun problema

    Patterns are social

    PATTERN LANGUAGES



    Patterns





    I pattern non sono fatti di codice o individuati da un design concreto: devono essere reificati e applicati in particolari linguaggi


    Patterns



    Un pattern è individuato attraverso:

    • Nome e definizione del suo scopo

    • Problema affrontato dal pattern

    • Soluzione, ovvero la descrizione della struttura e delle dinamiche del pattern

    Over-Engineering


    Non tutto ciò che riguarda lo sviluppo di un'architettura è da considerarsi un pattern:
    alcune parti sono solo algoritmi o semplice design.

    Inoltre prima di fare uso di un pattern è necessario valutare gli specifici benefici contro gli effetti collaterali negativi
    e dimostrare la sua applicabilità.

    Scegliere la soluzione più semplice e applicare i pattern dove emergono le condizioni per il loro utilizzo.

    Design Patterns




    Lista di alcuni design pattern comuni
    e di applicabilità generale

    Strategy pattern





    Definisce una famiglia di algoritmi,
    incapsulati in modo da essere intercambiabili indipendentemente dal client* che ne fa uso.




    *client: parte di un'applicazione che dipende dal componente

    Strategy pattern



    • Permette di incapsulare le parti variabili di un'applicazione, in modo che i cambiamenti non coinvolgano il resto del codice

    • Favorisce la composizione rispetto all'ereditarietà

    • Dependency Inversion: programmare verso un'astrazione piuttosto che verso un'implementazione concreta

    Strategy pattern



    public interface WelcomeMessageStrategy {
    
        String welcome(String userName);
    }
    

    private WelcomeMessageStrategy welcomeStrategy;
    
    public void welcomeWhenUserLogsIn(String userName) {
        System.out.println(welcomeStrategy.welcome(userName));
    }
    

    Strategy pattern


    public class HelloMessageStrategy implements WelcomeMessageStrategy {
    
        @Override
        public String welcome(String userName) {
            return "Hello " + userName + "!";
        }
    }
    

    public class GTFOMessageStrategy implements WelcomeMessageStrategy {
    
        @Override
        public String welcome(String userName) {
            return "Burn in a f**king fire " + userName + "!!!";
        }
    }
    

    Observer Pattern


    Definisce una dipendenza uno a molti tra oggetti in modo che, quando il subject cambia stato, gli observer vengano automaticamente aggiornati.

    Esistono due modalità di informare gli oggetti iscritti:

    • push: invio del contesto (nuovo valore dello stato, a volte anche del vecchio stato)

    • pull: semplice notifica di cambiamento, è compito degli observer recuperare le informazioni sullo stato del subject

    Observer pattern



    L'Observer Pattern permette un design dove subject e observer sono loosely coupled:
    possono interagire avendo la minima conoscenza necessaria l'uno dell'altro.

    Ridurre l'interdipendenza tra oggetti aumenta la flessibilità di un'architettura

    Decorator Pattern



    Aggiunge dinamicamente ulteriori funzionalità ad un oggetto.

    Alternativa più flessibile dell'ereditarietà
    per l'estensione di funzionalità.

    Per essere usato, il client non deve affidarsi  al tipo concreto di un componente.

    Decorator Pattern

    #RealWorldDecorators



    InputStream

    Astrazione di un flusso di byte

    Decorator pattern

    #RealWorldDecorators

    Implementazioni di InputStream

    • FileInputStream: legge i byte da un file

    • StringBufferInputStream: legge i byte da una stringa

    • ByteArrayInputStream: legge i byte da un array

    • FilterInputStream: decoratore che aggiunge funzionalità alla semplice lettura di byte uno per volta

    Decorator pattern

    #RealWorldDecorators

    Implementazioni di FilterInputStream

    • PushbackInputStream: consente di annullare la lettura di un byte

    • BufferedInputStream: legge chunk di più byte per volta

    • DataInputStream: converte i byte letti in oggetti

    • LineNumberInputStream: tiene conto del numero di linee

    Factory pattern




    Permette di disaccoppiare la creazione di un oggetto
    dalla sua specifica implementazione.

    Promuove la dependency inversion:
    evita la dipendenza da tipi concreti

    Factory pattern


    Esistono due tipologie di Factory Pattern:

    • Factory Method: definisce un metodo astratto per la creazione di un oggetto, lasciando alle sottoclassi la decisione di che implementazione instanziare.

    • Abstract Factory: fornisce un'interfaccia per creare una famiglia di oggetti semanticamente compatibili, senza dover specificare  le loro classi concrete.
         

    Factory Pattern



    L'Abstract Factory Pattern può essere visto come specializzazione dello Strategy Pattern
    nella creazione di oggetti

    Il Factory Method Pattern è un modo naturale spesso utilizzato di implementare un'Abstract Factory:
    ciascun metodo dell'Abstract Factory è un Factory Method

    Command Pattern


    Incapsula una richiesta in un oggetto, in modo da poter essere aggiunta dinamicamente in una parte di un algoritmo o in una coda di esecuzione.

    Ad esempio:

    • callback: Listener registrati per essere eseguiti all'occorrenza di un dato evento

    • computation: Runnable eseguiti da una thread pool

    Adapter Pattern



    Converte l'interfaccia di una classe in un'altra interfaccia richiesta dal client.

    Elimina l'incompatibilità tra interfacce diverse.

    Usato dove esiste un layer di astrazione, in modo da adattare le classi della libreria utilizzata a tale astrazione.
    Questo serve a minimizzare la dipendenza da una libreria esterna, impiegando gli adapter come connettori

    Adapter pattern

    #RealWorldAdapters


    Nelle vecchie versioni di Java, l'Iterator Pattern era implementato attraverso l'interfaccia Enumeration

    public interface Enumeration<E> {
    
        boolean hasMoreElements();
    
        E nextElement();
    }
    

    Adapter pattern

    #RealWorldAdapters


    Attualmente l'implementazione del pattern è data dall'interfaccia Iterator

    public interface Iterator<E> {
    
        boolean hasNext();
    
        E next();
    
        void remove();
    }
    

    Adapter pattern

    #RealWorldAdapters

    /* Costruttore e annotazioni @Override omesse */
    public class EnumerationIterator<E> implements Iterator<E> {
    
    private final Enumeration<? extends E> enumeration; public boolean hasNext() { return enumeration.hasMoreElements(); } E next() { return enumeration.nextElement(); } void remove() { throw new UnsupportedOperationException("read only iterator"); } }

    Facade Pattern



    Fornisce un'interfaccia che raggruppa quelle di un sottosistema, in modo da semplificarne l'accesso.

    Utile per disaccoppiare il client
    da un sottosistema complesso.

    Null Object Pattern




    Si tratta di fornire un'implementazione che funga da elemento neutro.

    Risparmia dal dover fare controlli sui riferimenti a null
    e evita il rischio di errori a runtime a causa di null references.

      Null Object Pattern



      Alcuni esempi

      • Empty Iterator: nel caso una collection sia vuota, invece di restituire null si restituisce un iteratore senza elementi su cui iterare

      • NOP Command: nel caso non deve essere eseguita alcuna callback, invece di avere un null command si utilizza uno con un metodo vuoto

      The dark side




      Darth Sidious, Imperatore Galattico
      implementazione di Singleton Pattern

      Antipatterns



      Quasi tutti i pattern hanno degli effetti collaterali o carenze, ma per alcuni di questi si ha un degrado dell'architettura non giustificabile dai vantaggi che il pattern porta

      In questo caso vengono definiti antipattern, ma è ugualmente importante conoscerli per poterli evitare

      Dalla storia si devono imparare sia successi che errori

      Antipatterns


      Un antipattern descrive come giungere da un problema
      a una cattiva soluzione

      Inoltre specifica:

      • perché una cattiva soluzione è attrattiva

      • perché la soluzione a lungo termine risulta essere cattiva

      • quali pattern forniscono un'alternativa buona soluzione

      Service Locator


      Permette di ottenere le implementazioni di classi astratte, dette servizi, da un unico punto centrale.

      È possibile fare dependency injection richiedendo le dipendenze direttamente al service locator.

      Non è necessario che il client sia a conoscenza della libreria utilizzata, in quanto il service locator fa da collegamento.

      I servizi possono essere sostituiti a runtime.

      Service Locator


      Viene completamente nascosto il grafo delle dipendenze, rendendo i componenti che fanno riferimento
      al service locator difficilmente manutenibili.

      Nel caso non vengano forniti dei servizi, si hanno errori a run time invece che a compile time o startup time.

      Siccome il registro dei servizi deve essere unico, si ha un bottleneck in caso di applicazioni multithreaded.

      Riportare l'equilibrio nella forza



      Gof (Gang of four)

       
      Design Patterns:
      Elements of Reusable
      Object-Oriented Software

      1994

      POSA 1 2 3 4 5


      Pattern-Oriented Software Architecture

      1996 - 2007

      POSA 2 Course



      Douglas Schmidt
      Professor of Computer Science, Vanderbilt University

      POSA 2, GOF and Android lessons on coursera.org




      Grazie!


      @0x1abe5
      @unitsdev

      design patterns

      By labes

      design patterns

      • 2,547