ASP.Net e Pattern Observer in Pratica – Quarta Parte

In questo post, come promesso in ASP.Net e Pattern Observer in Pratica – Terza Parte, utilizzeremo il pattern observer per assolvere al requisito prensentato nel primo post della serie ASP.Net e Pattern Observer in Pratica.
Allaciate le cinture che questa volta saremo un po’ lunghetti 🙂

Ricordiamo il Requisito

Implementazione di una pagina di ricerca dove sono presenti alcuni web user controls tra i quali uno in particolare si occupa di raccogliere l’input dell’utente per effettuare una ricerca tramite la pressione di un pulsante apposito (presente sullo stesso user control), l’operazione di ricerca deve in qualche modo essere notificata agli altri user controls presenti sulla stessa pagina che utilizzando gli stessi valori inseriti nel primo controllo dovranno filtrare e renderizzare i dati di dettaglio.

Ciò che implementeremo

Una serie di classi necessarie alla rappresentazione di un repository di Clienti, un unica pagina contenente due web user control uno per gestire l’operazione di ricerca (il pubblicatore) l’altro per la rappresentazione a video della lista filtrate di clienti (il sottoscrittore). Il controllo web che presenterà i dati filtrati riceverà da parte del controllo di ricerca una notifica ogni volta che l’utente modificherà i valori del filtro ed effettuerà una nuova ricerca. Insieme alla notifica viaggeranno anche i valori del filtro impostato da un controllo all’altro, il tutto senza scomodare in alcun modo la sessione.

Di seguito la screenshot dell’interfaccia web

DesignPattern - Esempio del pattern Observer

Per prima cosa andiamo a definire la classe “Customer”, che rappresenta il nostro modello

namespace DesingPattern.Sample.Observer.Model
{
    /// <summary>
    /// Rappresenta l'entità Cliente.
    /// </summary>
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public string City { get; set; }
    }
}

e la relativa “CustomerRepository” (che tramite alcuni metodi statici, vi ricordo che siamo in un esempio, ci fornirà la lista dei Clienti):

namespace DesingPattern.Sample.Observer.Data
{
    /// <summary>
    /// Rappresenta il repository per le entità Cliente.
    /// </summary>
    public class CustomerRepository
    {
        /// <summary>
        /// Restituisce la lista completa di tutti i Clienti.
        /// </summary>
        /// <returns></returns>
        public static IQueryable<Customer> GetAllCustomers()
        {
            List<Customer> customers = new List<Customer>();
            customers.Add(new Customer() { 
                Id = 1, 
                Surname = "Santaniello", 
                Name = "Ernesto", 
                City = "Napoli" 
            });
            customers.Add(new Customer() { 
                Id = 2, 
                Surname = "Tispo", 
                Name = "Armando", 
                City = "Napoli" 
            });
            // ecc..
            return customers.AsQueryable().OrderBy(c => c.Surname);
        }

        /// <summary>
        /// Restituisce una lista di clienti filtrata.
        /// </summary>
        /// <param name="filter">Rappresenta il filtro di ricerca.</param>
        /// <returns></returns>
        public static IQueryable<Customer> GetCustomers(CustomerFilter filter)
        {
            if (filter is NullCustomerFilter)
                return CustomerRepository.GetAllCustomers();

            if(filter.UseLikeFilter)
                return CustomerRepository.GetAllCustomers().Where(c =>
                        (string.IsNullOrEmpty(filter.Name) || c.Name.ToLower().Contains(filter.Name.ToLower()))
                        && (string.IsNullOrEmpty(filter.Surname) || c.Surname.ToLower().Contains(filter.Surname.ToLower()))
                        && (string.IsNullOrEmpty(filter.City) || c.City.ToLower().Contains(filter.City.ToLower()))
                    );
            else
                return CustomerRepository.GetAllCustomers().Where(c =>
                    (string.IsNullOrEmpty(filter.Name) || c.Name.Equals(filter.Name, StringComparison.OrdinalIgnoreCase))
                    && (string.IsNullOrEmpty(filter.Surname) || c.Surname.Equals(filter.Surname, StringComparison.OrdinalIgnoreCase))
                    && (string.IsNullOrEmpty(filter.City) || c.City.Equals(filter.City, StringComparison.OrdinalIgnoreCase))
                );
        }
    }
}

La classe “CustomerRepository” nel metodo “GetCustomers” accetta in ingresso un oggetto di tipo “CustomerFilter” (utilizzato per aggregare le informazioni di filtro) implementata come segue

