LINQ per Java

Una delle tecnologie di uso più pratico che verranno introdotte nel prossimo framework .Net (che io uso da più di un anno, ormai, anche col framework 2.0) è sicuramente LINQ. Per chi non sapesse di cosa si tratta dico solo che consente di fare su TUTTE le collections, vettori, array, file XML e database ciò che l’SQL fa solo sui database. Si tratta di una sintassi che è possibile usare nei propri progetti .Net che consente di effettuare query su oggetti (o XML o Database) sintatticamente verificate a compile time. Nel momento in cui le query sono scritte su collection di oggetti il completamento automatico ci viene incontro e possiamo sfruttare anche tutti i metodi degli oggetti stessi.
Supponiamo di avere una collection “persone” di oggetti “Persona”; possiamo effettuare la seguente query

var q = from p in persone
where p.Nome.Length > 4
select new{p.Nome.UpperCase(), p.cognome};

In questo caso q, che è una sorta di Variant, conterrà una collection di oggetti di un nuovo tipo (noto solo a run time) che contengono due attributi: Nome e Cognome. E’ possibile ovviamente fare join, groupby, orderby, distinct e tutti gli operatori tipici dell’SQL. Non mi dilungo molto sui meriti del LINQ e di tuttte le novità del framework 3.0 che reputo davvero ammirevoli per essere un parto in casa Microsoft, per dirvi che tutto ciò ora lo potremo anche godere in Java.
Il progetto Quaere infatti consentirà di avere più o meno le stesse funzionalità utilizzando una sintassi meno elegante ma altrettanto efficiente. Le scelte progettuali dell’autore gli complicheranno un pò lo sviluppo ma sembra molto motivato a raggiungere i suoi obiettivi. La sua scelta infatti è quella di scrivere queste API in puro Java, non considerando il fatto che la mancanza delle closures, a detta di alcuni, ne limiterà le possibilità (il consiglio che davano gli autori di db4o, ad esempio, era di utilizzare il linguaggio Scala). Ecco un esempio di query che seleziona i numeri di A minori di B (notare l’operatore lt, lessthan):

Integer[] numbersA = {0, 2, 4, 5, 6, 8, 9};
Integer[] numbersB = {1, 3, 5, 7, 8};
Iterable pairs =
from("a").in(numbersA).
from("b").in(numbersB).
where(lt("a", "b")).
select(
create(
property("a"),
property("b")
)
);

Non male per essere un progetto “limitato” fatto da una sola persona e per giunta open source.

Zac

The funniest joke in the world

Leggevo oggi la notizia apparsa su Punto Informatico della denuncia, mossa dai tipi di PirateBay, contro le major che cercano di sabotarli in tutti i modi pur di impedire il protrarsi del loro servizio di tracking di link bittorrent. Che siate contro o a favore della pirateria sappiate solo che i suddetti pirati vivono in Svezia e lì hanno i loro server. Secondo le leggi svedesi pare che non facciano nulla di illegale. Tanto sono legittimati nel fare quello che fanno che in Svezia esiste anche un partito a difesa dei Pirati, che si occupa in realtà di fare in modo che le leggi che governano tali questioni (diffusione di media a vario titolo) siano coerenti con la legislazione Svedese (che basa le sue fondamenta sulla ragione più che sugli interessi di politici e/o grosse industrie e lobby varie). Incuriosito sono andato sul loro sito e, tra i piccoli link in basso, ce n’è uno chiamato “Legal Threats” in cui sono raccolte tutte le email di diffida ricevute dai novelli Barbablu e le risposte che hanno dato hai legali di turno o agli sceriffi informatici che osavano sfidarli in audaci mezzogiorno di fuoco sul web.

Inutile dire che c’è solo da creparsi dalle risate dato che i legali delle major si arrampicano invano sugli specchi pur di portare a casa il loro misero tozzo di pane e ricevono solo beffe ed insulti; ecco un esempio:


(Lettera della Major)
> As you may be aware, Internet Service Providers can be held liable if they do not respond to claims of
> infringement pursuant to the requirements of the Digital Millennium Copyright Act (DMCA). In
> accordance with the DMCA, we request your assistance in the removal of infringements of the
> (IL FILM IN QUESTIONE) motion picture from this web site and any other sites for which you act as
> an Internet Service Provider. We further declare under penalty of perjury that we are authorized to act
> on behalf of (MAJOR IN QUESTIONE) and that the information in this letter is accurate.
>Please contact me immediately to discuss this matter further.

