communityWir suchen ständig neue Tutorials und Artikel! Habt ihr selbst schonmal einen Artikel verfasst und seid bereit dieses Wissen mit der Community zu teilen? Oder würdet ihr gerne einmal über ein Thema schreiben das euch besonders auf dem Herzen liegt? Dann habt ihr nun die Gelegenheit eure Arbeit zu veröffentlichen und den Ruhm dafür zu ernten. Schreibt uns einfach eine Nachricht mit dem Betreff „Community Articles“ und helft mit das Angebot an guten Artikeln zu vergrößern. Als Autor werdet ihr für den internen Bereich freigeschaltet und könnt dort eurer literarischen Ader freien Lauf lassen.

Battleship - Model Drucken E-Mail
Benutzerbewertung: / 87
SchwachPerfekt 
Sonntag, den 10. Mai 2009 um 00:00 Uhr
Beitragsseiten
Battleship
Beginn des Projekts
Model
Netzwerkprogrammierung in Java
Netzwerkprotokolle
Routing
View
Computerspieler
Controller
Alle Seiten

Model

Wir haben die wesentlichen Daten für das Model definiert. Nun gilt es mithilfe dieser Daten eine Geschäftslogik zu entwickeln, die es der Applikation erlaubt diese Daten konsistent zu manipulieren. Zum gegenwärtigen Zeitpunkt sind unsere Datenklassen nicht viel mehr, als ein loser Verbund von zwecklosen Klassen ohne jeden Kontext.

Die Implementierung eines vernünftigen Models erfordert die Einhaltung von strengen Regeln. Mit dem Model steht und fällt die Anwendung. Umso leistungsfähiger das Model ist, umso mehr Möglichkeiten geben sie Ihrer Applikation diese Leistung später auch voll auszureizen. Sollte das Model unzureichend sein, werden Sie zu einem späteren Zeitpunkt auf massive Probleme stoßen, die sich negativ auf ihr Design niederschlagen.

Zu den Grundkonzepten von MVC gehört das Observer Entwurfsmuster. Beobachter erhalten die Möglichkeit sich beim Model zu registrieren. Das Model stellt hierfür Schnittstellen und Methoden bereit. Es geht nicht näher darauf ein, wer sich bei ihm registriert. Java bietet eine fertige Observer-Schnittstelle und eine Observable-Klasse an, die der Entwickler verwenden kann.

Die Klasse Observable muss von allen Klassen erweitert werden, die das zu beobachtende Objekt darstellen. Das Model repräsentiert die Daten, so dass Beobachter sich beim Model registrieren müssen um über Datenänderungen informiert zu werden. Als analoges Beispiel in der Praxis sei das Zeitungsabonnement genannt. Abonnenten registrieren sich bei einem Verlag und werden bei Neuerscheinungen informiert bzw. erhalten ein Exemplar zugesandt.

Verwendete Softwaretechniken

Neben dem Entwurfsmuster Observer, verwendet das Model noch einige weitere Softwaretechniken, die in den folgenden Abschnitten kurz erläutert werden sollen.

Das Singleton Muster

Das Singleton (auch Einzelstück genannt) ist ein in der Softwareentwicklung eingesetztes Entwurfsmuster und gehört zur Kategorie der Erzeugungsmuster (engl. Creational Patterns). Es verhindert, dass von einer Klasse mehr als ein Objekt erzeugt werden kann. Dieses Einzelstück ist darüber hinaus üblicherweise global verfügbar. Das Muster ist eines der von der so genannten Viererbande (GoF) publizierten Muster.

Auch unser Model soll einzigartig sein. Es sollen keine weiteren Instanzen kreiert werden können. Dadurch kann eine Zugriffskontrolle realisiert werden.

Die Schnittstelle

Wann immer Sie Code schreiben, sollten Sie versuchen gegen eine Schnittstelle zu programmieren um die Implementierung austauschbar zu machen. Das Model bietet der Außenwelt eine Reihe von Methoden, so dass die Definition einer Schnittstelle Sinn macht. Das ModelInterface beinhaltet alle Schnittstellen, die von der Anwendung benötigt werden.

Nachdem einige wichtige Regeln bei der Programmierung eines Models genannt wurden, lässt sich ein grober Rumpf implementieren.