namespace DesingPattern.Sample.Observer.Model
{
    /// <summary>
    /// Rappresenta il filtro di ricerca per i Clienti.
    /// </summary>
    public class CustomerFilter
    {
        public string Name { get; set; }
        public string Surname { get; set; }
        public string City { get; set; }
        public bool UseLikeFilter { get; set; }
    }

inoltre, utilizzando le nozioni alla base del pattern “NullObject” implementiamo anche la classe “NullCustomerFilter” che in questo caso si riduce ad essere una classe vuota che eredita da “CustomerFilter”

namespace DesingPattern.Sample.Observer.Model
{
    /// <summary>
    /// Rappresenta il filtro vuoto di ricerca per i Clienti.
    /// </summary>
    public class NullCustomerFilter : CustomerFilter
    {
    }
}

L’implementazione d’esempio del pattern observer l’abbiamo già vista, ma rispetto a quella dobbiamo aggiungere qualcosa che permetta ai sottoscrittori di ricevere dei dati durante la fase di notifica. A tale scopo modifichiamo la firma del metodo “Update” aggiungendo a questa un parametro in input (del tipo a noi necessario) così da poter passare le informazioni al sottoscrittore durante la chiamata al metodo “Notify” del pubblicatore:

Nota
E’ di estrema importanza comprendere che modificare la firma del metodo Update non vuol dire modificare la struttura del Pattern Observer, i pattern identificano il disegno della soluzione da implementare e non la loro esatta implementazione.

Se a questo punto siamo abbastanza furbi da utilizzare i generics possiamo costruire un interfaccia generica per qualsiasi tipo di sottoscrittore in questo modo:

namespace DesingPattern.Sample.Observer.Interfaces
{
    /// <summary>
    /// Interfaccia da implementare in tutti i sottoscrittori.
    /// </summary>
    public interface IObserver<T>
    {
        /// <summary>
        /// Aggiorna lo stato interno del sottoscrittore quando riceve 
        /// l'evento di notifica dal pubblicatore.
        /// </summary>
        /// <param name="data">Dati da passare durante la notifica.</param>
        void Update(T data);
    }
}

Nota
Attenzione a partire dalla versione 4.0 del Framework .Net l’interfaccia <a title="Interfaccia System.IObserver” href=”http://msdn.microsoft.com/it-it/library/dd783449(v=vs.100).aspx” target=”_blank”>IObserver<T> è già presente all’interno del namespace System quindi attenti agli using.

Allo stesso modo, sempre utilizzando i generics, modificheremo l’interfaccia ISubject come segue:

namespace DesingPattern.Sample.Observer.Interfaces
{
    /// <summary>
    /// Interfaccia da implementare sul pubblicatore.
    /// </summary>
    interface ISubject<T>
    {
        /// <summary>
        /// Registra un sottoscrittore all'evento di notifica. 
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        void Attach(IObserver<T> o);

        /// <summary>
        /// Rimuove un sottoscrittore dall'evento di notifica.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        void Detach(IObserver<T> o);

        /// <summary>
        /// Notifica a tutti i sottoscrittori il cambio di stato.
        /// </summary>
        void Notify();
    }
}

A questo punto andiamo ad implementare una classe astratta che rappresenti la versione base del controllo web di ricerca che erediti da System.Web.UI.Usercontrol

namespace DesingPattern.Sample.Observer.WebUserControls
{
    /// <summary>
    /// Controllo di filtro base.
    /// </summary>
    /// <typeparam name="T">Tipo di filtro.</typeparam>
    public abstract class FilterControlBase<T> : System.Web.UI.UserControl, Interfaces.ISubject<T>
    {
        private T _filter;
        /// <summary>
        /// Lista interna degli sottoscrittori registrati all'evento di notifica.
        /// </summary>
        private List<Interfaces.IObserver<T>> _observers = new List<Interfaces.IObserver<T>>();

        /// <summary>
        /// Restituisce o imposta il valore del filtro.
        /// </summary>
        public T Filter
        {
            get { return _filter; }
            set
            {
                _filter = value;
                // Ogni volta che il valore del filtro cambia
                // viene spedita la notifica ai sottoscrittori.
                this.Notify();
            }
        }

        /// <summary>
        /// Sottoscrive un osservatore. 
        /// </summary>
        /// <param name="o">Riferimento al sottoscrittore.</param>
        public void Attach(Interfaces.IObserver<T> o)
        {
            _observers.Add(o);
        }

        /// <summary>
        /// Rimuove un osservatore.
        /// </summary>
        /// <param name="o">Riferimento al sottoscrittore</param>
        public void Detach(Interfaces.IObserver<T> o)
        {
            _observers.Remove(o);
        }

        /// <summary>
        /// Notifica, a tutti gli osservatori sottoscritti, 
        /// che il valore del filtro è stato modificato.
        /// </summary>
        public void Notify()
        {
            foreach (Interfaces.IObserver<T> o in _observers)
            {
                o.Update(this.Filter);
            }
        }
    }
}

La cose da notare nell’implementazione della classe “FilterControlBase” sono:

  1. La presenza della proporty “Filter” tipizzata tramite i generics;
  2. Il Setter della property “Filter” che invoca il metodo “Notify” ogni volta che viene cambiato lo stato del filtro;
  3. Nel metodo “Notify” a tutti i sottoscrittori viene notificato il valore del nuovo filtro.

Ormai ci siamo, le ultime cose di cui abbiamo bisogno sono i due web user control e la pagina che li ospiterà.

Il controllo Search.ascx

Il controllo Search.ascx identifica il nostro pubblicatore.
Nel markup inseriamo una serie di controlli per la raccolta dell’input utente e due pulsanti uno per effettuare la ricerca l’altro per ripulire la form.

<%@ Control Language="C#" 
    AutoEventWireup="true" 
    CodeBehind="Search.ascx.cs" 
    Inherits="DesingPattern.Sample.Observer.WebUserControls.Search" %>

<fieldset id="search">
    <legend>Search Fields</legend>
    <asp:CheckBox ID="chkUseLikeFilter" runat="server" /> Use Like Mode
    <div class="filters break">
        <div class="field">
            <asp:Label ID="lblSurname" runat="server" 
                       AssociatedControlID="txtSurname" Text="Surname" />
            <asp:TextBox ID="txtSurname" runat="server" />
        </div>
        <div class="field">
            <asp:Label ID="lblName" runat="server" 
                       AssociatedControlID="txtName" Text="Name" />
            <asp:TextBox ID="txtName" runat="server" />
        </div>
        <div class="field">
            <asp:Label ID="lblCity" runat="server" 
                       AssociatedControlID="txtCity" Text="City" />
            <asp:TextBox ID="txtCity" runat="server" />
        </div>
        <div class="buttons break">
            <asp:Button ID="btnApplyFilter" runat="server" 
                        Text="Apply Filter" OnClick="btnApplyFilter_Click" />
            <asp:Button ID="btnClearFilter" runat="server" 
                        Text="Clear Filter" OnClick="btnClearFilter_Click" />
        </div>
    </div>
</fieldset>

Il codice del controllo contiene i soli gestori d’evento dei due pulsanti dove vengono reimpostati i filtri. Inoltre ereditando dalla classe astratta “FilterControlBase” nel momento in cui viene reimpostata la property “Filter” viengono anche automaticamente notificati ai sottoscrittori i nuovi valori di filtro.

namespace DesingPattern.Sample.Observer.WebUserControls
{
    public partial class Search : WebUserControls.FilterControlBase<CustomerFilter>
    {
        protected void btnApplyFilter_Click(object sender, EventArgs e)
        {
            // Alla pressione del pulsante di ricerca sul controllo basterà 
            // aggiornare il valore del filtro intrinsecamente legato al pubblicatore.
            this.Filter = new CustomerFilter() { 
                UseLikeFilter = chkUseLikeFilter.Checked,
                Surname = txtSurname.Text,
                Name = txtName.Text,
                City = txtCity.Text
            };
        }

        protected void btnClearFilter_Click(object sender, EventArgs e)
        {
            // Alla pressione del pulsante Clear sul controllo basterà 
            // aggiornare il valore del filtro intrinsecamente legato al pubblicatore.
            this.Filter = new NullCustomerFilter();

            // Ripuliamo inoltre i controlli di input
            chkUseLikeFilter.Checked = false;
            txtSurname.Text = string.Empty;
            txtName.Text = string.Empty;
            txtCity.Text = string.Empty;
        }
    }
}

Il controllo SearchResults.ascx

Il controllo SearchResults.ascx identifica invece il nostro sottoscrittore.
Nella parte di marckup inseriamo un semplice repeater, come segue

<%@ Control Language="C#" 
    AutoEventWireup="true" 
    CodeBehind="SearchResults.ascx.cs" 
    Inherits="DesingPattern.Sample.Observer.WebUserControls.SearchResults" %>

<asp:Repeater ID="rptResults" runat="server">
    <HeaderTemplate>
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Surname</th>
                    <th>Name</th>
                    <th>City</th>
                </tr>
            </thead>
            <tbody>
    </HeaderTemplate>
    <ItemTemplate>
        <tr>
            <td><%#Eval("Id")%></td>
            <td><%#Eval("Surname")%></td>
            <td><%#Eval("Name")%></td>
            <td><%#Eval("City")%></td>
        </tr>
    </ItemTemplate>
    <FooterTemplate>
            </tbody>
        </table>
    </FooterTemplate>
</asp:Repeater>

mentre nella parte di codice inseriamo

  1. Il gestore di evento relativo al Load del controllo, che si occuperà di bindare la lista dei clienti al repeater durante il primo caricamento (ovvero quando la pagina viene caricata per la prima volta ed il filtro non è stato ancora impostato);
  2. Il metodo Update, relativo all’implementazione dell’interfaccia “Interfaces.IObserver” (che viene invocato dal publicatore durante l’esecuzione del metodo “Notify”) nel quale viene filtrata la lista dei clienti in base ai valori di filtro passati durante la notifica del pubblicatore.
namespace DesingPattern.Sample.Observer.WebUserControls
{
    public partial class SearchResults : System.Web.UI.UserControl, 
                                         Interfaces.IObserver<CustomerFilter>
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                // Binda i dati non filtrati sul repeater per la prima visualizzazione.
                rptResults.DataSource = CustomerRepository.GetAllCustomers();
                rptResults.DataBind();
            }
        }

        public void Update(CustomerFilter data)
        {
            // Binda i dati filtrati sul repeater.
            rptResults.DataSource = CustomerRepository.GetCustomers(data);
            rptResults.DataBind();
        }
    }
}

La pagina web contenitore

In fine nella pagina Default.aspx inseriamo i due web controls

<%@ Page Language="C#" AutoEventWireup="true" 
         CodeBehind="Default.aspx.cs" 
         Inherits="DesingPattern.Sample.Observer.Default" %>
<%@ Register src="WebUserControls/Search.ascx" 
             tagname="Search" tagprefix="uc" %>