(RISPOSTA DEI PIRATI)

As you may or may not be aware, Sweden is not a state in the United States of America.
Sweden is a country in northern Europe. Unless you figured it out by now, US law does not apply here.
For your information, no Swedish law is being violated.

Please be assured that any further contact with us, regardless of medium, will result in
a) a suit being filed for harassment
b) a formal complaint lodged with the bar of your legal counsel, for sending frivolous legal threats.

(INSULTI VARI)

Vi invito a leggere anche il dialogo con un presunto WEB-Sheriff…davvero da morire dal ridere.

iVi iPresentiamo iIl iNuovo iPod…

Per chi è in crisi d’astinenza da hype…annuncio che alle 19.00 di oggi 5/9/2007 verrà presentato la nuova serie di iPod; riusciranno a bissare il successo dei loro predecessori? Le indiscrezioni segnalano la possibilità che l’iPod Nano avrà lo schermo per i video più largo e sarà più corto e che l’iPod grande avrà un touch screen come l’iPhone. Vedremo ma non ne posso più di tutti questi iNomi…

Sembra come la moda delle parole mini, micro, tele in qui tutto era preceduto dal prefisso di turno. Essendo un utente mac sono circondato da iWork, iLife, iDvd, iMovie, iPhoto, iChat, iSync, iTunes, iPod, iPhone….arghh…quando è troppo è troppo

ISO’ Pazz

La notizia è : ISO ha votato per respingere la proposta di MS di adottare il formato OOXML come standard. Il rifiuto però è solo temporaneo, all’inizio del 2008 saranno esaminate le eventuali modifiche e le votazioni saranno ripetute.

OOXML (acronimo di “Office Open XML”) è il formato utilizzato dal nuovo Office per salvare i documenti e nelle intenzioni di MS dovrebbe ridurre il livello di ansia di chi ha paura di mettere i propri dati nelle mani di uno standard proprietario e chiuso. Ovviamente i principali ansiosi sono le pubbliche amministrazioni che non vogliono dipendere da un solo vendor finché morte non li separi.

L’avvento della filosofia “open”, ma soprattutto del formato ODF, già oggi standard ISO, ha smosso MS che ha voluto mostrare il proprio desiderio di assecondare le esigenze dei clienti con questa iniziativa in stile “famolo open!” OOXML però non è parso proprio del tutto aperto a troppi esaminatori, anche se la riservatezza dei lavori di ISO non ha lasciato trasparire troppo l’oggetto delle critiche. Comunque qui è riportato un elenco di difetti che non lascia troppi dubbi.

L’intera vicenda della votazione, però, è stata contrassegnata da accuse a MS di aver indotto i propri partner in vari paesi del mondo a unirsi ai membri dell’ISO per votare a favore. Forse non c’è una legge che lo vieti o uno standard che lo sconsigli, ma di sicuro questo sarebbe un comportamento sleale. Non mi stupirei se anche gli avversari fossero “scesi in campo” allo stesso modo. La vera brutta figura, comunque, l’avrebbe fatta ISO se avesse dimostrato che basta avere tanto denaro e tutto si standardizza. Invece, nonostante tutto, ISO ha assunto questa posizione critica verso OOXML che non potrà far altro che migliorarlo, ammesso che questo sia possibile.

Frameworks MVC, Reflection e Pattern Business Delegate per le nostre applicazioni Java.

Spesso quando si realizza un’applicazione, in particolare applicazioni Web, se non si ricorre a tecniche di progettazione si tende a riscrivere sempre le stesse operazioni e a produrre codice ridondante, come se si stesse lavorando presso una catena di montaggio del codice.

Per fortuna Java ragiona ad oggetti e ci permette, con i giusti accorgimenti, di risparmiare “quintali di codice”.

Quello che vedremo è come ottenere, sfruttando pochi pattern semplificati e gli strumenti di base dei Framework MVC, un’applicazione scalabile, affidabile e manutenibile lato server.

Scenario

Spesso quando si realizza un’applicazione, in particolare applicazioni Web, se non si ricorre a tecniche di progettazione si tende a riscrivere sempre le stesse operazioni e a produrre codice ridondante, come se si stesse lavorando presso una catena di montaggio del codice.

