HTML5-Tutorium: Canvas: MiniPong 05: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Zeile 282: Zeile 282:
Vier Atribute wurden allerdings im Diagramm als nur lesbar (read only, <code>frozen</code>)
Vier Atribute wurden allerdings im Diagramm als nur lesbar (read only, <code>frozen</code>)
deklariert: <code>left</code>, <code>right</code>, <code>top</code> und <code>bottom</code>.  
deklariert: <code>left</code>, <code>right</code>, <code>top</code> und <code>bottom</code>.  
Diese Attribute dürfen also nicht geändert sondern nur gelesen werden,
Diese Attribute dürfen also nicht geändert, sondern nur gelesen werden,
da die Werte aus anderen Attributen berechnet werden. Dies kann mit Hilfe echter Getter-Methoden
da die Werte aus anderen Attributen berechnet werden. Daher können die Werte auch nicht
recht elegant realisiert werden:
in Zustandsvariablen gespeichert werden. Allerdings kann man Read-only-Attribute
mit Hilfe echter Getter-Methoden recht elegant realisieren:


<source lang="javascript">
<source lang="javascript">
Zeile 306: Zeile 307:


console.log(my_paddle.left); // Aufruf der Getter-Methode
console.log(my_paddle.left); // Aufruf der Getter-Methode
</source>
Da keine Setter-Methoden existieren, können diese Attribute nicht geändert werden.
Falls man seinen JavaScript-Code mittels <code>"use strict";</code>  als „strikt“ dekalriert hat,
resultiert folgende Anweisung in einer Fehlermeldung:
<source lang="javascript">
my_paddle.left = 30; // Aufruf der nicht vorhandenen Setter-Methode
</source>
</source>



Version vom 5. November 2012, 13:39 Uhr

Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:

Korrektheit: 3
(zu größeren Teilen überprüft)
Umfang: 3
(einige wichtige Fakten fehlen)
Quellenangaben: 5
(vollständig vorhanden)
Quellenarten: 5
(ausgezeichnet)
Konformität: 5
(ausgezeichnet)

HTML-Tutorium: MiniPong

MiniPong: | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5

Musterlösung: Minipong 4 (SVN-Repository)

Dieser Artikel wird derzeit von einem Autor gründlich bearbeitet. Die Inhalte sind daher evtl. noch inkonsistent.

Ziel: Simulation von Klassen in JavaScript

Im vierten Teil des Tutoriums wird beschrieben, wie man Klassen in JavaScript nachbilden kann.

Vier Klassen werden implementiert: Main, Ball, Paddle und Collision. Es wird jeweils ein Objekt pro Klasse erstellt.

miniatur|ohne|709px|Das Datenmodell von MiniPong 04

Anwendung „MiniPongCanvas04

Neues Projekt anlegen

Legen Sie ein neues Statisches Web-Projekt mit dem Namen MiniPongCanvas04 an.

Speichern Sie dieses Projekt wie üblich in Ihrem Repository.

Dateien erstellen

Kopieren Sie die Dateien index.html, css/main.css und js/CONSTANT.js von Teil 3 des Tutoriums, passen Sie den Projekttitel in der Datei index.html an.

index.html

Jede Klasse soll in einer eigenen Datei definiert werden, wie dies auch bei anderen Programmiersprachen wie z.B. Java üblich ist. Erstellen Sie zu diesem Zweck zunächst drei leere Dateien js/ball.js, js/paddle.js sowie js/collision.js und fügen Sie anschließend folgende drei Zeilen in die Datei index.html ein (vor der Zeile, in der js/main.js geladen wird):

<script type="text/javascript" src="js/ball.js"     ></script>
<script type="text/javascript" src="js/paddle.js"   ></script>
<script type="text/javascript" src="js/collision.js"></script>

Anmerkung: Um den Kommunikations-Overhead zwischen Client (Browser) und Server möglichst gering zu halten, sollte eine HTML-Seite möglichst wenige weitere Dokumente (Bilder, CSS-Dateien, JavaScript-Dateien etc.) nachladen. Die Definition weiterer JavaSCript-Dateien stellt hier aber kein Problem dar, wenn – sobald die Anwendung fertiggestellt und ausgiebig getestet wurde – alle JavaScript-Dateien komprimiert und in eine einzige Datei (js/all.min.js) eingefügt werden. Natürlich darf in der Datei index.html anschließend auch nur noch diese eine JavaScript-Datei geladen werden.

ball.js

Die Definition der Klasse Ball wird in JavaScript durch zwei Objekte realisiert oder – besser gesagt – simuliert: Eine Konstruktor-Funktion (genauer: ein Konstruktor-Funktion-Objekt) sowie das zugehörige prototype-Objekt.