public final class BattleshipModel extends Observable 
        implements ModelInterface, Observer, Serializable
{   
    /**
     * Get a Singleton instance of the model class using double-checked
     * locking with a volatile field.
     */
    public static BattleshipModel getInstance() 
    {
        if( modelInstance_ == null ) {
            synchronized(BattleshipModel.class) {
                if( modelInstance_ == null ) {
                    modelInstance_ = new BattleshipModel();
                }
            }
        }
 
        return modelInstance_;
    }
 
    /**
     * This model is an observable as well as an observer of another object.
     * In our case, this model will observe the network node. Any changes of the
     * node will be instantly dispatched to our observers.
     * 
     * @param o the observable object
     * @param arg   arguments, send by the observable
     */
    @Override
    public void update(Observable o, Object arg)
    {        
    }
 
    /**
     * @serial  BattleshipModel Singleton instance
     */
    private static volatile BattleshipModel modelInstance_;
 
    /**
     * Determines if a de-serialized file is compatible with this class.
     *
     * Maintainers must change this value if and only if the new version
     * of this class is not compatible with old versions. See Sun docs
     * for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
     * /serialization/spec/version.doc.html> details. </a>
     *
     * Not necessary to include in first version of the class, but
     * included here as a reminder of its importance.
     */
    private static final long serialVersionUID = 3103200964227944224L;
}

Wie Sie erkennen können, implementiert das Model zusätzlich das Observer Interface. Den Grund dafür werden Sie später erfahren.

Neben den trivialen Spieldaten muss ein komplexes Model auch weitere Applikationsdaten verwalten. In einer echten Anwendung sind das Umgebungsdaten, Daten aus Konfigurationsdateien oder Sprachdateien. Letzteres führt uns zu einer wesentlichen Komponente in jeder Systemarchitektur - der Internationalisierung/Lokalisierung.

Internationalisierung/Lokalisierung

Ziel ist, alle Aspekte bereits bei dem Design und der Entwicklung des Produktes zu berücksichtigen, die einen weltweiten Einsatz, Verkauf, Wartung und Weiterentwicklung des Produktes ermöglichen. Zu Beginn des Produktentwicklungszyklus müssen hier detaillierte technische Überlegungen zu den Entwicklungsanforderungen eines Produkts für bestimmte lokale Märkte oder generell für den internationalen Markt im Vordergrund stehen, wie zum Beispiel Einsatz von Internationalisierungsstandards und -normen wie Unicode und andere Multibyte Zeichensatz-Technologien, um überhaupt die Benutzung und Verarbeitung der vielfältigen Buchstaben- und Zeichendarstellungen der Sprachen in den Software-Produkten zu ermöglichen.

Die Software-Lokalisierung ist der eigentliche Prozess der Anpassung und Übersetzung von internationalisierten Software-Anwendungen in die gewünschten Sprachen und deren lokalen, kulturellen und geographischen Standards.

Java bietet dem Programmierer einen angenehmen Weg, diese Herausforderungen in der Praxis zu meistern und die eigene Anwendung zu internationalisieren. Ein Vorteil besteht in der Tatsache das Java von vornherein Unicode zu 100 Prozent unterstützt. Bei allen Strings handelt es sich um Unicode, so dass keine besonderen Richtlinien bei der Internationalisierung beachtet werden müssen.

Mithilfe von Properties ist es möglich in Java Sprachdateien zu entwerfen. Die Sprachdateien folgen einer festgelegten Namenskonvention, welche in unserem Fall, wie folgt aussieht:

Battleship.properties
Battleship_de_DE.properties
Battleship_en_US.properties

Je nach Einstellung oder Umgebung kann mit der folgenden Methode die korrekte Datei ausgelesen werden.

ResourceBundle language = ResourceBundle.getBundle("Battleship", currentLocale);

Für die Verwaltung der Sprache in der Anwendung Battleship wurde eine zusätzliche Klasse definiert. Die Klasse Language.java kapselt intern den Zugriff auf Properties und verwaltet Lokalitäten. Sie stellt Methoden zur Verfügung, die entsprechende Schlüssel in Werte mappen. Spezifische Schlüssel werden je nach Laufzeitumgebung automatisch in den richtigen Sprachstring transformiert. Die Datei Battleship_de_DE.properties enthält unter anderen folgende Schlüssel/Wert-Paare.