Per fortuna Java ragiona ad oggetti e ci permette, con i giusti accorgimenti, di risparmiare “quintali di codice”.

Quello che vedremo è come ottenere, sfruttando pochi pattern semplificati e gli strumenti di base dei Framework MVC, un’applicazione scalabile, affidabile e manutenibile lato server.

Presupposti

Prima si comincia a programmare prima si vedono risultati, prima si finisce! Quale concetto più sbagliato di questo… E’ vero che si comincia prima, ma si procede male e quando si arriva alla fase conclusiva il codice è talmente intrecciato che nemmeno l’uomo ragno può aiutarvi…

Lo scopo invece deve essere quello di realizzare un sistema come fosse costituito da costruzioni Lego, dove ogni Classe/Funzionalità non è altro che un mattoncino il quale è inscindibile e da solo ha una sua funzione specifica.

Inoltre la forza di queste piccole costruzioni è che pur se piccoli quasi tutti i componenti, si legano tra di loro (ognuno con le sue caratteristiche e funzioni) per realizzare non 1 sola, ma N costruzioni diverse senza dover modificare la forma di ogni singolo elemento.

In particolar modo per questo articolo vedremo come strutturare il lato Model in livelli (layers) in modo tale da rendere la Business Logic (il core dell’applicazione) facilmente “pluggabile” indifferentemente dal framework (d’ora in poi FW) che si vorrà adottare, aiutandoci dalle funzionalità di base del controller ovvero fungere da spina dorsale del sistema collegando tra di loro la parte di presentation con la logica dell’applicazione stessa.

Hands on Head

Lo scenario con cui solitamente si lavora sui progetti utilizzanti FW MVC nella migliore delle ipotesi (non è insolito trovare programmatori che accantonano tutta la logica nelle action Struts o bean di JSF) è del tipo rappresentato in figura 1.

Analizziamo lo scenario. Per ogni coppia JSP-Action vi è associata nel controller una definizione che lega le due. A loro volta le action fanno riferimento a una o più unità Business. Ovvero abbiamo per N coppie JSP-Action N definizioni nel controller e minimo N legami Action-Business…

Questa magari per piccole applicazioni è una soluzione plausibile ma immaginate uno scenario più ampio dove ci sono centinaia di coppie e legami da definire, il codice inizierebbe a diventare poco leggibile per chiunque, e inoltre cosa succederebbe se per qualche esigenza di progetto il FW dovesse cambiare per effettuare un’integrazione o un upgrade su altre piattaforme… i costi di maintenance e gli errori sarebbero notevoli.

Lo scopo quindi, come illustrato in figura 2, è quello di sintetizzare al minimo l’utilizzo degli strumenti del FW utilizzando 1 sola definizione e 1 sola action aggiungendo un semplice ma potente livello alla nostra logica, il Business Delegator, ovvero uno smistatore “intelligente” di classi Business.

Il passo importante da considerare sta nel fatto che con l’aggiunta del nostro Delegator, lo strato di Business Logic è completamente separato e indipendente da qualsiasi FW.

Il controller di Struts permette in base alle risposte ricevute dalla Action (Interfaccia di collegamento tra il la Business Logic e il Controller) di re-indirizzare il risultato su una pagina o su un’altra definizione di Action.

Non è necessario realizzare una definizione nel controller (d’ora in poi ActionConfig) e una Action per ogni funzionalità/pagina che si vuole realizzare.

Infatti è possibile realizzare un’unica Classe di Action per tutte le funzionalità di recupero dati di cui la nostra applicazione necessita.

Come? semplice, sfruttando i Design Pattern.

In ogni ActionConfig è possibile associare una sola Action, ma per fortuna è anche possibile associare in base alla risposta, N forward a pagine (o tiles, file velocity) diverse.

Avendo un problema su 2 risolto dal FW, non ci resta che fornire alla nostra Action una classe Business con cui interloquire, un po’ più particolare delle altre, la quale avrà il compito di:

  • smistare in modo intelligente le richieste del client presso le altri classi Business
  • ritornare le risposte verso la Action.

Se qualcuno a questo punto sta immaginando, come soluzione tipo, un’infinità di if-else o switch-case è lontano dalla soluzione, perchè in questo caso la action pur essendo più veloce, sia come soluzione che come tempo di esecuzione, non avrebbe la dinamicità che un progettista deve assicurare. Una volta realizzato la classe di Action, anche se le classi di business diventeranno 10, 100 1000, 10000 il codice non deve aver bisogno di maintenance.