<%@ Register src="WebUserControls/SearchResults.ascx" 
             TagName="SearchResults" tagprefix="uc" %>

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>ASP.Net &amp; Pattern Observer in Practice</title>
</head>
<body>
    <form id="form1" runat="server">
        <h1>ASP.Net &amp; Pattern Observer in Practice</h1>
        <uc:Search ID="ucSearch" runat="server" />
        <uc:SearchResults ID="ucSearchResults" runat="server" />
    </form>
</body>
</html>

e nel code-behind registriamo il sottoscrittore alla notifica nel gestore d’evento “Init” della pagina

namespace DesingPattern.Sample.Observer
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Init(object sender, EventArgs e)
        {
            // Registra il componente "ucSearchResults" alla notifica.
            ucSearch.Attach(ucSearchResults);
        }
    }
}

Bene se hai resistito fino a questo punto ti meriti di scaricare il progetto di esempio, ma prima di lasciarti voglio aggiungere ancora uno spunto..

Immagina che, nell’esempio implementato dopo l’operazione di ricerca, effettuando un click su una delle righe presenti nella griglia dei clienti tu debba visualizzare un dettaglio più corposo dei dati relativi al cliente selezionato… 😉
Chi vieta che un sottoscrittore (in questo caso SearchResults.ascx) sia contemporaneamente anche un pubblicaotore?

Sperando di averti stuzzicato abbasanza ti lascio come esercizio questo unltimo punto, ormai dovresti essere perfettamente a tuo agio con il pattern observer!!!

ASP.Net e Pattern Observer in Pratica – Terza Parte

Nel post ASP.Net e Pattern Observer in Pratica – Seconda Parte abbiamo visto la teoria alla base del Pattern Observer e precedentemente in ASP.Net e Pattern Observer in Pratica – Prima Parte le motivazioni che mi hanno spinto ad affrontare la questione. Adesso vedremo un’implementazione didattica d’esempio per chiarire un po’ le idee prima di addentrarci nell’implementazione finale dello scenario presentato in principio.

Implementazione del Pattern Observer

Prima di iniziare ci tengo a ripetere che questa sarà un’implementazione ridotta al minimo, ovvero scritta al solo scopo di farti comprendere il disegno della soluzione proposta dal pattern, mentre nel prossimo post vedremo un’implementazione reale che, seppur semplice, potrebbe risultarti utile nel lavoro di tutti i giorni 😉

Iniziamo con la definizione dell’interfaccia che identifica il pubblicatore (ISubject)

    /// <summary>
    /// Interfaccia da implementare sul pubblicatore.
    /// </summary>
    interface ISubject
    {
        /// <summary>
        /// Registra un sottoscrittore all'evento di notifica.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        void Attach(IObserver o);

        /// <summary>
        /// Rimuove un sottoscrittore dall'evento di notifica.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        void Detach(IObserver o);

        /// <summary>
        /// Notifica a tutti i sottoscrittori il cambio di stato.
        /// </summary>
        void Notify();
    }

e poi quella che identifica gli eventuali sottoscrittori (IObserver)

    /// <summary>
    /// Interfaccia da implementare in tutti i sottoscrittori.
    /// </summary>
    public interface IObserver
    {
        /// <summary>
        /// Aggiorna lo stato interno del sottoscrittore quando riceve
        /// l'evento di notifica dal pubblicatore.
        /// </summary>
        void Update();
    }

ora veniamo all’implementazione dell’interfaccia ISubject

    /// <summary>
    /// Implementazione di esempio per il pubblicatore.
    /// </summary>
    public class Subject : ISubject
    {
        /// <summary>
        /// Lista interna degli oggetti sottoscritti all'evento di notifica.
        /// </summary>
        private List<IObserver> _observers = new List<IObserver>();

        /// <summary>
        /// Sottoscrive un sottoscrittore.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        public void Attach(IObserver o)
        {
            if (!(o == null || _observers.Contains(o)))
                _observers.Add(o);
        }

        /// <summary>
        /// Rimuove un sottoscrittore.
        /// </summary>
        /// <param name="o">Interfaccia che identifica un Sottoscrittore.</param>
        public void Detach(IObserver o)
        {
            if(o != null)
                _observers.Remove(o);
        }

        /// <summary>
        /// Notifica a tutti i sottoscrittori il cambio di stato.
        /// </summary>
        public void Notify()
        {
            // Cicla tutti i sottoscrittori registrati e ne invoca il metodo Update.
            foreach (IObserver o in _observers)
            {
                o.Update();
            }
        }

        /// <summary>
        /// Metodo di esempio che muta lo stato interno dell'oggetto.
        /// </summary>
        public void ChangeInternalStatusMethod()
        {
            // Operazioni varie...
            // this.Operation1();
            // this.Operation2();
            // ecc..

            // Notifica
            this.Notify();
        }
    }

e poi l’implementazione dell’interfaccia IObserver

    /// <summary>
    /// Implementazione di esempio per il sottoscrittore.
    /// </summary>
    public class Observer : IObserver
    {
        /// <summary>
        /// Aggiorna lo stato interno del sottoscrittore quando riceve
        /// l'evento di notifica dal pubblicatore.
        /// </summary>
        public void Update()
        {
            // La scrittura nella finestra di debug serve solo per darti
            // la possibilità di verificare che la notifica è avvenuta.
            Debug.WriteLine(string.Format("Notifica avvenuta alla: {0}", DateTime.Now.ToString()));
        }
    }