BATTLESHIP.BLUE=Blau
BATTLESHIP.RED=Rot
BATTLESHIP.YELLOW=Gelb
BATTLESHIP.BLACK=Schwarz
BATTLESHIP.ORANGE=Orange
BATTLESHIP.GREEN=Grün
BATTLESHIP.GRAY=Grau

Nachfolgend können Sie eine Methode der Klasse Language.java begutachten.

/**
 * Gets a string for the given key from this resource bundle or one of its
 * parents.
 *
 * @param key   the hashtable key
 * @return  the string for the given key
 */
public String getString(String key)
{
    String string = "";
 
    try {
        string = bundle_.getString( key );
    } catch( NullPointerException e ) {
        logger_.warning( e.getMessage() );
    } catch( MissingResourceException e ) {
        logger_.warning( e.getMessage() );
    }
 
    return string;
}

Alle Strings in einer internationalen Anwendung müssen auf diese Weise abstrahiert werden. Die Anwendung transformiert die Schlüssel anschließend in die korrekte Sprache. Das Spiel Battleship erlaubt es problemlos die Anwendung mit weiteren Sprachen auszustatten, ohne den Quelltext antasten zu müssen. Dazu muss lediglich die neue Sprachdatei mit der Übersetzung zur Verfügung gestellt werden.

Ressourcenverwaltung

Jede Anwendung muss diverse Ressourcen verwalten können. Die Konfigurationsdatei ist eine wichtige Komponente dieser Ressourcen. Es handelt sich dabei um eine Datei, in der bestimmte Einstellungen (die Konfiguration) von Computerprogrammen oder Hardwarebestandteilen gespeichert sind.

Die Singleton Klasse Environment.java ist für die Verwaltung der Umgebungsdaten und Ressourcen in der Anwendung Battleship zuständig. Environment beinhaltet auch die Klasse Language zur Sprachenverwaltung. Darüberhinaus speichert diese Klasse elementare Informationen über die Laufzeitumgebung, darunter die Java-Version und das Betriebssystem auf dem die Anwendung läuft.

/**
 * Determines what operating system this system is using.
 */
private void initializeOS()
{
    // Get the operating system
    String os = System.getProperty("os.name").toLowerCase();
 
    if( os.startsWith("mac os") ) {
        isMacOSX_ = os.endsWith("x");
        return;
    } else if( os.indexOf( "windows" ) != -1 ) {
        isWindowsOS_ = true;
        if ( os.indexOf( "windows 2000" ) != -1 ||
            os.indexOf( "windows xp" ) != -1 ) {
            isWin2000orXpOS_ = true;
        }
        return;
    }
}
Konfiguration

Daten, die während der Programmausführung im flüchtigen Arbeitsspeicher liegen, müssen nach Beendigung der Anwendung auf der Festplatte hinterlegt werden. Zu diesen Daten gehören elementare Einstellungen, darunter die eingestellte Sprache oder die verwendete Portnummer der Applikation. Diese Daten werden in der Regel vom Benutzer verändert und müssen auch nach dem Programmstart wieder eingelesen werden können.

Eine Speicherform der Konfigurationsdatei ist die bekannte Initialisierungsdatei (.ini). Sie speichert Schlüssel/Wert-Paare und ermöglicht auf diese Weise das einfache Auslesen von Daten. In Java lässt sich diese Funktionalität mit den bereits genannten Properties nachahmen.

JAXB (Java Architecture for XML Binding)

Eine wesentlich angenehmere Vorgehensweise Konfigurationsdateien zu generieren und wieder auszulesen bietet die Programmschnittstelle JAXB. Sie ermöglicht es, Daten aus einer XML-Schema-Instanz heraus automatisch an Java-Klassen zu binden, und diese Java-Klassen aus einem XML-Schema heraus zu generieren.
Somit ist ein Arbeiten mit XML-Dokumenten möglich, ohne dass der Programmierer direkt Schnittstellen zur Verarbeitung von XML wie SAX oder DOM verwenden muss.

jaxb

Die Klasse Environment verwaltet die Konfiguration der Anwendung. Vor dem Programmstart wird diese Klasse generiert und liest alle wichtigen Daten aus der Konfigurationsdatei ein. Die Klasse stellt hierfür eine Methode zur Verfügung, die den Namen getConfiguration() trägt.

/**
 * Returns the Configuration representing the complete structure.
 *
 * @return a Config object with the complete configuration
 */
