fek
23-09-2005, 19:24
Qualche esempio di refactoring vale piu' di mille parole. Ecco come e' cambiata la classe Game.java nel tempo, a seguito di vari interventi di Raffele, Cesare, vicius, cisc e me:
Questa e' la prima versione:
package it.diamonds;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import it.diamonds.texture.Texture;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.*;
public class Game
{
static private boolean finished = false;
static private float width = 640f;
static private float height = 480f;
static private float quadSize = 64f;
static private Texture texture;
/**
* @param args
* @throws LWJGLException
*/
public static void main(String[] args)
{
texture = new Texture();
texture.loadTextureFromFile("diamante.png");
System.out.println("File attributes");
System.out.println("width: " + texture.getWidth());
System.out.println("height: " + texture.getHeight());
init();
texture.initOpenGLStates();
texture.setupOpenGLStates();
initOpenGL();
while(!finished)
{
render();
Display.update();
processKeyboard();
}
}
private static DisplayMode findDisplayMode(final int width,
final int height, final int bpp, final int freq)
{
final DisplayMode[] modes;
try
{
modes = Display.getAvailableDisplayModes();
for(int i = 0; i < modes.length; i++)
{
if(modes[i].getWidth() == width
&& modes[i].getHeight() == height
&& modes[i].getBitsPerPixel() >= bpp
&& modes[i].getFrequency() >= 60)
return modes[i];
}
}
catch(LWJGLException e)
{
e.printStackTrace();
}
return null;
}
private static void init()
{
try
{
DisplayMode currentMode = findDisplayMode(640, 480, 24, 60);
Display.setFullscreen(false);
Display.setDisplayMode(currentMode);
Display.setTitle("Diamonds Project - Task 1.5");
Display.create(new PixelFormat(24, 0, 24, 0, 0));
}
catch(Exception e)
{
System.err.println("The current display mode " +
"is not available due to " + e);
System.exit(1);
}
}
private static void initOpenGL()
{
glEnable(GL_NORMALIZE);
glEnable(GL_CULL_FACE);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
glClearDepth(1f);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-width / 2, width / 2, -height / 2, height / 2, -1f, 1000f);
glMatrixMode(GL_MODELVIEW);
}
private static void render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
texture.enable(GL_TEXTURE0);
glBegin(GL_TRIANGLES);
// Front
glTexCoord2f(1, 0);
glVertex3f(quadSize, -quadSize, 1);
glTexCoord2f(1, 1);
glVertex3f(quadSize, quadSize, 1);
glTexCoord2f(0, 1);
glVertex3f(-quadSize, quadSize, 1);
glTexCoord2f(1, 0);
glVertex3f(quadSize, -quadSize, 1);
glTexCoord2f(0, 1);
glVertex3f(-quadSize, quadSize, 1);
glTexCoord2f(0, 0);
glVertex3f(-quadSize, -quadSize, 1);
glEnd();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
finished = true;
}
}
Cisc aggiunge l'Audio:
package it.diamonds;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import it.diamonds.audio.Audio;
import it.diamonds.audio.Sound;
import it.diamonds.audio.SoundException;
import it.diamonds.engine.Texture;
import it.diamonds.engine.TextureNotFoundException;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.*;
public class Game
{
static private boolean finished = false;
static private float width = 640f;
static private float height = 480f;
static private float quadSize = 64f;
static private Texture texture;
static private int windowHeight = 600;
static private int windowWidth = 800;
/**
* @param args
* @throws LWJGLException
*/
public static void main(String[] args)
{
Audio audio=new Audio();
Sound sound=null;
try
{
sound=new Sound("diamond");
sound.play();
}
catch(SoundException e1)
{
e1.printStackTrace();
}
texture = new Texture();
try
{
texture.loadTextureFromFile("diamond.png");
}
catch(TextureNotFoundException e)
{
e.printStackTrace();
System.exit(0);
}
init();
texture.initOpenGLStates();
texture.setupOpenGLStates();
initOpenGL();
while(!finished)
{
render();
Display.update();
processKeyboard();
}
texture.cleanup();
}
private static DisplayMode findDisplayMode(final int width,
final int height, final int bpp, final int freq)
{
final DisplayMode[] modes;
try
{
modes = Display.getAvailableDisplayModes();
for(int i = 0; i < modes.length; i++)
{
if(modes[i].getWidth() == width
&& modes[i].getHeight() == height
&& modes[i].getBitsPerPixel() >= bpp
&& modes[i].getFrequency() >= 60)
{
return modes[i];
}
}
}
catch(LWJGLException e)
{
e.printStackTrace();
}
return null;
}
private static void init()
{
try
{
DisplayMode currentMode = findDisplayMode(windowWidth, windowHeight,
24, 60);
Display.setFullscreen(false);
Display.setDisplayMode(currentMode);
Display.setTitle("Diamonds Project - Task 1.5");
Display.create(new PixelFormat(24, 0, 24, 0, 0));
}
catch(Exception e)
{
System.err.println("The current display mode " +
"is not available due to " + e);
System.exit(1);
}
}
private static void initOpenGL()
{
glEnable(GL_NORMALIZE);
glEnable(GL_CULL_FACE);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearDepth(1f);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-width / 2, width / 2, -height / 2, height / 2, -1f, 1000f);
glMatrixMode(GL_MODELVIEW);
}
private static void render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
texture.enable(GL_TEXTURE0);
final float size = quadSize/2f;
glBegin(GL_TRIANGLES);
// Front
glTexCoord2f(1, 0);
glVertex3f(size, -size, 1);
glTexCoord2f(1, 1);
glVertex3f(size, size, 1);
glTexCoord2f(0, 1);
glVertex3f(-size, size, 1);
glTexCoord2f(1, 0);
glVertex3f(size, -size, 1);
glTexCoord2f(0, 1);
glVertex3f(-size, size, 1);
glTexCoord2f(0, 0);
glVertex3f(-size, -size, 1);
glEnd();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
finished = true;
}
}
Qui il primo refactoring corposo, con la separazione della logica del gioco (poca per ora) dall'engine grafico di presentazione. Notare come il loop principale del gioco sia stato semplificato e sia ora piu' chiaro da leggere, dopo aver implementato il pattern "Template Method":
package it.diamonds;
import it.diamonds.audio.Audio;
import it.diamonds.audio.Sound;
import it.diamonds.audio.SoundException;
import it.diamonds.engine.Engine;
import it.diamonds.engine.Texture;
import it.diamonds.engine.TextureNotFoundException;
import org.lwjgl.input.Keyboard;
public class Game
{
static private boolean finished = false;
static private int windowWidth = 800;
static private int windowHeight = 600;
static private Audio audio;
static private Engine engine;
static private Texture texture;
public static void main(String[] args)
{
createEngine();
playSound();
loadTexture();
while(!finished)
{
render();
update();
processKeyboard();
processWindow();
}
close();
}
private static void createEngine()
{
engine = new Engine(windowWidth, windowHeight);
engine.setWindowTitle("Diamonds Project");
}
private static void close()
{
texture.cleanup();
audio.shutDown();
engine.shutDown();
}
private static void loadTexture()
{
try
{
texture = new Texture("diamond");
}
catch(TextureNotFoundException e)
{
e.printStackTrace();
System.exit(0);
}
texture.initOpenGLStates();
texture.setupOpenGLStates();
}
private static void playSound()
{
audio = new Audio();
Sound sound = null;
try
{
sound = new Sound("diamond");
sound.play();
}
catch(SoundException e1)
{
e1.printStackTrace();
}
}
private static void render()
{
texture.render();
}
private static void update()
{
engine.updateDisplay();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
finished = true;
}
private static void processWindow()
{
if (engine.isWindowClosed())
finished = true;
}
}
A seguito dei problemi nel testing della classe Engine, e' stata qui introdotta una classe DisplayImpl che racchiude il codice OpenGL non testabile automaticamente:
package it.diamonds;
import it.diamonds.audio.Audio;
import it.diamonds.audio.Sound;
import it.diamonds.audio.SoundException;
import it.diamonds.engine.DisplayImpl;
import it.diamonds.engine.Engine;
import it.diamonds.engine.Texture;
import it.diamonds.engine.TextureNotFoundException;
import org.lwjgl.input.Keyboard;
public class Game
{
static private boolean finished = false;
static private int windowWidth = 800;
static private int windowHeight = 600;
static private Audio audio;
static private DisplayImpl display;
static private Engine engine;
static private Texture texture;
public static void main(String[] args)
{
createEngine();
playSound();
loadTexture();
while(!finished)
{
render();
update();
processKeyboard();
processWindow();
}
quit();
}
private static void createEngine()
{
display = new DisplayImpl(windowWidth, windowHeight);
engine = new Engine(windowWidth, windowHeight, display);
engine.setWindowTitle("Diamonds Project");
}
private static void quit()
{
texture.cleanup();
audio.shutDown();
engine.shutDown();
}
private static void loadTexture()
{
try
{
texture = new Texture("diamond");
}
catch(TextureNotFoundException e)
{
e.printStackTrace();
System.exit(0);
}
texture.initOpenGLStates();
texture.setupOpenGLStates();
}
private static void playSound()
{
audio = new Audio();
Sound sound = null;
try
{
sound = new Sound("diamond");
sound.play();
}
catch(SoundException e1)
{
e1.printStackTrace();
}
}
private static void render()
{
texture.render();
}
private static void update()
{
engine.updateDisplay();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
finished = true;
}
private static void processWindow()
{
if (engine.isWindowClosed())
finished = true;
}
}
Ultimo refactoring, con l'introduzione del pattern "Factory Method", la logica di creazione della classe Engine e' stata spostata nella classe stessa, semplificando ulteriormente la classe Game, per l'ultima versione:
package it.diamonds;
import it.diamonds.audio.Audio;
import it.diamonds.audio.Sound;
import it.diamonds.audio.SoundException;
import it.diamonds.engine.Engine;
import it.diamonds.engine.Texture;
import it.diamonds.engine.TextureNotFoundException;
import org.lwjgl.input.Keyboard;
public class Game
{
static private boolean finished = false;
static private int windowWidth = 800;
static private int windowHeight = 600;
static private Audio audio;
static private Engine engine;
static private Texture texture;
public static void main(String[] args)
{
createEngine();
playSound();
loadTexture();
while(!finished)
{
render();
update();
processKeyboard();
processWindow();
}
quit();
}
private static void createEngine()
{
engine = Engine.create(windowWidth, windowHeight, "Diamonds Project");
}
private static void quit()
{
texture.cleanup();
audio.shutDown();
engine.shutDown();
}
private static void loadTexture()
{
try
{
texture = new Texture("diamond");
}
catch(TextureNotFoundException e)
{
e.printStackTrace();
System.exit(0);
}
texture.initOpenGLStates();
texture.setupOpenGLStates();
}
private static void playSound()
{
audio = new Audio();
Sound sound = null;
try
{
sound = new Sound("diamond");
sound.play();
}
catch(SoundException e1)
{
e1.printStackTrace();
}
}
private static void render()
{
texture.render();
}
private static void update()
{
engine.updateDisplay();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
{
finished = true;
}
}
private static void processWindow()
{
if (engine.isWindowClosed())
{
finished = true;
}
}
}
Siamo soddisfatti di questa versione che e' una buona base per proseguire con l'implementazione della logica del gioco. Notare come abbiamo evoluto la prima versione verso un design logico e razionale, che separa logica da presentazione e fornisce ad ogni classe del sistema una e una sola responsabilita'. Il codice dell'ultima versione e' senza dubbio piu' semplice da leggere e quindi da mantenere rispetto al codice della prima versione.
Non e' servito fare un design up front dell'architettura, il lavoro e' stato svolto a piccoli passi da piu' persone che hanno collaborato nel design. Ognuno di noi che ha lavorato su questa classe ora la conosce e potra' intervenire in futuro, per aggiungere funzionalita' o semplificarne il design. Il tutto e' stato coperto da una decina di test che ci danno confidenza sul corretto funzionamento dell'applicazione, e ci hanno avvertito di eventuali errori durante i vari passi di refactoring.
Questa e' la prima versione:
package it.diamonds;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import it.diamonds.texture.Texture;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.*;
public class Game
{
static private boolean finished = false;
static private float width = 640f;
static private float height = 480f;
static private float quadSize = 64f;
static private Texture texture;
/**
* @param args
* @throws LWJGLException
*/
public static void main(String[] args)
{
texture = new Texture();
texture.loadTextureFromFile("diamante.png");
System.out.println("File attributes");
System.out.println("width: " + texture.getWidth());
System.out.println("height: " + texture.getHeight());
init();
texture.initOpenGLStates();
texture.setupOpenGLStates();
initOpenGL();
while(!finished)
{
render();
Display.update();
processKeyboard();
}
}
private static DisplayMode findDisplayMode(final int width,
final int height, final int bpp, final int freq)
{
final DisplayMode[] modes;
try
{
modes = Display.getAvailableDisplayModes();
for(int i = 0; i < modes.length; i++)
{
if(modes[i].getWidth() == width
&& modes[i].getHeight() == height
&& modes[i].getBitsPerPixel() >= bpp
&& modes[i].getFrequency() >= 60)
return modes[i];
}
}
catch(LWJGLException e)
{
e.printStackTrace();
}
return null;
}
private static void init()
{
try
{
DisplayMode currentMode = findDisplayMode(640, 480, 24, 60);
Display.setFullscreen(false);
Display.setDisplayMode(currentMode);
Display.setTitle("Diamonds Project - Task 1.5");
Display.create(new PixelFormat(24, 0, 24, 0, 0));
}
catch(Exception e)
{
System.err.println("The current display mode " +
"is not available due to " + e);
System.exit(1);
}
}
private static void initOpenGL()
{
glEnable(GL_NORMALIZE);
glEnable(GL_CULL_FACE);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
glClearDepth(1f);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-width / 2, width / 2, -height / 2, height / 2, -1f, 1000f);
glMatrixMode(GL_MODELVIEW);
}
private static void render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
texture.enable(GL_TEXTURE0);
glBegin(GL_TRIANGLES);
// Front
glTexCoord2f(1, 0);
glVertex3f(quadSize, -quadSize, 1);
glTexCoord2f(1, 1);
glVertex3f(quadSize, quadSize, 1);
glTexCoord2f(0, 1);
glVertex3f(-quadSize, quadSize, 1);
glTexCoord2f(1, 0);
glVertex3f(quadSize, -quadSize, 1);
glTexCoord2f(0, 1);
glVertex3f(-quadSize, quadSize, 1);
glTexCoord2f(0, 0);
glVertex3f(-quadSize, -quadSize, 1);
glEnd();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
finished = true;
}
}
Cisc aggiunge l'Audio:
package it.diamonds;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import it.diamonds.audio.Audio;
import it.diamonds.audio.Sound;
import it.diamonds.audio.SoundException;
import it.diamonds.engine.Texture;
import it.diamonds.engine.TextureNotFoundException;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.*;
public class Game
{
static private boolean finished = false;
static private float width = 640f;
static private float height = 480f;
static private float quadSize = 64f;
static private Texture texture;
static private int windowHeight = 600;
static private int windowWidth = 800;
/**
* @param args
* @throws LWJGLException
*/
public static void main(String[] args)
{
Audio audio=new Audio();
Sound sound=null;
try
{
sound=new Sound("diamond");
sound.play();
}
catch(SoundException e1)
{
e1.printStackTrace();
}
texture = new Texture();
try
{
texture.loadTextureFromFile("diamond.png");
}
catch(TextureNotFoundException e)
{
e.printStackTrace();
System.exit(0);
}
init();
texture.initOpenGLStates();
texture.setupOpenGLStates();
initOpenGL();
while(!finished)
{
render();
Display.update();
processKeyboard();
}
texture.cleanup();
}
private static DisplayMode findDisplayMode(final int width,
final int height, final int bpp, final int freq)
{
final DisplayMode[] modes;
try
{
modes = Display.getAvailableDisplayModes();
for(int i = 0; i < modes.length; i++)
{
if(modes[i].getWidth() == width
&& modes[i].getHeight() == height
&& modes[i].getBitsPerPixel() >= bpp
&& modes[i].getFrequency() >= 60)
{
return modes[i];
}
}
}
catch(LWJGLException e)
{
e.printStackTrace();
}
return null;
}
private static void init()
{
try
{
DisplayMode currentMode = findDisplayMode(windowWidth, windowHeight,
24, 60);
Display.setFullscreen(false);
Display.setDisplayMode(currentMode);
Display.setTitle("Diamonds Project - Task 1.5");
Display.create(new PixelFormat(24, 0, 24, 0, 0));
}
catch(Exception e)
{
System.err.println("The current display mode " +
"is not available due to " + e);
System.exit(1);
}
}
private static void initOpenGL()
{
glEnable(GL_NORMALIZE);
glEnable(GL_CULL_FACE);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearDepth(1f);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-width / 2, width / 2, -height / 2, height / 2, -1f, 1000f);
glMatrixMode(GL_MODELVIEW);
}
private static void render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
texture.enable(GL_TEXTURE0);
final float size = quadSize/2f;
glBegin(GL_TRIANGLES);
// Front
glTexCoord2f(1, 0);
glVertex3f(size, -size, 1);
glTexCoord2f(1, 1);
glVertex3f(size, size, 1);
glTexCoord2f(0, 1);
glVertex3f(-size, size, 1);
glTexCoord2f(1, 0);
glVertex3f(size, -size, 1);
glTexCoord2f(0, 1);
glVertex3f(-size, size, 1);
glTexCoord2f(0, 0);
glVertex3f(-size, -size, 1);
glEnd();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
finished = true;
}
}
Qui il primo refactoring corposo, con la separazione della logica del gioco (poca per ora) dall'engine grafico di presentazione. Notare come il loop principale del gioco sia stato semplificato e sia ora piu' chiaro da leggere, dopo aver implementato il pattern "Template Method":
package it.diamonds;
import it.diamonds.audio.Audio;
import it.diamonds.audio.Sound;
import it.diamonds.audio.SoundException;
import it.diamonds.engine.Engine;
import it.diamonds.engine.Texture;
import it.diamonds.engine.TextureNotFoundException;
import org.lwjgl.input.Keyboard;
public class Game
{
static private boolean finished = false;
static private int windowWidth = 800;
static private int windowHeight = 600;
static private Audio audio;
static private Engine engine;
static private Texture texture;
public static void main(String[] args)
{
createEngine();
playSound();
loadTexture();
while(!finished)
{
render();
update();
processKeyboard();
processWindow();
}
close();
}
private static void createEngine()
{
engine = new Engine(windowWidth, windowHeight);
engine.setWindowTitle("Diamonds Project");
}
private static void close()
{
texture.cleanup();
audio.shutDown();
engine.shutDown();
}
private static void loadTexture()
{
try
{
texture = new Texture("diamond");
}
catch(TextureNotFoundException e)
{
e.printStackTrace();
System.exit(0);
}
texture.initOpenGLStates();
texture.setupOpenGLStates();
}
private static void playSound()
{
audio = new Audio();
Sound sound = null;
try
{
sound = new Sound("diamond");
sound.play();
}
catch(SoundException e1)
{
e1.printStackTrace();
}
}
private static void render()
{
texture.render();
}
private static void update()
{
engine.updateDisplay();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
finished = true;
}
private static void processWindow()
{
if (engine.isWindowClosed())
finished = true;
}
}
A seguito dei problemi nel testing della classe Engine, e' stata qui introdotta una classe DisplayImpl che racchiude il codice OpenGL non testabile automaticamente:
package it.diamonds;
import it.diamonds.audio.Audio;
import it.diamonds.audio.Sound;
import it.diamonds.audio.SoundException;
import it.diamonds.engine.DisplayImpl;
import it.diamonds.engine.Engine;
import it.diamonds.engine.Texture;
import it.diamonds.engine.TextureNotFoundException;
import org.lwjgl.input.Keyboard;
public class Game
{
static private boolean finished = false;
static private int windowWidth = 800;
static private int windowHeight = 600;
static private Audio audio;
static private DisplayImpl display;
static private Engine engine;
static private Texture texture;
public static void main(String[] args)
{
createEngine();
playSound();
loadTexture();
while(!finished)
{
render();
update();
processKeyboard();
processWindow();
}
quit();
}
private static void createEngine()
{
display = new DisplayImpl(windowWidth, windowHeight);
engine = new Engine(windowWidth, windowHeight, display);
engine.setWindowTitle("Diamonds Project");
}
private static void quit()
{
texture.cleanup();
audio.shutDown();
engine.shutDown();
}
private static void loadTexture()
{
try
{
texture = new Texture("diamond");
}
catch(TextureNotFoundException e)
{
e.printStackTrace();
System.exit(0);
}
texture.initOpenGLStates();
texture.setupOpenGLStates();
}
private static void playSound()
{
audio = new Audio();
Sound sound = null;
try
{
sound = new Sound("diamond");
sound.play();
}
catch(SoundException e1)
{
e1.printStackTrace();
}
}
private static void render()
{
texture.render();
}
private static void update()
{
engine.updateDisplay();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
finished = true;
}
private static void processWindow()
{
if (engine.isWindowClosed())
finished = true;
}
}
Ultimo refactoring, con l'introduzione del pattern "Factory Method", la logica di creazione della classe Engine e' stata spostata nella classe stessa, semplificando ulteriormente la classe Game, per l'ultima versione:
package it.diamonds;
import it.diamonds.audio.Audio;
import it.diamonds.audio.Sound;
import it.diamonds.audio.SoundException;
import it.diamonds.engine.Engine;
import it.diamonds.engine.Texture;
import it.diamonds.engine.TextureNotFoundException;
import org.lwjgl.input.Keyboard;
public class Game
{
static private boolean finished = false;
static private int windowWidth = 800;
static private int windowHeight = 600;
static private Audio audio;
static private Engine engine;
static private Texture texture;
public static void main(String[] args)
{
createEngine();
playSound();
loadTexture();
while(!finished)
{
render();
update();
processKeyboard();
processWindow();
}
quit();
}
private static void createEngine()
{
engine = Engine.create(windowWidth, windowHeight, "Diamonds Project");
}
private static void quit()
{
texture.cleanup();
audio.shutDown();
engine.shutDown();
}
private static void loadTexture()
{
try
{
texture = new Texture("diamond");
}
catch(TextureNotFoundException e)
{
e.printStackTrace();
System.exit(0);
}
texture.initOpenGLStates();
texture.setupOpenGLStates();
}
private static void playSound()
{
audio = new Audio();
Sound sound = null;
try
{
sound = new Sound("diamond");
sound.play();
}
catch(SoundException e1)
{
e1.printStackTrace();
}
}
private static void render()
{
texture.render();
}
private static void update()
{
engine.updateDisplay();
}
private static void processKeyboard()
{
Keyboard.poll();
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
{
finished = true;
}
}
private static void processWindow()
{
if (engine.isWindowClosed())
{
finished = true;
}
}
}
Siamo soddisfatti di questa versione che e' una buona base per proseguire con l'implementazione della logica del gioco. Notare come abbiamo evoluto la prima versione verso un design logico e razionale, che separa logica da presentazione e fornisce ad ogni classe del sistema una e una sola responsabilita'. Il codice dell'ultima versione e' senza dubbio piu' semplice da leggere e quindi da mantenere rispetto al codice della prima versione.
Non e' servito fare un design up front dell'architettura, il lavoro e' stato svolto a piccoli passi da piu' persone che hanno collaborato nel design. Ognuno di noi che ha lavorato su questa classe ora la conosce e potra' intervenire in futuro, per aggiungere funzionalita' o semplificarne il design. Il tutto e' stato coperto da una decina di test che ci danno confidenza sul corretto funzionamento dell'applicazione, e ci hanno avvertito di eventuali errori durante i vari passi di refactoring.