giovedì 18 dicembre 2008

Operatori Cast e OfType di LINQ: usare LINQ su colllezioni non generics

Linq è nato per essere usato su collezioni che implementano IEnumerable.
Ad esempio su List

IList<int> mialista = new List<int>();

IEnumerable<int> result = mialista.Where(miovalore => miovalore==44).Select(miovalore => miovalore);



Ma come si può usare LINQ su collection di vecchio tipo? Ad esempio su una normalissima ArrayList ?
Usando l'operaatore Cast, oppure l'operatore OfType in questo modo:
poniamo di avere una ArrayList in cui inseriamo un pò di interi, ma anche una stringa.

ArrayList mialista = new ArrayList();

mialista.Add(44);

mialista.Add(55);

mialista.Add("pippo");


Ora vogliamo recuperare l'oggetto, o gli oggetti, di valore 44.
Dato che noi cerchiamo un intero, dobbiamo modificare la ArrayList in modo che diventi una IEnumerable.

Questo si ottiene usando Cast():

IEnumerable<int> listaGenerics =mialista.Cast<int>();
IEnumerable<int> result = listaGenerics.Where(miovalore => miovalore==44).Select(miovalore => miovalore);

oppure usando OfType():

IEnumerable<int> listaGenerics =mialista.OfType<int>();
IEnumerable<int> result = listaGenerics.Where(miovalore => miovalore == 44).Select(miovalore => miovalore);


La differenza è che Cast prova a castare tutti gli oggetti nella lista. Se non riesce a castare anche un solo oggetto lancia eccezione. Nel nostro caso quindi avremo un'eccezione dato che non riuscirà a castare ad intero la stringa "pippo".

Invece OfType si accontenta di castare solo gli oggetti che può castare. Gli altri vengono tralasciati e non vengono inseriti nella collection IEnumerable. Nel nostro caso la collection listaGenerics  conterrà solo i valori 44 e 55.

Prerequisiti per usare Linq To Object in un progetto C#


Per utilizzare le librerie .NET di LINQ to Object è necessario:

  • che il progetto abbia come target framework ".NET framework 3.5" o successivi (modificabile dalla finestra delle proprietà del progetto)
  • che il progetto includa fra le references quella a System.Data.Linq
  • che la classe che necessita di usare funzioni di Linq includa, tramite uno "using", il namespace System.Linq
A questo punto sarà possibile usare, su tutti gli oggetti che estendono IEnumerable, i metodi che Linq mette a disposizione. Son facilmente identificabili perchè sono extension methods, e dunque l'intellisense li visualizza in modo particolare. Il solito cubetto che contraddistingue il metodo ha associato una freccia blu rivolta verso il basso. Vedi figura qui sotto...

Gli oggetti su cui Linq to Object è utilizzabile sono tutti gli array (come int[],  MyCustomObject[], ...) , tutte le collezioni di generics (List ,...) e, con un minimo adattamento, anche sulle collezioni non-generics, usando gli operatori Cast  e OfType.

martedì 16 dicembre 2008


Su questo sito


c'è una serie di test di programmazione passati i quali si diventa cintura nera di Java!!

Il bello è che i test sono creati dalla community in vero spirito 2.0.

Io per ora sono solo cintura gialla...


venerdì 12 dicembre 2008

Component Diagram UML: le interfacce

Trovo che il Component Diagram sia il più utile dei diagrammi UML, poichè è chiaro,  facile da capire e sopratutto utile, dato che descrive come le varie parti di un'applicazione (component) interagiscono.

Può essere usato per descrivere l'interazione fra vari component, oppure fra le parti interne di un singolo component.

Vediamo un esempio di questo secondo tipo di uso.

Poniamo di avere un component (se si vuole concretizzare, una dll) che serve per la modellizzazione di un negozio.

Riporto qui un esempio preso dal sito di Rational - IBM (articolo completo) .



La figura rappresenta il component diagram della struttura interna e delle interfaccie verso l'esterno del componente "Store".

Nella simbologia UML 2.0, un' interfaccia esposta si disegna con un "lollipop" (un  "lecca-lecca")  cioè una linea dritta con un pallino in cima.
Invece, un'interfaccia richiesta si simboleggia con un "socket" cioè una linea con un mezzio cerchio in cima. 
Ovviamente lollipop e socket sono fatti per "agganciarsi" uno nell'altro.