public synchronized Config getConfiguration()
{
    if( configRoot_ == null ) {
        try {
            setupConfigRoot( null );
        } catch( IOException e ) {
            // initialize the file name.
            System.exit( - 1 );
        }
    }
 
    return configInstance_;
}

Diese Methode liefert eine Instanz der Klasse Config zurück, die die zu Java-Klassen gebundenen Daten beherbergt. Dazu ruft die synchronisierte Methode die private Methode setupConfigRoot() auf. Diese Methode generiert die JAXB Marshaller und liest die Daten aus einer XML-Datei ein.

/**
 * Sets the directory into which Battleship adds its configuration files
 * and other data. When configRoot is null the directory is set to:
 * {user.home}/data on windows systems and {user.home}/.data on unix
 * and mac systems.
 *
 * @param configRoot the directory of the configuration files or null
 * @return  true if cofiguration setup was successful
 */
private boolean setupConfigRoot(File configRoot)
        throws IOException
{
    if( configRoot == null ) {
        StringBuffer path = new StringBuffer( 25 );
        // Get the working directory. This is the location in the file system
        // from where the java command was invoked.
        path.append( System.getProperty( "user.home" ) );
        path.append( File.separator );
 
        // Since there are many UNIX like operation systems with Java
        // support out there, we can not recognize the OS through it's name.
        // Thus we check if the root of the filesystem starts with "/"
        // since only UNIX uses such filesystem conventions.
        if( File.separatorChar == '/' ) {
            path.append ('.');
        }
 
        path.append( "Battleship" );
        configRoot_ = new File( path.toString() );
    } else {
        configRoot_ = configRoot;
    }
 
    // Create JAXB variables
    JAXBContext context = null;
    Marshaller marshaller = null;
    Unmarshaller unmarshaller = null;
    Config config = null;
 
    File configuration = new File( configRoot_ + File.separator + configFileName_ );
    File loggingPath = new File( configRoot_ + File.separator + logDir_ );
 
    try {
        // Get JAXB context and instantiate marshaller
        context = JAXBContext.newInstance( Config.class );
        marshaller = context.createMarshaller();
        marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
 
        // Check for file and directory
        if( configuration.exists() ) {
            unmarshaller = context.createUnmarshaller();
            FileInputStream in = new FileInputStream( configRoot_ + 
                    File.separator + configFileName_ );
            Reader reader = new InputStreamReader( in, "UTF-8" );
            configInstance_ = (Config) unmarshaller.unmarshal( reader );
        } else {
 
            // Check if we have a root directory
            if( !configRoot_.isDirectory() ) {
                boolean success = configRoot_.mkdirs();
                if( !success ) {
                    System.err.println( "Unable to create directory." );
                    return false;   // Fatal error
                }
            }
 
            // Generate a new config file
            Preferences pref = new Preferences();
            pref.setDatadir( configRoot_.getPath() + File.separator );
            pref.setLocale( "en_US" );
            pref.setPort( "8734" );
            pref.setLogFilePath( loggingPath.getPath() + File.separator );
 
            config = new Config();
            config.setWebsite( Version.getURL() );
            config.setVersion( Version.getShortVersion() );
            config.setPublisher( Version.getCopyright() );
            config.setProduct( Version.getProduct() );
            config.setLastUpdate( "" );
            config.setPreferences( pref );
 
            // Create JAXB context and instantiate marshaller
            context = JAXBContext.newInstance( Config.class );
            marshaller = context.createMarshaller();
            marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
            File file = new File( configRoot_ + File.separator + configFileName_ );
            marshaller.marshal( config, new FileOutputStream( file ) );
 
            // Set the config instance of the model
            configInstance_ = config;
        }
    } catch( JAXBException e ) {
        e.printStackTrace();
        return false;   // Fatal error
    } catch( FileNotFoundException e ) {
        e.printStackTrace();
        return false;   // Fatal error
    } catch( IOException e) {
        e.printStackTrace();
        return false;   // Fatal error
    }
 
    // Now check for language directory, too
    if( !loggingPath.isDirectory() ) {
        boolean success = loggingPath.mkdir();
 
        if( success == false ) {
            System.err.println( "Unable to create directory." );
            return false;   // Fatal error
        }
    }
 
    // At this point, we should have a valid Config instance
    if( configInstance_ == null ) {
        // Todo: logger
        System.err.println( "Couldn't create Config instance." );
        return false;
    }
 
    String resources = "eu/codeplanet/battleship/resources/lang/Battleship";
 
    // Perform some integrity checks
    if( configInstance_.getPreferences() == null ||
            configInstance_.getPreferences().getLocale().isEmpty() ||
            configInstance_.getPreferences().getLocale().length() != 5 ) {
        // Get the JVM Locale
        String country = Locale.getDefault().getCountry();
        String language = Locale.getDefault().getLanguage();
        // Set the config file
        configInstance_.getPreferences().setLocale( language + "_" + country );
        ResourceBundle bundle = ResourceBundle.getBundle( resources );
        try {
            language_ = new Language( bundle ); // default
        } catch( MissingResourceException e) {
            e.printStackTrace();
            return false;
        }
    } else {
        // Load the locale from the config
        String tmp = configInstance_.getPreferences().getLocale();
        String language = tmp.split("_")[0];
        String country = tmp.split("_")[1];
        locale_ = new Locale( language, country );
        try {
            ResourceBundle bundle = ResourceBundle.getBundle( resources, locale_ );
            language_ = new Language( bundle );
        } catch( MissingResourceException e) {
            e.printStackTrace();
            return false;
        }
    }
 
    // Everything was fine, we are ready to proceed
    return true;
}