Bene il codice relativo al pattern è completo, non ci resta che verificare come gira il tutto all’interno di un programma che per semplicità sarà una Console Application

class Program
    {
        static void Main(string[] args)
        {
            // Istanzia il pubblicatore.
            Subject subject = new Subject();

            // Istanzia e registra i sottoscrittori alla notifiche.
            IObserver observer1 = new Observer();
            subject.Attach(observer1);

            IObserver observer2 = new Observer();
            subject.Attach(observer2);

            // Il metodo "ChangeInternalStatusMethod" identifica un metodo di
            // esempio che dopo alcune elaborazioni cambia lo stato interno
            // del pubblicatore, al suo interno viene invocato il metodo Notify()
            // in modo che i sottoscrittori ricevano le nodifiche di avvenuto
            // cambio di stato.
            subject.ChangeInternalStatusMethod();

            Console.Read();
        }
    }

Come ho già detto questa è un’implementazione didattica al solo scopo di capire come gira il tutto ma così fatta è poco utile, in quanto nella maggior parte delle implementazioni reali al momento della notifica risulta necessario che ai sottoscrittori venga notificato non solo che c’è stato cambimento di stato ma anche cosa è cambiato nel contesto del pubblicatore in modo da poter reagire di conseguenza…

Bene nel prossimo post ne parleremo diffusamente con un esempio reale.

ASP.Net e Pattern Observer in Pratica – Seconda Parte

Nel post ASP.Net e Pattern Observer in Pratica – Prima Parte ho presentato un caso reale (anche se estremamente semplice) di uno di quei tantissimi casi in cui un cattivo (mancato) design possa portare sorprese inaspettate durante lo sviluppo di un’applicazione software.
Con molto dispiacere mi permetto di aggiungere, senza però voler aprire aspre ed infruttuose polemiche, che purtroppo queste situazioni non sono per nulla rare.
Tornando a noi…

Il Pattern Observer

Il pattern Observer, è un’elegante soluzione che permette di generare una relazione uno a molti tra diversi oggetti dove uno in particolare ha la possibilità di notificare agli altri un suo cambio di stato ed eventualmente i valori modificati senza violare le best practices per mantenere un corretto incapsulamento.

In sostanza il pattern segue il modello Publisher and Subscribe dove alcune entità chiamate sottoscrittori si registrano presso un pubblicatore che li informa ogni qualvolta ci siano delle nuove informazioni da notificare.

Di seguito vediamo il diagramma UML del pattern observer:

Design Pattern - Observer

Nel diagramma la classe Subject rappresenta il pubblicatore che espone pubblicamente i seguenti tre metodi

  • Attach (Utilizzato per registrare un sottoscrittore alle notifiche);
  • Detach (Utilizzato per la rimuovere la registrazione di un sottoscrittore alle notifiche);
  • Notify (Utilizzato per notificare ai sottoscrittori il cambio di stato).

Le classi Observer, Observer2 e Observer3 rappresentano invece tre implementazioni di sottoscrittori e espongono il metodo Update che sarà utilizzato all’interno di Notify del pubblicatore per aggiornarli relativamente al suo cambio di stato.

Ovviamente per far si che i sottoscrittori (classi Observer, Observer2, ecc..) possano ricevere le notifiche da parte del pubblicatore (classe subject) dovranno in qualche modo sottoscriversi alla notifica. Questo è possibile grazie all’evento “Attach(IObserver o);” esposto dal publicatore che accettando in ingresso un tipo IObserver da la possibilità di registrare un riferimento agli oggetti sottoscritti all’evento di notifica.

Di seguito vediamo un sequence diagram che dovrebbe chiarire un po’ le idee relativamente all’andamento della notifica.

Design Pattern - Observer sequence diagram

Nel prossimo post vedremo un’ implementazione di esempio del pattern Observer, d’altro canto il titolo del post promette un esempio pratico ma qualcuno potrebbe obbiettare che siamo già alla seconda parte e di pratica ne abbiamo fatta poca 😛
E’ vero ma un po’ di teoria iniziale per qualche lettore magari poteva risultare necessaria…

ASP.Net e Pattern Observer in Pratica – Prima Parte

Ieri mi è stata chiesta una piccola consulenza relativamente ad un bug presente su una pagina web sviluppata in ASP.Net Web Forms e vorrei condividere con voi sia lo scenario sia la soluzione implementata così da approfittare per parlare del Pattern Observer.

Il Requisito

Implementazione di una pagina di ricerca dove sono presenti alcuni web user controls tra i quali uno in particolare si occupa di raccogliere l’input dell’utente per effettuare una ricerca tramite la pressione di un pulsante apposito (presente sullo stesso user control), l’operazione di ricerca deve in qualche modo essere notificata agli altri user controls presenti sulla stessa pagina che utilizzando gli stessi valori inseriti nel primo controllo dovranno filtrare e renderizzare i dati di dettaglio.

La Soluzione originariamente implementata