(qui sopra Notazione UML 2.0)
(qui sopra Notazione UML 1)

Se pensiamo alla classe "Customer", possiamo dire che sicuramente avrà un'interfaccia "Person" attraverso la quale può esporre le sue proprietà interne. Un'interfaccia di questo tipo conterrà metodi come getName() oppure getPersonData()...
Possiamo dire quindi che "Customer" espone un'interfaccia verso l'esterno. Dunque dal rettangolo della classe Customer esce un lollipop chiamato "Person".

Discorso parallelo per "Product" che espone un'interfaccia (un lollipop) chiamata"OrderableItem". Questa interfaccia avrà metodi come getPrice() o getItemDescription()...

Guardiamo ora la classe "Order". Un ordine, per essere definito, deve avere il riferimento del prodotto acquistato e del cliente acquirente. Dunque, nella classe "Order"esisteranno dei metodi che richiedono come parametri delle istanze di  "Person" e "OrderableItem". Da "Order" escono quindi due socket, a simboleggiare le due interfaccia richieste.

    interface Person { }

    interface OrderableItem { }

    class Order

    { // Order richiede (ad esempio attraverso il costruttore) l'uso di due istanze delle interfacce Person e OrderableItem

       public Order(Person p, OrderableItem o) {       

        }

    }

    class Customer : Person {    }

    class Product : OrderableItem {    }


Collegando i socket con i lollipop si ottiene il collegamento fra le tre classi che costituiscono il componente. 




Ma il componente può avere interfaccie verso l'esterno.

Ad esempio l'interfaccia Account serve per collegare il Customer con i dati del conto di pagamento, gestiti da un altro componente. L'interfaccia account avrà metodi come: getAccountData(), ...
Un customer deve conoscere questi dati, dunque ha un'interfaccia rischiesta (socket) verso l'esterno del componente.

Il componente può esser parte di più programmi (un programma web di invio ordini da un sito internet, un programma di contabilità, un programma di data-mining....). In ogni caso dovrà esporre verso l'esterno un'interfaccia che riassuma i dati dell'ordine, chiamata "OrderEntry". Essa avrà metodi come: getTotalPrice(), getDateOfOrder(), ...

Dunque la classe "Order" espone vesro l'esterno del componente un lollipop chiamato "OrderEntry".  

martedì 9 dicembre 2008

Aggiungere un TraceListener ad una classe da codice

Vogliamo aggiungere un listener del Trace di una applicazione. Sebbene questa operazione si possa eseguire direttamente dal file di configurazione dell'applicazione (App.config), preferisco gestirla da codice, in modo da avere un maggiore controllo ed una maggiore comprensione su quello che accade.

Il Trace è il modo migliore per gestire i log in .NET . Per info cercare informazioni su msdn alla voce TraceSource.

Nel file App.config (se non esiste, va creato nella directory base del progetto) bisogna definire un "source" avente come nome, il nome che daremo al nostro Trace. In questo caso "ApplicationTraceName".
Poi bisogna associare al source uno "switch", che serve ad indicare il livello minimo di ascolto rispetto a questa source. In questo caso "Verbose", cioè ascoltiamo tutto ciò che la source notifica.
E' ovviamente possibile gestire più di una source.

xml version="1.0" encoding="utf-8" ?>

<configuration>

  <system.diagnostics>

    <sources>   

      <source name="ApplicationTraceName" switchName="VerboseSwitch">      

      source>     

    sources>

    <sharedListeners>     

    sharedListeners>

    <switches>

      <add name="VerboseSwitch" value="Verbose" />

    switches>

  system.diagnostics>

configuration>




Definiamo ora la sorgente del flusso di Trace. Deve essere una classe che fa da wrapper alla classe TraceSource. Deve avere un metodo per registrare un listener ed un metodo per lanciare un nuovo evento di Trace. Il nome affibiato al TraceSource deve essere quello dichiarato nell' App.config. 

        public static class ApplicationTrace

        {

            static private System.Diagnostics.TraceSource Source = new System.Diagnostics.TraceSource("ApplicationTraceName");

             ///

            ///     Writes a trace event message to the trace listeners in the

            ///     System.Diagnostics.TraceSource.Listeners

            ///     collection using the specified event type, event identifier, and message.

            ///

            ///

One of the System.Diagnostics.TraceEventType values

            ///  that specifies the event type of the trace data.

            ///

A numeric identifier for the event.

            ///

The trace message to write.

            [System.Diagnostics.Conditional("TRACE")]

            static public void TraceEvent(System.Diagnostics.TraceEventType eventType, int id, string message)

            {

                Source.TraceEvent(eventType, id, message); Source.Flush();

             }

            ///

            /// Add a listener to the trace

            ///

            ///

            static public void AddListener(System.Diagnostics.TraceListener listener)

            {

                Source.Listeners.Add(listener);

            }

      }