Ovviamente, si ha la necessità di fare un compromesso. In questo caso se vogliamo che il codice lato server sia intelligente, abbiamo bisogno di un piccolo aiuto da parte del client.il quale dovrà fornirci informazioni di base attraverso i link/form, nel nostro caso basteranno il nome del Business, il nome del metodo da chiamare, e i parametri di cui il metodo necessita.

Ipotizziamo di avere N classi di business Class1usiness.java, Class2Business.java, …, ClassNBusiness. Se non ragionassimo con le interfaccie, ogni volta in cui sia necessario istanziare una classe dovremmo per forza inserire nella nostra Action il nome della classe specifica così come mostra il codice seguente

String businessName = (String)request.getParameter(“businessName”);

ArrayList results = new ArrayList();

if(businessName.equals(“Class1Business”)){

results = new Class1Business().getResults();

} else if(businessName.equals(“Class2Business”)){

results = new Class2Business().getResults();

}

//…

else if(businessName.equals(“ClassNBusiness”)){

results = new ClassNBusiness().prendiRisultatiArrayList();

}

Inoltre, così come illustrato, non sempre i nomi dei metodi di recupero sono li stessi, soprattutto quando si è in molti a sviluppare codice sullo stesso progetto.

Già questi 3 motivi (classi/controller da aggiornare continuamente, multi if-else, nomi metodi diversi) dovrebbero scoraggiarci ad operare in questo modo. Onde evitare pensieri “troppo liberi” conviene mettere qualche paletto nel nostro (e in quello degli altri) modo di operare.

Creiamo una Factory.

Abbiamo bisogno di dare una linea guida alle nostre classi Business, quindi scriveremo un’interfaccia.

public interface BusinessInterface {

public ArrayList getResults(String methodName, Object params) throws Exception;

}

Leggiamo assieme questa semplice interfaccia. Essa necessita in input di 2 parametri, ovvero il nome del metodo che la classe business deve eseguire, un Object rappresentante il/i parametro/i da interpretare (ricordiamo che l’oggetto Object può assumere significato multiplo).

Questa interfaccia cattura e rimanda alla classe chiamante l’eventuale eccezione generata/avvenuta. Questo metodo inoltre restituisce un ArrayList di valori e il nome del metodo è getResults. In questo modo abbiamo assicurato che tutte le classe Business abbiano un metodo per la restituzione dei risultati con lo stesso nome, e non solo…

Implementando in Class1Business l’interfaccia appena creata avremo:

public class Class1Business implements BusinessInterface{

private ArrayList results = new ArrayList();

public ArrayList execute(String methodName, Object params) throws Exception {

//il nostro codice

return results;

}

}

Adesso che le nostre classi Business hanno uno standard di implementazione, abbiamo bisogno di uno strato intermedio che ci faccia da interprete tra le richieste della classe di Action e le singole classi di Business.

La Business Delegate.

Adesso abbiamo la possibilità di fare questo:

BusinessInteface businessInterface = null;

businessInterface = new Class1Business();

businessInterface = new Class2Business();

businessInterface = new ClassNBusiness();

Ovvero possiamo istanziare nella colonna di destra tutte le classi che implementano l’interfaccia.

Il nostro scopo ora è quello di implementare in modo dinamico le classi nella colonna di destra. In questo modo la nostra action potrà caricare N classi senza aver il bisogno di nessuna sorta di switch. E’ qui che entra in gioco la Reflection di Java.

BusinessDelegator.java

Class c = Class.forName(ProjectConstant.BUSINESS_PREFIX + businessName);

BusinessInterface bus = (BusinessInterface)c.cast(c.newInstance());

dove Class.forName(String className) effettua l’istanza della classe dato il suo nome, e BUSINESS_PREFIX non è altro che una costante di progetto con valore org.solimena.corsojava.business (nel nostro caso), mentre businessName è il nome della classe Business fornita, come dicevamo prima, dal Client,. Giusto per chiarezza prendiamo il caso in cui dal client sia arrivata la richiesta di utilizzare i servizi della classe Class7Business.

Dinamicamente il compilatore interpreterà il comando come

Class c = Class.forName(“org.solimena.corsojava.business.Class7Business”);

