Visualizzazione dei risultati da 1 a 5 su 5
  1. #1
    Utente di HTML.it
    Registrato dal
    Oct 2014
    residenza
    Padova
    Messaggi
    361

    Singleton con serializzazione

    Ciao a tutti, un po' di tempo fa avevo aperto una discussione per chiedere consigli sul design di una classe che doveva essere "leggibile" da molte altre, nel mio caso la classe contiene le preferenze che l'utente di una applicazione swing sceglie da un apposito menù.
    Mi era stato consigliato (tralasciando opzioni troppo complesse per me ) di utilizzare un singleton per questa classe, che avevo implementato in modo abbastanza standard (credo) :


    codice:
    import java.io.*;
    public class Preferences
    {
    	private static Preferences instance;
    	private boolean[] visibleColumns;
    	private boolean hideEmptyColumns,playerDown,reverseOrder;
    	private int deleteMode,sortIndex;
    	private String filterPlayer,preferredPlayer;
    	private Preferences()
    	{
    		// inizializzo le variabili ai valori "di default"
    		visibleColumns=new boolean[]{true,true,true,true,true,true};
    		deleteMode=1;
    		sortIndex=0;
    		filterPlayer=preferredPlayer="";
    		// ... qui leggo da file le preferenze specificate, se i valori letti sono accettabili aggiorno le variabili		
    	}
    	
    	// ... Vari metodi getter/setter ...
    	
    	public static Preferences getInstance()
    	{
    		if(instance==null)instance=new Preferences();
    		return instance; 
    	}
    }

    Fondamentalmente la prima volta che necessito di una preferenza viene istanziata la classe che setta i propri campi ai valori di default e poi richiama un metodo che legge le preferenze da file, controlla se i valori sono ammissibili e in caso aggiorna le preferenze.


    Dopo aver ampliato il numero di preferenze che l'utente può scegliere ho deciso di utilizzare la serializzazione per scrivere e leggere l'oggetto Preferences da file.


    Qui sorge qualche dubbio: come faccio a essere sicuro di leggere valori "corretti", senza che il file sia stato "sporcato"?
    Pensavo di modificare i controlli facendo qualcosa di questo tipo :


    codice:
    import java.io.*;
    public class Preferences implements Serializable
    {
    	private static Preferences instance;
    	private boolean[] visibleColumns;
    	private boolean hideEmptyColumns,playerDown,reverseOrder;
    	private int deleteMode,sortIndex;
    	private String filterPlayer,preferredPlayer;
    	private Preferences()
    	{
    		// Stavolta il costruttore non legge da file, imposta solo i valori di default se necessario
    		visibleColumns=new boolean[]{true,true,true,true,true,true};
    		deleteMode=1;
    		sortIndex=0;
    		filterPlayer=preferredPlayer="";
    	}
    	
    	// ... Vari metodi getter/setter ...
    	
    	public static Preferences getInstance()
    	{
    		if(instance==null)
    		{
    			try
    			{
    				ObjectInputStream reader=new ObjectInputStream(new FileInputStream("Preferenze.aur"));
    				instance=(Preferences)reader.readObject();
    				reader.close();
    				int dm=instance.getDeleteMode(),si=instance.getSortIndex();
    				if(instance==null||(dm<0||dm>2)||(si<0||s1>7)||instance.getFilterPlayer()==null||instance.getPreferredPlayer()==null)instance=new Preferences();
    			}
    			catch(Exception ex){
    				instance=new Preferences();
    			}
    		}
    		return instance; 
    	}
    }

    In pratica stavolta alla prima chiamata l'oggetto Preferences viene letto da file, e poi controllo la validità dei campi utilizzando i metodi getter sulla istanza restituita da readObject().
    In caso di errori sostituisco quindi l'istanza con una creata dal costruttore che setta solo i valori di default.


    Ha senso implementare questo procedimento all'interno di un singleton, e se sì è un "buon" modo di procedere ?


    Infine un altro consiglio di design: voglio aggiungere per l'utente la possibilità di scegliere tra diversi "stili".
    Cambiando stile verrebbero modificate impostazioni come il colore di sfondo dell'interfaccia, il colore del testo, dei bordi delle tabelle e molto altro.
    Per queste carattaristiche volevo gestire una classe a parte, ColorSet, con vari campi che memorizzano i colori scelti.


    Ha senso incapsulare l'oggetto ColorSet all'interno di Preferences ? E come salvare il set di colori scelto ?
    Ad esempio è meglio memorizzare l'indice di un ipotetico array o mantenere un reference al ColorSet scelto, cioè va bene scrivere/leggere su file il reference a un campo della classe ?


    Spero di non avere scritto troppe scemenze, grazie dell' attenzione intanto

  2. #2
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    Quote Originariamente inviata da Ansharja Visualizza il messaggio
    Qui sorge qualche dubbio: come faccio a essere sicuro di leggere valori "corretti", senza che il file sia stato "sporcato"?
    La serializzazione degli oggetti non fornisce di per sé alcun controllo sulla "integrità" dello stream. A parte header, prefissi, codici speciali ecc.. tutto il resto sono esattamente i dati in binario come sono. Cioè es. int in Java ha 32 bit e sullo stream sono appunto quei 32 bit, 4 byte in sequenza né più né meno. Le stringhe sono invece in un modified UTF-8 (una forma di UTF-8 con due varianti rispetto allo standard) quindi intelleggibili anche solo con un editor esadecimale.

    Quindi se qualcuno va a smanettare (maliziosamente o no) sui byte del file, se cambia le parti dei dati ... beh, li può cambiare esattamente come li scriverebbe la applicazione. Se tocca header, prefissi ecc.. chiaramente sballa l'intero stream.

    Se vuoi una qualche forma di signature e/o cifratura, chiaramente puoi farla, tu ovviamente (la serializzazione non ne dovrebbe sapere nulla).

    Quote Originariamente inviata da Ansharja Visualizza il messaggio
    In pratica stavolta alla prima chiamata l'oggetto Preferences viene letto da file, e poi controllo la validità dei campi utilizzando i metodi getter sulla istanza restituita da readObject().
    Se ci sono logiche particolari, per cui certi valori sono ristretti in un certo range oppure più valori sono correlati tra di loro, allora un controllo di validità di queste logiche è chiaramente saggio e ottimale. Nulla da eccepire.

    Quote Originariamente inviata da Ansharja Visualizza il messaggio
    In caso di errori sostituisco quindi l'istanza con una creata dal costruttore che setta solo i valori di default.
    Potrebbe avere anche senso lanciare una eccezione (errori di I/O, errori logici ecc...) e NON toccare instance che resta a null e quindi il getInstance potrebbe essere rieseguito.
    Ma è questione di scelte ...

    Quote Originariamente inviata da Ansharja Visualizza il messaggio
    Infine un altro consiglio di design: voglio aggiungere per l'utente la possibilità di scegliere tra diversi "stili".
    Cambiando stile verrebbero modificate impostazioni come il colore di sfondo dell'interfaccia, il colore del testo, dei bordi delle tabelle e molto altro.
    Per queste carattaristiche volevo gestire una classe a parte, ColorSet, con vari campi che memorizzano i colori scelti.


    Ha senso incapsulare l'oggetto ColorSet all'interno di Preferences ? E come salvare il set di colori scelto ?
    Ad esempio è meglio memorizzare l'indice di un ipotetico array o mantenere un reference al ColorSet scelto, cioè va bene scrivere/leggere su file il reference a un campo della classe ?
    Sì, chiaramente dovrai aggiungere tutti i campi necessari in Preferences. Non è di per sé un design sbagliato se la Preferences si ingrandisce un po' tenendo strutture dati più complesse o oggetti più annidati.

    Sul fatto di tenere un indice vs tenere il reference dell'elemento scelto, così su due piedi, io opterei per l'indice.
    Però sarebbe meglio incapsulare la logica di gestione dell'elemento scelto, mi spiego meglio: io farei una classe es. ColorTheme (che è un singolo tema) e poi es. un ColorThemes (che è un insieme di temi con uno selezionato logicamente). E in Preferences metterei ColorThemes.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

  3. #3
    Utente di HTML.it
    Registrato dal
    Oct 2014
    residenza
    Padova
    Messaggi
    361
    Grazie della risposta, disponibile come sempre

    Non avevo ancora pensato a un oggetto come al ColorThemes del tuo esempio, in effetti sarebbe più comodo nascondere la creazione degli stili a Preferences...

    Per quanto riguarda la lettura da file in questo caso non pensavo di gestire delle eccezioni (che comunque ho trovato molto utili per il problema dei caratteri illegali che avevo sollevato sempre un po' di tempo fa), lo farei se volessi informare l'utente riguardo al fatto che la lettura del file è andata male, ma lo trovo superfluo visto che non si tratta di informazioni vitali.

    Grazie ancora

  4. #4
    Utente di HTML.it
    Registrato dal
    Oct 2014
    residenza
    Padova
    Messaggi
    361
    Eccomi qua di nuovo, sono andato avanti seguendo i tuoi consigli e sono arrivato a scrivere questo (mantengo solo il codice utile alla domanda):


    Classe Preferences:


    codice:
    import java.io.*;
    public class Preferences implements Serializable
    {
        private static Preferences instance;
        private boolean[] visibleColumns;
        private boolean hideEmptyColumns,playerDown,reverseOrder;
        private int currentStyleIndex,deleteMode,sortIndex;
        private String filterPlayer,preferredPlayer;
        private Styles styles;
        private Preferences()
        {
            visibleColumns=new boolean[]{true,true,true,true,true,true};
            currentStyleIndex=sortIndex=0;
            deleteMode=1;
            filterPlayer=preferredPlayer="";
            styles=new Styles();
        }
        public Style getCurrentStyle()
        {
            return styles.getStyle(currentStyleIndex);
        }
        public static Preferences getInstance()
        {
            if(instance==null)
            {
                try
                {
                    ObjectInputStream reader=new ObjectInputStream(new FileInputStream("Preferenze.aur"));
                    instance=(Preferences)reader.readObject();
                    reader.close();
                    if(!instance.isValid())instance=new Preferences();
                }
                catch(Exception ex){
                    ex.printStackTrace();
                    instance=new Preferences();
                }
            }
            instance.setStyle(1);
            return instance; 
        }
        public boolean isValid()
        {
            if(visibleColumns==null||filterPlayer==null||preferredPlayer==null)return false;
            return deleteMode>=0&&deleteMode<=2&&sortIndex>=0&&sortIndex<=7&&currentStyleIndex>=0&&currentStyleIndex<=1&&styles.isValid();
        }
    }

    Classe Styles:


    codice:
    public class Styles implements java.io.Serializable
    {
        private Style[] styles=new Style[2];
        public Styles()
        {
            styles[0]=new Style("basic","22b166","ed1c22");
            styles[1]=new Style("ocean","1ef5fb","ffffff");
        }
        public Style getStyle(int index)
        {
            return styles[index];
        }
        public boolean isValid()
        {
            for(int i=0;i<styles.length;i++)if(!styles[i].isValid())return false;
            return true;
        }
    }

    Classe Style:


    codice:
    import java.awt.Color;
    public class Style implements java.io.Serializable
    {
        private Color backgroundColor,focusColor,foregroundColor,mainColor,specialColor,textFieldBorderColor ; // etc. , solo per dare un'idea
        private String name,buttonsFolder,focusButtonsFolder;
        public Style(String n,String bf,String fbf)
        {
            name=n;
            buttonsFolder=bf;
            focusButtonsFolder=fbf;
            // setto i vari colori che in seguito passero' al costruttore...
        }
        public String getButtonPath(int type,boolean focus)
        {
            if(focus)return "chess/img/buttons/"+focusButtonsFolder+"/"+type+".png";
            return "chess/img/buttons/"+buttonsFolder+"/"+type+".png";
        }
        public boolean isValid()
        {
            return name!=null&&buttonsFolder!=null&&focusButtonsFolder!=null&&backgroundColor!=null&&focusColor!=null&&foregroundColor!=null&&mainColor!=null
            &&specialColor!=null&&textFieldBorderColor!=null;
        }
    }

    Da quello che dicevi nello scorso messaggio riguardo alla serializzazione penso di aver capito questo:


    - i valori booleani non vanno controllati a livello di esistenza (non possono essere messi a null ovviamente), nel mio caso quindi il metodo isValid() di Preferences non li controlla in quanto, anche se venissero modificati, non farebbero danni.
    Se invece fosse stato rimosso un header o un prefisso di quelle variabili verrebbe lanciata un'eccezione e quindi richiamato il costruttore di default.
    - stesso discorso per le variabili intere, dove però controllo i valori perchè devono essere contenuti in un certo range.
    - per il resto delle variabili controllo solo il fatto che non siano null, da cui la catena di metodi isValid(), che mi pare un po' ripetitiva ma dovrebbe essere necessaria per essere sicuro di non avere in seguito eccezioni che non voglio dover gestire.


    E' corretto quanto ho scritto?


    Per quanto riguarda invece il design ho usato appunto la doppia classe Styles (di cui ho un'istanza in Preferences) e Style.
    Ho memorizzato in Preferences la variabile currentStyleIndex, e la utilizzo per reperire lo stile corrente nel metodo getCurrentStyle().


    Un'altra "complicazione" è che ogni stile ha anche delle proprie immagini, quindi passo ad ogni stile le cartelle da cui prendere le immagini (per ora solo i bottoni di diversi colori) e ritorno il path con il metodo getButtonPath(), che immagino potrebbe essere scritto meglio.


    Può andare bene (o almeno benino) come design ?
    Ultima modifica di Ansharja; 15-04-2016 a 15:07

  5. #5
    Utente di HTML.it
    Registrato dal
    Oct 2014
    residenza
    Padova
    Messaggi
    361
    Altra domanda che mi sorge: ho scoperto adesso l'esistenza del modificatore transient , potrei usarlo per non salvare su file l'oggetto Styles, che deve essere costruito sempre uguale ?
    In fin dei conti a me interessa solo leggere l'ultimo stile scelto e in questo modo non dovrei controllare che sia letto correttamente ...

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.