Definita la sorgente del flusso di Trace, da codice creiamo il listener che ascolta il flusso di dati e lo scrive da qualche parte. Per fare questo usiamo gli oggetti del namespace System.Diagnostic di .NET

System.Diagnostics.ConsoleTraceListener traceListener = new System.Diagnostics.ConsoleTraceListener();

//impostiamo il verbose level

traceListener.Filter = new System.Diagnostics.EventTypeFilter(System.Diagnostics.SourceLevels.Verbose);

Nota: qui avremmo potuto definire un filtro più restrittivo, ad esempio a livello Information. 


Infine, colleghiamo il listener creato alla sorgente di Trace usando il metodo della sorgente per l'aggiunta di listener:

ApplicationTrace.AddListener(traceListener);

Abbiamo terminato. Ora, ogni volta che verrà invocato il metodo TraceEvent, l'evento sarà tracciato a Console.

ApplicationTrace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "my message");



Il codice completo è questo:

public class MyClass{

        public MyClass()

        {

            System.Diagnostics.ConsoleTraceListener traceListener = new System.Diagnostics.ConsoleTraceListener();

            //information  level

            traceListener.Filter = new System.Diagnostics.EventTypeFilter(System.Diagnostics.SourceLevels.Verbose);        

            ApplicationTrace.AddListener(traceListener);

        }

         public void myMethod() {

            ApplicationTrace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "my message");

        }

    }

  static void Main(string[] args)

        {

            MyClass myClass = new MyClass();

            myClass.myMethod();

            Console.ReadLine();

}


Infine, si può pensare di voler redirigere il flusso del Trace verso un altro output che non sia la console, ad esempio scrivendo su un file di log. Per fare questo basta ridefinire in questo modo il listener:

          System.Diagnostics.TextWriterTraceListener traceListener = new System.Diagnostics.TextWriterTraceListener("myfilename.txt");


Ovviamente si può creare un insieme di listener a piacere, in modo da scrivere il log su più destinazioni diverse (files, console, maschere a video, pagine web...) anche a livelli di log differenti.

mercoledì 3 dicembre 2008

Nested class/ Inner class differenza fra C# e Java

Nested o Inner class sono classi definite all'interno del corpo di un'altra classe (Outer class).

Per una introduzione sulle Nested class in C# vedere il post precedente.

Diciamo subito una cosa:
In C# esiste solo il concetto di Nested class, mentre in Java esistono sia le Nested class sia le Inner class.

In Java:
Vediamo che differenza c'è fra Inner class e Nested class in Java: le Nested class sono delle Inner class statiche.

Inner class:

public class OuterClass{

public class InnerClass{

}

}



Nested class:


public class OuterClass{

public static class NestedClass{

}

}



La differenza è la stessa che c'è fra un metodo statico o non statico...
L'istanza di una inner class appartiene ad un'istanza della outer class, mentre l'istanza di una nested class non ha un'istanza associata.

L'inner class si invoca così:

OuterClass outer = new OuterClass();     OuterClass.InnerClass inner = outer.new OuterClass.InnerClass();

La nested class si invoca così:

    OuterClass.NestedClass nested = new OuterClass.NestedClass();

Si noti che la Inner class ha accesso a tutti i metodi/variabili della Outer class, anche se definiti privati. Questo perchè p intimamente legata all'instanza della Outer class da cui è stata creata.
La Nested class invece, può accedere solo ai metodi/variabili static della Outer class.



In C#

La Nested class di C# ha lo stesso significato semantico della Nested class di java: è una classe statica definita dentro un'altra classe. La differenza è, se vogliamo, solo sintattica: infatti in C# viene omesso il modificatore "static" ed una Nested class si definisce così:

public class OuterClass
    {
        public class NestedClass {

        }
    }

Anche in questo caso la Nested class può accedere solo ai metodi static della Outer class.