La soluzione originariamente implementata, ovvero quella che generava bug, lavorava più o meno così:

  • Creazione di 3 user control
    • Ricerca.ascx
    • ListaDettagli.ascx
    • Dettaglio.ascx
  • L’utente inputa una serie di valori (tramite TextBox, CheckBox, ed altri controlli… presenti nel controllo Ricerca.ascx);
  • L’utente Clicca sul pulsante “Cerca” (sempre presente nel controllo Ricerca.ascx);
  • L’user control nel gestore di evento associato al click del bottone “cerca” istanzia un oggetto preposto con tutte le informazioni inserite dall’utente e lo memorizza in sessione;
  • Nell’evento Load degli altri user control viene letto l’oggetto in sessione e se questo non è null allora viene utilizzato come filtro.

Le problematiche riscontrate

Quello che il cliente lamenta è che la ricerca funziona a singhiozzi… ovvero:

  • La prima ricerca non funziona mai;
  • La seconda ricerca funziona se non vengono cambiati i dati inputati per la ricerca;
  • Le seguenti ricerche filtrano i dati dei dettagli con i dati inputati dall’utente per la ricerca precedente.

..ma come mai? ..e soprattutto sono solo questi i problemi effettivamente portati dalla soluzione implementata? ..oppure ce ne sono altri che l’utente (o addirittura il pogrammatore) non sta considerando? Inoltre qual’è il grado di portabilità e/o di estensione delle componenti software sviluppate?

La motivazione del problema ed una soluzione apparente

La motivazione del problema è molto semplice, nel ciclo di vita di una pagina ASP.Net  l’evento Postback viene scatenato dopo l’evento Load, quindi quando i controlli di dettaglio leggono il valore dalla sessione (nell’evento Load) lo leggono prima che l’user control Ricerca.ascx lo inserisca in sessione durante il Postback!

A questo punto, individuata l’origine del comportamento anomalo, potremmo essere portati a pensare che posticipare la lettura dei dati della sessione in un evento successivo al Postback (per esempio il PreRender) risolva il problema… bè diciamo che l’anomalia relativa alla ricerca effetivamente scompare ma questa è veramente una soluzione? ..oppure è quella che più comunemente  nel nostro campo viene definita come una pezza su un’implementazione poco controllabile?

Mi spiace dire che purtroppo siamo nel secondo caso…

..e allora cerchiamo di capire insieme come i design pattern possano aiutarci nell’implementare una soluzione più elegante ed affidabile, vediamo a cosa serve e come implementare il pattern observer.

Pattern MVP ed Asp.Net – Prima parte

La complessità delle interfacce grafiche, sia in applicazioni Client/Server che Web Based, è costantemente in aumento e quindi nel tempo sono stati proposti diversi strumenti per facilitarne lo sviluppo come l’approccio RAD (Rapid Application Development) e l’introduzione di widget e controlli sempre più complessi e con potenti meccanismi di databinding dichiarativo. Ovviamente tali facility, come ho gia affermato in altre occasioni,  hanno spostato l’attenzione del programmatore sempre più sulla configurazione che sullo sviluppo vero e proprio (in questo caso del layer di presentazione) portando da un lato ad un aumento della produttività iniziale ma dall’altro allo sviluppo di applicazioni monolitiche difficilmente testabili in ambiente isolato e di conseguenza difficilmente estendibili senza incorrere nella prolificazione di bug di riflessione (stiamo parlando dunque di applicazioni difficilmente manutenibili).

Fortunatamente esistono diversi pattern architetturali che tentano di risolvere questo tipo di problematiche. Negli ultimi anni è il pattern MVC (Model View Contoller – Model2) ad essere sotto i riflettori come scelta vincente in tutti quei casi in cui si voglia sviluppare una GUI (Graphic User Interface) fortemente disaccoppiata dalle logiche di business. In effetti MVC è un’elegante soluzione che assolve splendidamente a tale problema, ma in alcuni contesti tecnologici (vedi l’utilizzo di ASP.Net Web Form) che non offrono un supporto nativo a tale pattern “potrebbe” non essere conveniente prendersi l’onere di implementare tutta l’infrastruttura necessaria per la sua implementazione. In questi casi il Pattern MVP è una soluzione da tenere assolutamente in considerazione 😉

Il Pattern MVP

Il pattern MVP (Model View Presenter) risale all’anno 1979 ed il suo nome originario era Thing Model View Editor (coniato dalla Taligent), nel tempo poi evoluto grazie soprattutto a Martin Fowler che ne ha sviluppato due diverse implementazioni Supervising Presenter e Passive View. La soluzione proposta prevede il disaccoppiamento totale della View dalle logiche di manipolazione del Modello tramite l’introduzione di un componente, il Presenter, che funga da intermediario e da coordinatore in risposta alle interazioni con l’utente. Ma vediamo nel dettaglio gli attori coinvolti:

  • View (visualizza i dati contenuti nel model e raccoglie gli input dell’utente)
  • Model (rappresenta il dominio di busines ed incapsula lo stato dell’applicazione)
  • Presenter (interagisce con il model in base alle richieste ricevute dalla view)
Schema di interazione – MVP Supervising Controller
In questo tipo di implementazione la view conosce il modello e generalemente è connessa ad esso tramite un meccanismo di data binding dichiarativo che permette l’aggiornamento dei dati da visualizzare (in situazioni complesse dove il data bindig dichiarativo non è sufficiente a garantire tale l’aggiornamento è necessario l’intervento del presenter).
  • L’utente interagisce in qualche modo con la View;
  • La View notifica al Presenter dell’interazione;
  • Il Presenter opera sul Model eventualmente modificandone lo stato in rispetto all’interazione notificata dalla View;
  • La View viene aggiornata in base ai nuovi dati esposti dal Model tramite un meccanismo di data binding;
  • Il Presenter, se presenti, aggiorna i dati della view che non possono essere agganciati a quest’ultima tramite data binding dichiarativo;

