Feb 17 2010

Class Resource

Tag: EntwicklungPhoscur @ 00:40

Ich kam noch nie dazu hier einen Codefetzen vorzustellen (außer des Beitrags zum Dekorierer). Nun nutze ich diese Gelegenheit eine meiner ersten JavaScript Klassen zu besprechen.

Gleichzeitig handelt es sich um das Muster Wertobjekt (ValueObject), welches unveränderlich ist.

Vorweg muss noch gesagt werden, dass alle Attribute als Konvention protected gelten, public wird nicht benötigt und private lässt sich über Closures realisieren. Einfache Attribute tragen zudem den Anfangsbuchstaben ihres Typs (z.B. s für String).

Objekte der Klasse stellen eine Menge eines Rohstoffs dar.

/**
 * Represents an amount of a resource
 * @param {number} amount
 * @param {string} type
 */
function Resource(amount, type) {
    var nAmount = amount;
    var sType = type;

    if (amount < 0) {
        throw new IllegalArgumentException("amount has to be positive");
    }

    /**
     * @method Resource
     * @return {number} amount of the resource
     */
    this.getAmount = function() {
        return nAmount;
    };

    /**
     * @method Resource
     * @return {string} resource type
     */
    this.getType = function() {
        return sType;
    };
}

/**
 * Addition of two resources produces a new resource with the sum amount
 * the new object uses the old one as prototype
 * @param {Resource} resource
 * @return {Resource} new Resource object
 */
Resource.prototype.plus = function(resource) {
    if (!(resource instanceof Resource && this.getType() == resource.getType())) {
        throw new IllegalArgumentException("resources don't match.");
    }
    var newRes = Object.create(this); // create a new object based on the current one
    // execute the Resource constructor on it
    Resource.call(newRes, this.getAmount() + resource.getAmount(), this.getType());
    return newRes;
};

/**
 * Subtraction of two resources produces a new resource with a smaller amount
 * @param {Resource} resource
 * @return {Resource} new Resource object
 */
Resource.prototype.minus = function(resource) {
    if (!(resource instanceof Resource && this.getType() == resource.getType())) {
        throw new IllegalArgumentException("resources don't match.");
    }
    if (this.getAmount() < resource.getAmount()) {
        throw new IllegalArgumentException("can't substract a higher amount");
    }
    var newRes = Object.create(this);
    Resource.call(newRes, this.getAmount() - resource.getAmount(), this.getType());
    return newRes;
};

Interessant ist vor allem die Stelle, an der dynamisch neue Objekte der selben Klasse zurückgegeben werden (plus() und minus()), ich möchte Resource noch erweitern können und evtl. für die verschiedenen Rohstoffsorten eigene Klassen ableiten, daher sollten diese Methoden dann nicht nach einer mathematischen Operation auf die Klasse Resource zurückschrumpfen. In PHP kann man „new“ einfach einen String übergeben, in Java muss man einen Umweg über eine Factory oder Reflection machen.

In JavaScript bietet es sich an, das momentane Objekt als Prototyp für das neue zu verwenden und dieses durch den Resource Konstruktor zu initialisieren.

Ich weiß noch nicht, ob mir dieses Pattern noch öfter begegnen wird, aber vor allem bei Wertobjekten (ValueObject), die mit Vererbung erweiterbar bleiben sollen ist dies praktisch. Allgemein wenn man ein neues Objekt des selben Typs wie das momentane zurückgeben möchte.

Jetzt kann ich eine Kindklasse ResourceContainer schreiben, die noch Funktionalität für Gewicht und Volumen des Rohstoffs bereitstellt (z.B zum Beladen von Schiffen):

/**
 * stores resources, adds weight and volume to a resource object
 * @inherits Resource
 * @param {Object} amount
 * @param {Object} type
 */
function ResourceContainer(amount, type) {
    Resource.call(this, amount, type); // inherit priviledged Resource methods
}
ResourceContainer.prototype = Object.create(Resource.prototype); // inherit nonpriviledged Resource methods

Resource.DESITY.metal = 7.85;
Resource.WEIGHT.metal = 1000;

/**
 * @return {number} weight of the resoure amount
 */
ResourceContainer.prototype.getWeight = function() {
    return this.getAmount() * Resource.WEIGHT[this.getType()];
};

/**
 * @return {number} volume of the resource amount
 */
ResourceContainer.prototype.getVolume = function() {
    return this.getAmount() * Resource.DENSITY[this.getType()];
};

Ich beginne langsam die Prototypennatur von JavaScript zu verwenden, bin aber immer noch sehr von Klassen von PHP und Java geprägt.