Ricordo ancora che il concetto di Inner class in C# non è previsto.

Nested class in C#

Una Nested class è una classe definita all'interno del codice di un'altra classe (Outer class).


Ad esempio:

  class OuterClass
    {
  
        class NestedClass {

         
        }
    }

Come usare la Nested class dall'esterno

La classe interna può essere referenziata dall'esterno in questo modo:

OuterClass. NestedClass  myObj = new OuterClass. NestedClass();//non compila perchè NestedClass è private

ma solo se è definita public!! ( Il valore di default dell'access modifier di una nested class è private). Dunque la precedente riga di codice non compila a meno di definire così la nested class:


  class OuterClass
    {
  
        public class NestedClass {
         
        }
    }


Ora si può usare con successo:
OuterClass. NestedClass  myObj = new OuterClass. NestedClass();//ok!!


Nota, si potrebbe essere tentati di usare un'istanza della outer class per definire la nested class, come in questa chiamata, ma non è corretto dato che  è come se la nested class fosse un membro statico della outer:
OuterClass. NestedClass  myObj =(new OuterClass()).NestedClass(); //non compila, non si può chiamare in questo modo.


Come usare usare la Outer class dall'interno della Nested class

Il principale motivo per cui si inserisce una classe dentro un'altra è quello di utilizzare membri e variabili della Outer class dall'interno della Nested class.
Attenzione:  dalla Nested class si possono utilizzare solo i membri static della Outer class!!! 

La sintassi è questa: OuterClass.membro 

    class OuterClass
    {
        private string myOuterString;

        private static string myOuterStaticString;


        public class NestedClass {

            public void Method() {

                OuterClass.myOuterStaticString="ciao";//ok!

                OuterClass.myOuterString = "ciao";//No, questa riga non compila, dato che myOuterString  non è static
            }
        }
    }

Nota che la Nested class accede ai membri della Outer class anche se questi sono privati!!! Questa è la grande potenza delle nested class e l'unico motivo per cui andrebbero usate.

lunedì 1 dicembre 2008

Anni bisestili in C#

 Capire se un anno è bisestile (leap year) o no in C# è banale grazie al metodo isLeapYear della classe DateTime


public static bool IsLeapYear(int year);


Ad esempio 

DateTime.IsLeapYear(1993);

torna, ovviamente, false.

venerdì 28 novembre 2008

Il pattern observer e il MVC... come separare un Model dalle Views

Il pattern Model-View-Controller (MVC)  indica che  una applicazione deve essere separata in tre componenti architetturali. Il Model è il core del programma: esegue gli algoritmi, gestisce i dati.  La View è l'interfaccia del programma verso l'utente. Il controller tiene insieme Model e View facendoli comunicare (in particolare, permette alla View di accedere ai metodi del Model).
Ulteriori dettagli su wikipedia.

I concetti fondamentali sono questi:
  • La View (o le views) conosce il Model, e ne può invocare i metodi, ma solo tramite il controller
  • Il Model non conosce le view, non sa se c'è una View ad essa associato, nè tantomeno come è fatta e quali metodi ha.
I vantaggi di questo pattern sono ovvi:
  • Ad un dato Model si possono associare quante View si desiderano, senza dover toccare una virgola del Model stesso
  • Il Model ha una API chiara, ovvero non sporcata da eventuali metodi/parti di codice legati all'interazione con l'utente
  • Fatta una view, è relativamente facile crearne un'altra come copia della prima, salvo poi modificare i dettagli voluti (ad es cambiare la lingua dei messaggi...) 

Usando il pattern observer è facile creare una architettura di questo genere.

Il pattern observer (wikipedia qui) serve a disaccoppiare due classi: un publisher di eventi ed un subscriber di eventi. Proprio come per l'abbonamento ad un giornale, gli eventi (i nuovi numeri del giornale) vengono notificati (spediti) dal publisher (l'editore) ai subscribers (gli abbonati).
E' dunque necessaria una prima fase di registrazione (creazione dell'abbonamento) in cui il subscriber attivamente si registra presso il publisher. Una volta "abbonato" il subscriber diventa passivo e si limita a ricevere le chiamate del publisher che gli notifica nuovi eventi.
Grazie all'observer le due classi sono disaccoppiate (NB: in un verso solo!!  Il Publisher non conosce i subscriber ma i subscriber sanno benissimo che esiste un certo Publisher a cui sono registrati!).
 (Nota: a volte il publisher è chiamato "subject", i subscriber son chiamati "observer"... ma è solo una questione di nomi)