Die Konstruktor-Funktion Ball erstellt neue Ball-Objekte und initialisiert diese, indem sie im neu erstellten Objekt (this) Attribute erstellt und initialisiert.

Konstruktor

/** 
 *  Creates an instance of <code>Ball</code>.
 *
 *  @constructor
 *  @this {Ball} 
 *  @param {CanvasRenderingContext2D} p_context 
 *         The 2d context of the canvas upon which the ball is to be drawed.
 *  @param {number} p_r
 *         The radius of the ball.
 *  @param {number} [p_x_start = BALL_X]
 *         The starting position of the ball in x-direction.
 *  @param {number} [p_y_start = BALL_Y]     
 *         The starting position of the ball in y-direction.
 *  @param {number} [p_vx_start_min = BALL_VX_MIN] 
 *         The minimal starting velocity of the ball in x-direction. 
 *  @param {number} [p_vx_start_max = BALL_VX_MAX]
 *         The maximal starting velocity of the ball in x-direction.
 *  @param {number} [p_vy_start_min = BALL_VY_MIN]
 *         The minimal starting velocity of the ball in y-direction.
 *  @param {number} [p_vy_start_max = BALL_VY_MAX} 
 *         The maximal starting velocity of the ball in y-direction.
 */
function Ball(p_context, 
              p_r, p_x_start, p_y_start, 
              p_vx_start_min, p_vx_start_max, 
              p_vy_start_min, p_vy_start_max
             )
{this.r = p_r || BALL_RADIUS;

  this.v_x_start        = p_x_start      || BALL_X;
  this.v_y_start        = p_y_start      || BALL_Y;
  this.v_vx_start_min   = p_vx_start_min || BALL_VX_MIN; 
  this.v_vx_start_max   = p_vx_start_max || BALL_VX_MAX; 
  this.v_vy_start_min   = p_vy_start_min || BALL_VY_MIN; 
  this.v_vy_start_max   = p_vy_start_max || BALL_VY_MAX;

  this.v_vx_start_delta = this.v_vx_start_max - this.v_vx_start_min;
  this.v_vy_start_delta = this.v_vy_start_max - this.v_vy_start_min;

  this.v_context  = p_context;
  
  this.reset();
};

Die reset-Methode nimmt weitere Initialisierungen vor. Da diese Initialisierungen bei jedem Spielstart erneut ausgeführt werden müssen, wurden sie in eine Methode ausgelagert.

Beachten Sie bitte, dass der dem Konstruktor vorangestellte Kommentar JSDoc-konform ist. Dies ermöglicht es, mit einem geeigneten JSDoc-Tool eine HTML-API-Dokumentation automatisch zu erstellen.

Methoden

Der Konstruktor weist jedem neu erstellten Objekt ein weiteres Attribut automatisch zu: prototype. Dieses Attribut enthält einen Verweis auf das prototype-Objekt des zugehörigen Konstruktors. JavaScript sucht bei einem Methodenaufruf obj.m() die Definition der Methode m zunächst im Objekt obj selbst. Sollte dort keine Definition vorhanden sein, so sucht JavaScript als nächstes in obj.prototype, dann in obj.prototype.prototype usw. Die Suche endet sobald entweder eine Definition gefunden oder kein weiteres prototype-Objekt mehr existiert. Im letzteren Fall meldet JavaScript einen Fehler.

Das heißt, in JavaScript werden Methoden üblicherweise im Prototyp-Objekt des Konstruktors definiert. (Anmerkung: Auch hier werden wieder JSDoc-konforme Kommentare benutzt.)

Ball.prototype =
{ //////////////////////////////////////////////////////////////////////////////
  // data
  //////////////////////////////////////////////////////////////////////////////
  
  /** The radius of the ball. */  
  get r()    { return this.v_r; },
  set r(p_r) { this.v_r = p_r;  },

  /** The x-position of the ball. */
  get x()    { return this.v_x; },
  set x(p_x) { this.v_x = p_x;  },

  /** The y-position of the ball. */
  get y()    { return this.v_y; },
  set y(p_y) { this.v_y = p_y;  },

  /** The vx-velocity of the ball. */
  get vx()     { return this.v_vx; },
  set vx(p_vx) { this.v_vx = p_vx; },

  /** The vy-velocity of the ball. */
  get vy()     { return this.v_vy; },
  set vy(p_vy) { this.v_vy = p_vy; },

  //////////////////////////////////////////////////////////////////////////////
  // logic
  //////////////////////////////////////////////////////////////////////////////

  /** 
   * Resets the ball: 
   *   Moves the ball to its starting position 
   *   and computes randomly a starting velocity vector.
   */
  reset:
    function()
    { this.x  = this.v_x_start;
      this.y  = this.v_y_start;
      this.vx = (Math.random()<0.5?1:-1)
                * (this.v_vx_start_min+Math.random()*this.v_vx_start_delta);
      this.vy = (this.v_vy_start_min+Math.random()*this.v_vy_start_delta);
    },

  /** Moves the ball in direction (vx,vy); the step size depends on FPS. */
  move:
    function()
    { this.x += this.vx/FPS;
      this.y += this.vy/FPS;
    },

  //////////////////////////////////////////////////////////////////////////////
  // view
  //////////////////////////////////////////////////////////////////////////////

  /** Draws the ball at its current position onto a 2d context. */
  draw:
    function()
    { this.v_context.beginPath();
      this.v_context.arc(this.x, this.y, this.r, 0, 2*Math.PI, true);
      this.v_context.lineWidth = BALL_BORDER_WIDTH;
      this.v_context.lineStyle = BALL_BORDER_COLOR;
      this.v_context.fillStyle = BALL_COLOR;
      this.v_context.stroke();
      this.v_context.fill();
    },

  //////////////////////////////////////////////////////////////////////////////
  // end of prototype
  //////////////////////////////////////////////////////////////////////////////
};

