How to add a new class and refactor from Class-inheritance to the Behavior Delegation design pattern?

Viewed 127

Here i build a simple code using class-inheritance pattern (I took it from my game made just for trainig) :

class BasicGamePiece {
    constructor(param) {
        this.size = param.size  
    }
    fire(){
        console.log('fire')
    }
}

// first and second children . Got different methods move() and attachTo() plus different properties 
class MovingWeaponPiece extends BasicGamePiece {
    constructor(param) {
        super(param)
        this.movementPoints = param.movementPoints
    }
    move(){
        this.movementPoints -= 1
    }
}

class StaticWeaponPiece extends BasicGamePiece {
    constructor(param) {
        super(param)
        this.owner = param.owner
    }
    attachTo(owner){
        this.owner = owner
    }
}

// now children of MovingWeaponPiece

class Squad extends MovingWeaponPiece {
    constructor(param) {
        super(param)

    }
    getUnderCommand(){
        console.log('ready to action')
    }
    fire(){                         // shadowing
        super.fire()
        console.log('as squad')
    }
}

class Vehicle extends MovingWeaponPiece {
    constructor(param) {
        super(param)
    }
    ignition(){
        console.log('at the start!')
    }
}
// and the third step of inheritance :

class Tank extends Vehicle {
    constructor(param) {
        super(param)
        this.orientation = param.orientation
    }
    rotateArmament(where){
        this.orientation = where
    }
}

For clarity i made this scheme : scheme

Now i want to add another class Artyllery :

It MUST HAVE method attachTo() (same as class Weapon) and property this.owner (same as class Weapon)

It MUST HAVE rotateArmament() (same as class Tank) and property this.orientation (same as class Tank)

It SHOULD NOT HAVE Vehicle-like methods ignition()

So questions are :

1) How should i correctly add class Artyllery ?

and

2) What if i want to refactor all my code from class-inheritance pattern, to the Behavior Delegation design pattern (same as in the Kyle Simpson's book 'You don't know Java Script').

Is it will be easier to add Artillery in this case ?

I would be grateful if someone show me the refactored code

1 Answers

Classical OOP

The standard thinking in classical object-oriented languages is to prefer flat to deep class structures. As the famous mantra goes: "Prefer object composition to class inheritance." That is, it is better to have a bunch of objects which hold references to each other, rather than a deep inheritance structure. Why? Well... I think your example demonstrates well why object composition is preferred.

So instead of having a moving piece and a static piece, you can have a class position which manages coordinates, movement points etc... Pieces that are movable will have a member of type position and pieces that are static won't. We can re-implement your example using composition:


class Position {

    constructor(movementPoints) {
        this.movementPoints = movementPoints;
    }

    move() {
        this.movementPoints -= 1
    }

}

class Armament {

    constructor(orientation) {
        this.orientation = orientation;
    }

    rotate(where) {
        this.orientation = orientation;
    }

}

class Ownership {

    constructor(owner) {
        this.owner = owner
    }

    set(owner) {
        this.owner = owner
    }

}

class Artillery {

    constructor(config) {
        this.position = new Position(config.movementPoints);
        this.armament = new Armament(config.orientation);
        this.ownership = new Ownership(config.owner);
    }

}

let artillery = new Artillery({
    movementPoints: 4,
    orientation: 3,
    owner: player
});

artillery.position.move(4);
artillery.armament.orient(3);
artillery.ownership.set(otherPlayer);

As you can see having small classes each responsible for a different functionality and combining them together to make something new is quite powerful. This doesn't mean you should never use inheritance (having a class Ownership is a bit weird in my opinion), but you should in general perfer composition where you can. There are other tools OOP gives you to solve similar problems - multiple inheritance, mixins, interfaces etc.

Javascript

Javascript behaves quite differently from classical object-oriented programming languages, as it doesn't have static typing. You can use classes and define members but ultimately javascript doesn't care how you've defined your objects. You can add or remove properties when the freak takes you. Because of that, javascript is very flexible. There are a lot of patterns to chose from and you would have to find the ones that best suit your style.

Behavioral Delegation is in general how the prototype chain works in javascript. If a property is not found on the object, the whole prototype chain is traversed; thus "delegating" the behavior to some other object. Thus you will have something like this.


let Positionable = {
    move() {
        this.movementPoints -= 1;
    }
}

let Attachable = {
    attachTo(owner) {
        this.owner = owner;
    }
}

Then you would use Positionable and Attachable as prototypes of other objects.


let Tank = Object.create( Positionable );

Full disclosure I'm not very familiar with 'You Don't Know JS', and looking through it I couldn't find how to Kyle Simpson recommends handling situations in which objects have to be reused to construct different prototype chains (I could have missed something). One option would be to simply construct a different chain prototype chain for Tank and Artillery.

Tank --> Positionable --> Object
Artillery --> Attachable --> Positionable --> Object

But I don't believe this to be a particularly good solution. What I would do is use Object.assign to combine objects together.

let Tank = Object.assign({
    // stuff specific to tank
}, Positionable);

let Artillery = Object.assign({
    // stuff specific to artillery
}, Positionable, Attachable);

let tank1 = Object.assign({
    movementPoints: 1
}, Tank);

let artillery1 = Object.assign({
    movementPoints: 1,
    owner: player
}, Artillery);

That way you can combine objects however you like and achieve quite flexible code.

Well, those are my two cents on the matter. I hope it was useful.

Related