Schema di interazione – MVP Passive View

In questo caso la view non conosce il modello (non vi è quindi alcuna dipendenza tra la View ed il Modello) ed il presenter è l’unico responsabile dell’aggiornamento dei dati visualizzati.

  • L’utente interagisce in qualche modo con la View;
  • La View notifica al Presenter dell’interazione;
  • Il Presenter opera sul Model eventualmente modificandone lo stato in rispetto all’interazione notificata dalla View;
  • Il Presenter aggiorna la View in base ai nuovi dati esposti dal Model;

Minimizzare le dipendenze

Una delle problematiche principali che spesso impediscono la realizzazione di applicazioni modulari e facilmente estendibili è rappresentato dalle dipendenze esistenti tra i componenti che le costituiscono. Eliminare queste dipendenze vuol dire poter godere di innumerevoli vantaggi come la possibilità di effettuare test automatici ed avere un codice più incline al cambiamento. Negli ultimi anni, la marcata diffusione di framework come Spring, ha portato l’attenzione di un gran numero di sviluppatori su interessanti concetti quali la gestione dell’IoC (Inversion of Control), l’implementazione di tecniche relative alla AOP (Aspect Oriented Programming) e servizi di Service Locator Transparency che, se ben applicati, ci aiutano a minimizzare le dipendenze tra le entità di un sistema.

Ma cerchiamo di chiarire il concetto di dipendenza:

  1. “Una classe A dipende da una classe B quando: A eredita da B”
  2. “Una classe A dipende da una classe B quando: A crea un istanza e/o utilizza un istanza di B ”
  3. “Una classe A dipende da una classe C quando: B dipenda da C ed A dipende da B”

Inversion of Control (o IoC)
Solitamente nello scrivere applicazioni di una certa complessità spesso si finisce ad avere frequenti casi di dipendenza tra le classi di un Domain Model, questo accade perché, in generale, si rendono le classi stesse responsabili della propria inizializzazione e soprattutto nel costruttore, o in un metodo di inizializzazione preposto, si fa si che la classe istanzi tutto ciò di cui ha bisogno per funzionare correttamente. In parole povere è il codice stesso che ha il “controllo” di come vengono inizializzati gli oggetti. In progetti di elevata complessità tutto questo porta spesso ad avere diversi problemi in fase di refactory dell’architettura applicativa. Possiamo per esempio immaginare quei casi in cui, a fronte di nuove esigenze, si abbia la necessità di modificare il processo di inizializzazione di alcune entità del nostro modello, ovviamente ciò comporta il dover “mettere mano” al codice in tutti quei punti in cui queste vengono istanziate ed inizializzate. Per ovviare a tali problematiche si possono sfruttare dei meccanismi di inizializzazione configurabile, ed è proprio in questo ambito che l’Inversion of Control (IoC) trova il suo campo d’applicazione.

In generale non è detto che un oggetto sia a conoscenza del macro-contesto nel quale sarà utilizzato, ciò significa che un’entità dovrebbe saper comunicare con l’ambiente nel quale viene posta nel modo più generico possibile e potersi, quindi, considerare indipendente dal contesto applicativo corrente. A tal proposito viene introdotto il concetto di iniettore (talvolta chiamato anche assemblatore) ovvero l’entità che dovrebbe occuparsi, di “preparare i componenti già pronti all’uso” su richiesta di un qualsiasi consumer in modo da svincolare quest’ultimo dall’onere di istanziare ed inizializzare le sue dipendenze (leggi classi utilizzate). In secondo luogo bisogna dire che il consumer stesso, essendo un eventuale candidato all’utilizzo da parte di altre entità, non dovrebbe doversi preoccupare neanche della sua inizializzazione.

Progettare un iniettore è un’operazione che in prima analisi più sembrare un operazione alquanto semplice, ma bisognerebbe considerare con attenzione che sviluppare un iniettore troppo semplice potrebbe comportare come unico risultato lo spostamento delle problematiche succitate in un’altro punto dell’applicazione. Un iniettore per avere senso di esistere deve garantire un elevato grado di flessibilità ottenibile con un altrettanto elevato grado di configurabilità.

La Dependency Injection
La Dependency Injection è un design pattern che tenta di risolvere i problemi di dipendenza tra entità di un modello di domino attuando politiche di Inversion Of Control. Con la Dependency Injection una classe o un sistema non è responsabile dell’inizializzazione delle proprie dipendenze ma prevede la presenza di un iniettore che “inietti” tali dipendenze direttamente negli oggetti gestiti.

L’iniettore deve offrire un meccanismo di configurazione utilizzato per la definizione degli oggetti conosciuti e di come questi siano correlati tra di loro; alla richiesta di un dato oggetto l’iniettore controlla quali altre classi sono ad esso collegate, le crea, e le inietta al suo interno prima di rendere l’oggetto disponibile. Arrivati a questo punto, potremmo quasi considerare le classi del nostro domain model come dei dispensatori di servizi e l’iniettore come un Service Locator particolare in quanto oltre ad individuare le classi svolge anche il compito non banale di inizializzarle e renderle pronte all’uso.