Tipicamente il publisher ha una struttura di questo genere:
  • metodo per registrare nuovi subscriber
  • metodo per de-registrare un vecchio subscriber
  • set di metodi per notificare eventi ai subscibers
I subscriber invece hanno questi metodi:
  • set di metodi per reagire alla notifica degli eventi


Nel nostro caso (l 'MVC), le View sono i subscriber, mentre il Model è il publisher.
Il Model, quando ne ha voglia o necessiatà, invia un messaggio a tutte le View ad esso registrate con la notifica di un evento. Se non c'è nessuna View registrata, semplicemente il messaggio non arriva a nessuno, ma di questo il Model non si cura affatto. Il Model quindi comunica con le View senza conoscerle e addirittura senza sapere se esistono o meno.

Dunque d'ora in poi
subscriber=View
publisher=Model


In questo esempio C# costruisco una applicazione in cui ad un Model sono associate due Views. 
Il Model, banalissimo, non fa altro che cambiare il valore di una sua variabile di stato.
Le view saranno
  •  una classe che scrive lo stato del Model su console 
  • una windows form che scrive lo stato del Model su un'interfaccia grafica
Vedremo che di fronte ad una modifica dello stato del Model, tutte e due le View ne saranno contemporaneamente informate, ed entrambe mostreranno a video l'evento.

Sebbene sia utile avere due view per visualizzare i dati del modello, è sconsigliabile avere due fonti di "input" per il programma. Dunque decidiamo che la view console serve solo a mostrare gli output, mentre gli input al programma vengono dati tramite la view windows form.

Il modello è questo:

  public class MyModel:ConcreteModel
    {
        public int statusVariable;

        public void doWork() {

            statusVariable = 0;
            NotifyToObserver();

            //do something
            
            //send a message to the views
            string msg = "hello!";
            NotifyMessageToObserver(msg);

            //send to the views the information that a status variable has changed
            statusVariable = 1;
            NotifyToObserver();

        }
    }


Ha una variabile di stato che rappresenta un possibile stato interno del modello. Il metodo doWork() non fa altro che modificare questo stato, portandolo prima a 0 e poi  a 1.

I metodi "Notify" servono a inviare alle View registrate le notifiche di modifica dello stato "NotifyToObserver()" o un messaggio particolare "NotifyMessageToObserver(msg)". Questi metodi si trovano nel ConcreteModel, il prototipo generico di un modello:

 public class ConcreteModel:IModel
    {
        List registeredViews = new List();

        #region IModel Members

        public void RegisterObserver(IView paramView)
        {
            registeredViews.Add(paramView);
        }

        public void RemoveObserver(IView paramView)
        {
            registeredViews.Remove(paramView);
        }

        public void NotifyToObserver()
        {
            foreach (IView view in registeredViews) {
                view.Update(this);
            }
        }

        public void NotifyMessageToObserver(string message)
        {
            foreach (IView view in registeredViews)
            {
                view.NewMessage(message);
            }
        }

        #endregion
    }

Il ConcreteModel contiene l'elenco delle viste registrate, ed i metodi per notificare ad esse le modifiche del Model. Inoltre contiene i metodi per registrare/de-registrare le viste.

Il ConcreteModel implementa l'interfaccia IModel.

 public interface IModel
    {
       

        #region Observer pattern
        ///
        /// Attach an observer view to the model. Max 1 view! If a view is currently registered, it will be unregistered and overwritten by the new one
        ///
        ///
        void RegisterObserver(IView paramView);

        ///
        /// Unregister the view currently attached to the model
        ///
        ///
        void RemoveObserver(IView paramView);

        ///
        /// Notify any change in the model to the attached view
        ///
        void NotifyToObserver();

        ///
        /// Notify a string message to the attached view
        ///
        ///
        void NotifyMessageToObserver(string message);

               
        #endregion
    }



Questa è invece l'interfaccia che ogni vista deve implementare. Contiene i metodi per "reagire" alle notifiche inviate dal Model.

public interface IView
    {
      
        #region  observer pattern methods
        
        ///
        /// Update the view given the current instance of the model
        ///
        ///
        void Update(IModel paramModel);


        ///
        /// Update the view given a message string coming from the model
        ///
        ///
        void NewMessage(string message);
      

        #endregion
    }

Il metodo "Update(IModel paramModel)" gestisce le notifiche inviate con "NotifyToObserver()".
Il metodo "NewMessage(string message)" gestisce le notifiche inviate con "NotifyMessageToObserver(string message)".
Ovviamente è possibile definire altre coppie di notifiche/reazioni a seconda delle necessità.



Ecco una possibile implementazione di View. Questa View manda i messaggi alla console:

 class MyViewConsole: IView
    {

        #region IView Members

        void IView.Update(IModel paramModel)
        {
            ConcreteModel model = paramModel as ConcreteModel;
            if (model != null)
            {
                //do something
                MyModel myModel = model as MyModel;
                if (myModel != null) {
                    Console.WriteLine("Internal status:" + myModel.statusVariable);
                }
            }
        }

        void IView.NewMessage(string message)
        {
            //do something
            Console.WriteLine("Message from model:"+message);
        }

        #endregion
    }

Il metodo "Update(IModel paramModel)" permette di stampare a Console lo stato interno del modello.
Il metodo "NewMessage(string message)" permette di stampare a console il messaggio inviato dal modello.



Come detto in precedenza una delle View deve essere quella "di input", cioè quella da cui si inviano comandi al Model tramite il Controller.  Questa View dovrà avere la reference al Controller. Definiamo quindi l'interfaccia di "View di Input":

public interface IInputView
    {
         void setController(Controller c);
        
    }


Dunque creaiamo una View concreta che sia contemporaneamente di output e input. In questo caso estende anche Windows.Form.


public partial class MyViewForm : Form,IView,IInputView
    {
        public MyViewForm()
        {
            InitializeComponent();
      
        }



        #region IView Members

        void IView.Update(IModel paramModel)
        {
            ConcreteModel model = paramModel as ConcreteModel;
            if (model != null)
            {
                //do something
                MyModel myModel = model as MyModel;
                if (myModel != null)
                {
                    textBox1.Text= myModel.statusVariable+"";
                }
            }
        }

        void IView.NewMessage(string message)
        {
           //do something
           MessageBox.Show("Message from model:" + message);
        }

        #endregion

        Controller controller;

        #region IInputView Members

        public void setController(Controller c)
        {
           controller=c;
        }

        #endregion

        private void button1_Click(object sender, EventArgs e)
        {
            controller.doWorkModel();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            this.Close();
        }

      
    }


Il metodo "Update(IModel paramModel)" permette di stampare in una casella di testo lo stato interno del modello.
Il metodo "NewMessage(string message)" permette di mostrare una messagebox con il messaggio inviato dal modello.
Il metodo banale "setController(Controller c)" non fa altro che creare una referenza al controller e dunque, di riflesso, al modello.

Ed infine manca solo il controller anch'esso banale: gira ogni comando ricevuto verso il modello.
Volendo nel controller si può (e si deve) mettere tutta la logica che riguarda la modifica delle View in base allo stato delle View stesse e del controller (ad es abilitazione/disabilitazione tasti...)

 public class Controller
    {

        public Controller( MyModel m) {
            model = m;
        }

        MyModel model;

        public void doWorkModel() {
            model.doWork();
        }
    }


Il programma principale instanzia il Model, le due View e il Controller. Poi registra le View sul Model ed attacca il Controller alla View di Input. Infine lancia la View di Input

 class Program
    {
        static void Main(string[] args)
        {
            //create the model
            MyModel myModel = new MyModel();
            
            //create the 2 views
            MyViewConsole myViewConsole = new MyViewConsole();
            MyViewForm myViewForm = new MyViewForm();
          
            //register the 2 views(subscribers) to the model(publisher)
            myModel.RegisterObserver(myViewConsole);
            myModel.RegisterObserver(myViewForm);

            //create the controller to the model
            Controller c = new Controller(myModel);

            //attach the controller to the input view
            myViewForm.setController(c);

            //run the input view
            Application.Run(myViewForm);

           
        }
    }


Il risultato è questo:

Start del programma:


Click sul bottone "model di work"


Click sull'ok del messagebox

Si può notare come l'update delle due viste, quella console e quella windows form, sia parallelo e contemporaneo.

Aggiungere una terza vista (ad esempio una console in un'altra lingua, o addirittura una pagina web in ASP)  sarebbe banale.


Lettori fissi