Die Methode lokalisiert zunächst das Stammverzeichnis des Benutzers. In diesem Verzeichnis werden anschließend die Programmdateien hinterlegt. Dazu wird im Stammverzeichnis bei Bedarf ein separater Ordner namens "Battleship" erstellt. Nachdem der Ordner erfolgreich generiert wurde, überprüft die Methode das Vorhandensein der Konfigurationsdatei Settings.Config. Ist diese vorhanden, werden alle Daten nacheinander mithilfe des Unmarshaller in die Klasse Config eingelesen. Die Klasse Config enthält unter anderen eine Unterklasse Preferences mit den Einstellungen und weiteren Programmdaten. Alle Objekte dieser Klasse werden bei Programmende automatisch in eine XML-Datei geschrieben. Diese XML-Datei hat gegenwärtig den folgenden Inhalt:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:config xmlns:ns2="eu.codeplanet.battleship">
    <lastUpdate></lastUpdate>
    <preferences>
        <datadir>C:\Users\Christian\Battleship\</datadir>
        <locale>en_US</locale>
        <logFilePath>C:\Users\Christian\Battleship\log\</logFilePath>
        <port>8734</port>
    </preferences>
    <product>Battleship</product>
    <publisher>Copyright © 2005 - 2009 CodePlanet. All rights reserved.</publisher>
    <version>1.0.0</version>
    <website>http://www.codeplanet.eu/</website>
</ns2:config>

Die Methode setupConfigRoot() generiert eine Standarddatei, falls keine Konfigurationsdatei existiert. Die Klasse Config wird mit Default-Werten gefüllt und anschließend in eine Datei geschrieben. Das Spiel Battleship bezieht alle seine Programmdaten aus dieser Konfigurationsdatei. Die Klassen lassen sich bei Bedarf problemlos erweitern

Die Klasse Environment stellt eine Basisklasse in der Anwendung dar, ohne die das Programm nicht starten kann. Als Verwalter der Umgebungsdaten und Ressourcen, leitet sie alle wichtigen Informationen an das Model weiter. Das Model besitzt eine Instanz dieser wichtigen Klasse. Nach dem Programmstart muss das Model initialisiert werden. In der synchronisierten Initialisierungsphase generiert das Model die Umgebung und liest alle notwendigen Informationen ein.

/**
 * Initializes the model. Call this method immediately after
 * constructing the model object.
 *
 * @return  true if model was successfully initialized
 */
@Override
public synchronized boolean initialize()
{
    // Check if the environment was already initialized
    if( environment_ != null ) {
        return false;
    }
 
    // Get the system environment
    environment_ = Environment.getInstance();
 
    // Check if environment setup was successful
    if( environment_ == null ||
            environment_.getConfiguration() == null ||
            environment_.getLanguage() == null) {
        return false;
    }
 
    on_ = true; // Set model active
 
    return true;
}

Die Methode initialize() des Models muss unmittelbar nach Programmstart aufgerufen werden.



Zuletzt aktualisiert am Montag, den 23. April 2012 um 12:59 Uhr
 
AUSWAHLMENÜ