NOTE:

In generale per minimizzare le dipendenze tra oggetti è necessario che questi dialoghino tra loro tramite interfacce (o classi astratte per i linguaggi che le supportano), in questo modo l’oggetto dipendente (un oggetto A ad esempio) può regredire al grado di semplice utilizzatore senza doversi più preoccupare di conoscere le modalità con le quali la sua dipendenza (intesa come un oggetto B dal quale dipende A) viene instanziata e inizializzata. Potremmo dire  che l’oggetto A, a questo punto, sa che dialogherà con qualcuno (che conosce come IB e non come B, ovvero conosce la sua interfaccia e non una sua speciale implementazione) e che questo qualcuno saprà come portare a termine determinate operazioni ma senza sapere questo qualcuno chi sia (A conosce cosa c’è da fare ma non chi e come lo farà). L’oggetto B, quindi, sarà costretto ad implementare un interfaccia (un comportamento da garantire, IB appunto).

Una dependency è un modo diverso di chiamare le variabili di un classe, i suoi campi, o se si preferisce la variabili di istanza che dipendono da tipi non appartenenti al core standard del linguaggio utilizzato.

Il procedimento di injection è l’inizializzazione di una variabile (della dependency), ciò avviene quando all’esterno della classe che contiene la dependency  si passa la dependency stessa già inizializzata che può avvenire tramite costruttore, tramite proprietà (interfaccia) o tramite metodo setter.

IoC Container
L’importanza dell’implementazione sempre crescente dei meccannismi di IoC e di DI in progetti reali ha portato negli ultimi anni allo sviluppo di svariati framework per semplificarne l’utilizzo (un famoso esempio ne è Srping), tali framework sono considerati come IoC Container ovvero una sorta di repository dove a fronte di un’adeguata configurazione possono essere referenziati dei mapping sui tipi i nmodo che il framework possa su richiesta dei consumer scovare le dipendenze instanziarle e iniettarle al chiamante. Definendo quindi delle interfacce comportamentali, ed implementandole nelle nostre classi concrete, risulta possibile accoppiare le classi a run-time senza più porsi il problema di conoscere le entità concrete a compile-time.

S.O.L.I.D. – Solidi principi per la scrittura di buon codice

I principi di seguito elencati, ed in parte spiegati, dovrebbero aiutare il programmatore, laddove non arriva l’esperienza, ad ottenere un codice di qualità che mantenga una forte coesione ed un basso accoppiamento tra le entità del sistema. Non devono però diventare un dogma, ma uno strumento da conoscere e da utilizzare a seconda degli obiettivi e dei requisiti applicativi.

Single Responsibility Principle (SRP)
Ogni classe deve essere disegnata per svolgere bene un solo compito, avere una sola responsabilità, in pratica una classe deve avere un solo motivo per cambiare. Seguire questo principio porta a diminuire per quanto possibile l’accoppiamento tra le entità coinvolte in uno o più processo. Uno dei modi più semplici per scrivere classi da modificare veramente di rado consiste nello scrivere classi che svolgono una sola azione. In questo modo, la classe dovrà essere modificata soltanto se sarà necessario modificare l’azione per la quale è stata creata.

Open/Closed Principle (OCP)
Una classe deve essere aperta alle estensioni, ma chiusa alle modifiche. Dobbiamo quindi essere in grado di estendere il comportamento di una classe senza modificarne l’implementazione. Come? Facendo uso di classi astratte o interfacce, ovvero sfruttando il concetto di polimorfismo.

Liskov Substitution Principle (LSP)
Una funzione che utilizza un riferimento ad una classe base deve poter utilizzare al suo posto una qualsiasi delle classi derivate senza conoscerne l’implementazione. In pratica non dobbiamo modificare in alcun modo il comportamento di una classe base in una derivata. Particolare attenzione dovrebbe infatti essere rivolta ai concetti pre-condizioni e le post-condizioni relative al ciclo di vita di una classe e delle classi da essa ereditate. In generale non si può pensare di scrivere una classe base partendo da determinate precondizioni e pensare di specializzare da essa una classe che ne presenti altre di tutt’altra forma. In alcuni contesti quindi si dovrebbe valutare se preferire dove possibile la composizione all’ereditarietà, ponendosi ogni volta la fatidica domanda IS A or HAS A?

Interface Segregation Principle (ISP)
Una classe non deve implementare una interfaccia che non usa o che usa parzialmente. Molte volte si deve preferire avere più interfacce con una sola funzionalità che averne una unica ma che si occupa di troppe cose. Il motivo? Una classe è influenzata dal cambiamento di una interfaccia anche se non la usa.

Dependency Inversion Principle (DIP)
Questo principio si basa sul concetto che una classe di un alto livello non deve dipendere dall’implementazione di classi o entità di un livello inferiore. In pratica per raggiungere tale scopo si dovrebbero progettare le classi pensando alle interfacce e non alle implementazioni. Questo porta ad avere non solo un basso accoppiamento tra i livelli dell’architettura, ma automaticamente a poter sostituire l’implementazione di un livello inferiore senza pregiudicare il funzionamento di quelli superiori.