BusinessInterface bus = (BusinessInterface)c.cast(c.newInstance());

La prima riga di codice effettua un’istanza della classe org.solimena.corsojava.business.Class7Business, mentre la seconda effettua un casting della classe a BusinessInterface.

Questo è il core del nostro Delegator, poichè tutti i BusinessObject implementano l’interfaccia BusinessInterface e quindi il casting è concesso.

L’utilizzo della Reflection ovviamente è un pò più lento rispetto ad istanziare al volo una classe, ma come iniziate a capire i benefici sono notevoli

// tempo 16ms

Class7Business bus = new Class7Business();

// tempo 31ms

Class c = Class.forName(“org.solimena.corsojava.business.Class7Business”);

BusinessInterface bus = (BusinessInterface)c.cast(c.newInstance());

Vediamo al completo la nostra classe di Business Delegate.

public class BusinessDelegator {

public static ArrayList getResults(String businessName, String methodName, Object params) throws ClassNotFoundException, IllegalAccessException, InstantiationException, Exception{

Class c = Class.forName(ProjectConstant.BUSINESS_PREFIX + businessName);

BusinessInterface bus = (BusinessInterface)c.cast(c.newInstance());

//recuperiamo i risultati del Business

ArrayList results = bus.getResults(methodName, params);

return result;

}

}

Ovviamente è stata inserita la parte finale del nostro delegator, ovvero richiamare il metodo getResults appartenente a tutti i Business implementanti l’interfaccia BusinessInterface, e ritornare al chiamante la risposta recuperata dal Business desiderato.

Adesso abbiamo tutti gli strumenti per scrivere un’unica classe di Action.

package org.solimena.corsojava.presentation.action;

import …;

public class BuilderAction extends Action {

private String result = “”;

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {

//preleviamo dal client le richieste

String businessName = (String)request.getParameter(“businessName”);

String methodName = (String)request.getParameter(“methodName”);

Object params = request.getParameter(“params”);

//effettuiamo un controllo sull’esistenza dei parametri fondamentali fortiti dal client

if(businessName != null && !businessName.equals(“”) && methodName != null && !methodName.equals(“”)){

ArrayList results = new ArrayList();

try {

// Delegator

results = BusinessDelegator.getResults(businessName, methodName, params);

// Fine Delegator

request.setAttribute(“matrix”, results);

result = businessName + “_” + methodName.split(“,”)[0];

} catch (Exception e) {

request.setAttribute(“errore”, “Exception -> ” + e.getMessage());

result = “failure”;

e.printStackTrace();

}

}

return mapping.findForward(result);

}

}

Con l’aggiunta di appena un’interfaccia ed una classe la nostra applicazione ha acquisito la dinamicità che cercavamo diminuendo drasticamente i tempi di sviluppo (se pur aumentando sensibilmente quello di esecuzione).

La differenza rispetto all’approccio iniziale della nostra classe di Action è veramente sostanziale, in quanto adesso viene usata unicamente come punto di incontro tra la logica ed il controller.

Il nome Matrix della request.setAttribute non è a caso, in quanto quello che viene salvato è una matrice di valori (potreste realizzare i metodi Business in modo che tornino degli ArrayList di HashMap ad esempio).

Attraverso politiche di loop all’interno delle pagine JSP sarà possibile recuperare i dati memorizzati all’interno della nostra matrice.

La riga

result = businessName + “_” + methodName.split(“,”)[0];

viene utilizzata allo scopo di far capire al nostro controller a quale forward deve essere associata la risposta.

Vediamo adesso come comparirà il nostro Controller.

<action

path=”/recuperoDati”

type=”org.solimena.corsojava.presentation.action.BuilderAction”

>

<forward name=”Class1Business_findAll” path=”/pages/visualizzaTuttiGliOggetti.jsp” />

<forward name=”Class1Business_findById” path=”/pages/visualizzaDettaglioOggetto.jsp” />

<forward name=”Class4Business_Method1″ path=”/pages/funzionalita1.jsp” />

<forward name=”failure” path=”/pages/failure.jsp” />

</action>

Così come si vede, vi è una sola classe di Action associata, mentre le classi di forward sono N.

Ovviamente un URL tipo avrà la seguente forma

http://[NomeServer]:[porta]/[NomeApplicazione]/[NomeActionConfig].[estensione]?businessName=[NomeBusiness]& methodName=[NomeMetodo]&params=[valori separati da comma]