Beachten Sie, dass die Getter- und Setter-Methoden für die Attribute r, x, y, vx, vy EcmaScript-5-konform (HTML5) definiert wurden. Das hat den Vorteil, dass man mit der üblichen Attribut-Syntax auf diese Attribute zugreifen kann:

var my_ball = new Ball();

my_ball.r = 20;         // Aufruf der Setter-Methode
console.log(my_ball.r); // Aufruf der Getter-Methode

In EcmaScript 3 (HTML 4 oder früher) gibt es diese Art der Getter- und Setter-Methoden nicht. Hier muss man auf den in Java üblichen Trick zurückgreifen und ersatzweise zwei Methoden definieren.

/** The radius of the ball. */  
getR: function()    { return this.v_r; },
setR: function(p_r) { this.v_r = p_r;  },

...

In diesem Fall erfolgen die Attribut-Zugriffe mittels Methodenaufrufen:

var my_ball = new Ball();

my_ball.setR(20);            // Aufruf der Setter-Methode
console.log(my_ball.getR()); // Aufruf der Getter-Methode

Die Verwendung von echten Getter- und Setter-Methoden erhöht die Stetigkeit des Codes, da sich der Zugriff auf öffentliche Zustandsvariablen syntaktisch nicht vom Zugriff auf echte Getter- und Setter-Methoden unterscheidet. Das heißt, die Implementierung der Klasse kann jederzeit die Art der Attribut-Definition ändern, ohne dass das Auswirkungen auf die Zugriff-Syntax von zugehörigen Objekten hätte.[1]

Beispielsweise könnten in der obigen Definition der Klasse Ball die Getter- und Setter-Methoden der Attribute x und y (und alle anderen Getter- und Setter-Methoden) einfach aus dem Prototype-Objekt gelöscht werden. Am Verhalten der Klasse Ball würde das nichts ändern, da die Methoden reset und move nun direkt auf die Zustandsvariablen this.x und this.y zugreifen würden. Der zugehörige Code wäre sogar etwas effizienter.

Kommentieren Sie in der Musterlösung oder in Ihrer finalen Lösung doch einfach mal ein paar Getter- und Setter-Methoden aus und starten Sie die Anwendung erneut. Anschließend sollten Sie die Methoden wieder aktivieren und testweise einmal einen Log-Befehl einfügen:

set x(p_x) { this.v_x = p_x; console.log("Setter 'x', param: " + p_x); },

Wenn Sie jetzt das die Anwendung ausführen, wird jede Änderung der x-Position des Balls im Konsolfenster ausgegeben. Ohne echte Setter-Methode hätten Sie diesen Logging-Befehl an jede Stelle, an der die Ballposition geändert wird, einfügen mussen. Oder Sie hätten an jeder dieser Stellen ...ball.x = ... durch ...ball.setX(...) ersetzen müssen.

paddle.js

Die Klasse Paddle wird analog zur Klasse Ball definiert. Die benötigten Attribute und MEthoden findet man im obigen Klassendiagramm.

Die Methoden-Definitionen von reset, move und draw werden (wie bei der Klasse Ball aus) der Lösung des dritten Teils des Tutoriums übernommen. Bei der Definition der Methode reset sollte analog zur Klasse Ball auf der Zugriff auf globale Konstanten (PADDLE_X und PADDLE_Y) durch den Zugriff auf lokale Zustandsvariablen (this.v_x_start und this.v_y_start) ersetzt werden. Dies Variablen sollten (wie in der Klasse Ball) von Konstruktor initialisiert werden.

Zusätzlich werden drei weitere Methoden benötigt:

/** Makes the paddle move left (if it is currently not moving). */  
startLeft:  function(){ if (this.vx == 0) this.vx = -this.v_vx_start; },