per esempio:

http://localhost:8080/corsojava/recuperoDati.do?businessName=Class1Business&methodName= findById&params=7

In questo modo abbiamo assicurato che la logica di recupero e smistamento dati sia demandata al server, come anche la distribuzione dell’output alla relativa pagina di presentation, il tutto in base ai dati forniti dal client, (comodo no?), così se dovesse cambiare il FW, anche i link rimarrebbero invariati.

Conclusione

Avevamo detto all’inizio che avremmo realizzato un’applicazione estensibile, affidabile e manutenibile, vediamo assieme i miglioramenti apportati.

  • Estensibile: Seguendo la logica del Low Coupling(Basso Accoppiamento tra le classi) l’applicazione è stata suddivisa in Layer dove ogni strato dialoga unicamente con il livello superiore e con quello sottostante e ogni classe ha le sue funzionalità specifiche che le altre non hanno. Necessitando in futuro di estensioni la nostra applicazione di base resterà pressoché invariata.
  • Affidabile: gestione delle eccezioni e nomenclatura consolidata, inoltre
  • Manutenibile: abbiamo abolito le classi tuttofare per fare posto a classi leggere, facilmente leggibili e documentabili, con degli standard associati, inoltre anche lo strato Presentation è preservato. In fase di test avendo a che fare con molte classi, ma estremamente semplici, è facile (e quindi veloce) intervenire sul problema senza compromettere l’integrità dell’applicazionie

Ovviamente la gestione delle eccezioni è una parte fondamentale su cui andrebbero scritti numerosi articoli, qui è stato dato solo lo spunto di ragionamento catturando l’eccezione generica Exception ma è ovvio che le eccezioni vanno gestite e vanno gestite tutte per realizzare un sistema affidabile.

A questo punto al di là che usiate o no framework MVC, usando 1 o N ActionConfig (nel caso raro in cui lo stesso metodo di una classe Business sia associato a più pagine), o che usiate a delle servlet, oppure delle semplici JSP, utilizzare il pattern Business Delegate è di fondamentale importanza. Introducendo questo concetto nei vostri progetti vi verrà facile utilizzarlo al meglio.

PRO

  • nei grandi progetti la suddivisione a strati rende le classi e il lavoro del team meglio organizzato.
  • interfaccia unica per N classi di business con gestione delle eccezioni
  • chiamata alle classi Business dinamica
  • nessuna classe tuttofare, assicurati i principi di alta coesione/basso accoppiamento
  • basso accoppiamento con il FW

CONTRO

  • costo computazionale superiore (31ms contro i 16ms)

Apache Log4J – Il logging semplice e potente di una applicazione

Ben tornati dalle vacanze.

Vi riporto un articolo interessante comparso su Java Italian Portal che tratta un tema recentemente presentato in una delle ultime riviste di IoProgrammo (116 o 117 del 2007): Il logging negli applicativi.

Senza dubbio lo stato dell’arte è ormai Log4J di Apache, pochi forse sanno però che anche il JDK offre un sistema di logging nativo (java.util.logging).

Ciò che mi è sembrato interessante nell’articolo che vi sottopongo, è proprio la semplicità con cui è descritto questo potente framework di logging basato su tre componenti: Loggers, Appenders e Layout. Per dare un’occhiata all’articolo eccovi il link.

Se qualcuno fosse interessato ad un’analisi comparata fra Log4J di Apache ed il sitema di logging nativo del JDK vi consiglio qualche numero passato di IoProgrammo. La differenza sostanziale è comunque nella possibilità offerta da Log4J di essere configurato attraverso file xml di configurazione. Ciò lo rende può facilmente configurabile ed adattabile alle diverse esigenze di logging che si presentano in fase di sviluppo, testing e produzione.

Ciao e a presto.

un iMac MultiTouch??

E se i progettisti dell’iPhone avessero progettato un iMac?. Un demo reel di un grafico 3D, fatto in Blender, si è rivelato ai miei occhi un vero esempio di profezia involontaria. Sul suo sito, l’autore illustra simpaticamente un caso d’uso di un sistema operativo Multi Touch. Certo per la Apple è stata un’occasione sprecata…per quanto mi riguarda questo iMac immaginato sarebbe stato molto più bello di quello effettvamente realizzato. Godetevi il filmato!

Zac