/** Makes the paddle move right (if it is currently not moving). */  
startRight: function(){ if (this.vx == 0) this.vx = +this.v_vx_start; },

/** Makes the paddle stop moving. */  
stop:       function(){ this.vx = 0; },

Die Attribute werden genauso definiert wie in der Klasse Ball: Entweder gar nicht oder mittels echten Setter- und Getter-Methoden. Vier Atribute wurden allerdings im Diagramm als nur lesbar (read only, frozen) deklariert: left, right, top und bottom. Diese Attribute dürfen also nicht geändert, sondern nur gelesen werden, da die Werte aus anderen Attributen berechnet werden. Daher können die Werte auch nicht in Zustandsvariablen gespeichert werden. Allerdings kann man Read-only-Attribute mit Hilfe echter Getter-Methoden recht elegant realisieren:

/** The left side of the paddle (read only). */
get left() { return this.x; },

/** The right side of the paddle (read only). */
get right() { return this.x + this.width; },

/** The top side of the paddle (read only). */
get top() { return this.y;  },

/** The bottom side of the paddle (read only). */
get bottom() { return this.y + this.height; },

Auf diese Attribute kann ebenfalls mit der üblichen Attribut-Syntax zugegriffen werden:

var my_paddle = new Paddle();

console.log(my_paddle.left); // Aufruf der Getter-Methode

Da keine Setter-Methoden existieren, können diese Attribute nicht geändert werden. Falls man seinen JavaScript-Code mittels "use strict"; als „strikt“ dekalriert hat, resultiert folgende Anweisung in einer Fehlermeldung:

my_paddle.left = 30; // Aufruf der nicht vorhandenen Setter-Methode

main.js

TO BE DONE

Weitere Verbesserungsmöglichkeiten

Bei der Realisierung der Anwendung MiniPongCanvas04 wurden wichtige Programmierprinzipien nur eingeschränkt beachtet:
  ★★★★☆ Verständlichkeit/Lesbarkeit: sehr gut
  ★★★★☆ Stetigkeit: sehr gut
  ★★★★☆ Konfigurierbarkeit: sehr gut
  ★★★☆☆ DRY: wenige Wiederholungen
  ★★★★★ Gesetz von Demeter: wurde beachtet
  ★★☆☆☆ Überprüfbarkeit: eine simple formale Spezifikation ist vorhanden
  ★★★☆☆ Modularität: modular (die wesentlichsten Regeln wurden beachet)

<ul><li>„6“ befindet sich nicht in der Liste (0, 1, 2, 3, 4, 5) zulässiger Werte für das Attribut „Codequalität:Schreibbarkeit“.</li> <!--br--><li>„6“ befindet sich nicht in der Liste (0, 1, 2, 3, 4, 5) zulässiger Werte für das Attribut „Codequalität:Interfaces“.</li> <!--br--><li>„6“ befindet sich nicht in der Liste (0, 1, 2, 3, 4, 5) zulässiger Werte für das Attribut „Codequalität:Integritätsbedingungen“.</li> <!--br--><li>„6“ befindet sich nicht in der Liste (0, 1, 2, 3, 4, 5) zulässiger Werte für das Attribut „Codequalität:Ersetzbarkeitsprinzip“.</li></ul>

Das Prinzip der „Verständlichkeit/Lesbarkeit

TO BE DONE

Das Prinzip der „Schreibbarkeit

TO BE DONE

Das Prinzip der „Stetigkeit

TO BE DONE

Das Prinzip der „Konfigurierbarkeit

TO BE DONE

Das Prinzip „Don't repeat yourself

TO BE DONE

Das „Gesetz von Demeter

TO BE DONE

Das Prinzip der „Überprüfbarkeit

TO BE DONE

Das Prinzip „Design by Contract

TO BE DONE

Das „Liskovsche Substitutionsprinzip

TO BE DONE

Das Prinzip der „Modularität

TO BE DONE

Quellen

  1. vgl. Meyer (1997): Bertrand Meyer; Object-oriented Software Construction; Auflage: 2; Verlag: Prentice Hall International; ISBN: 0136291554; 1997; Quellengüte: 5 (Buch)
  1. Braun (2011): Herbert Braun; Webanimationen mit Canvas; in: c't Webdesign; Band: 2011; Seite(n): 44–48; Verlag: Heise Zeitschriften Verlag; Adresse: Hannover; 2011; Quellengüte: 5 (Artikel)
  2. Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)

Fortsetzung des Tutoriums

Sie sollten nun das Hello-World-SVG-Tutorium bearbeiten, sofern Sie dies noch nicht gemacht haben. Anderenfalls können Sie gleich mit dem Minipong-SVG-Tutorium forfahren.