'Switch methods in object using protoype

I have a piece of code that switches the processing used on an object, a, outside of a function test. The function, test, uses properties of a and a.b and a.c:

let a;
let fB = {method: function(o){o.b.count++;return o.b.value}};
let fC = {method: function(o){o.c.count++;return o.c.value}};
a = new class A {value='Method:'; b={value:'B',count:0}; c={value:'C',count:0}};
function test (a,f) {
    console.log(a.value+f.method(a));
}
test(a,fB); // method:B
test(a,fC); // method:C
console.log('Counts:',a.b.count,a.c.count) // Counts: 1 1

I want to write it in a more OO way, something like :

function test (a) {
    console.log(a.value+a.method());
}
class B {
    constructor(){this.val='B';this.count=0}
    method (){this.count++;return this.val}
}
class C {
    constructor(){this.val='C';this.count=0}
    method (){this.count++;return this.val}
}
class A {
    value='Method:';
}

a = new A();
Object.setPrototypeOf(a,new B());
test(a); // method:B
Object.setPrototypeOf(a,new C());
test(a); // method:C
console.log('Counts:',a.b.count,a.c.count) // a.b is undefined

The best I could come up with was :

class B {
    constructor(){this.value='B';this.count=0}
    method (){this.count++;return this.value}
}
class C {
    constructor(){this.value='C';this.count=0}
    method (){this.count++;return this.value}
}
class A {
    value='Method:';
    b=new B();
    c=new C();
    x=this.b;

    switchToB(){this.x = this.b}
    switchToC(){this.x = this.c}
    method(){return this.x.method()}
}

a = new A();

function test (a) {
    console.log(a.value+a.method());
}
test(a); // method:B
a.switchToC();
test(a); // method:C
console.log('Counts:',a.b.count,a.c.count) // Counts: 1 1

I don't much like this, nor an alternative :

class B {
    constructor(a){this.value=a.value;this.val='B';this.count=0}
    method (){this.count++;return this.val}
}
class C {
    constructor(a){this.value=a.value;this.val='C';this.count=0}
    method (){this.count++;return this.val}
}
class A {
    value='Method:';
    b=new BB(this);
    c=new CC(this);
}

a = new A();
test(a.b); // method:B
test(a.c); // method:C
console.log('Counts:',a.b.count,a.c.count) // Counts: 1 1

Is there a better way to do this in JS ?



Solution 1:[1]

Looking at the first OO attempt that you provided, there are these observations:

  • No b or c properties are created on a, yet the testing code needs them.
  • Also the switching mechanism should work with a reference to these b and c properties
  • B and C are look-alikes so that could be generalised to one Counter class whose constructor takes an argument that distinguishes them (determining the value property).
  • We could also think of a more lazy initialisation, where a counter object is only created when a switches to it.
  • There seems to be a relationship between the name "b" and the output "B". Similar between "c" and "C". So one could be derived from the other.

This leads to the following implementation:

class Counter {
    constructor(value) {
        this.value = value.toUpperCase();
        this.count = 0;
    }
    method() {
        this.count++;
        return this.value;
    }
}

class A {
    constructor() {
        this.value = 'Method:'; 
    }
    activateCounter(name) {
        // Create a new counter if not used before
        this[name] ??= new Counter(name);
        // Redirect the method to use this counter's method
        this.method = () => this[name].method();
    }
}

function test(a) {
    console.log(a.value + a.method());
}

let a = new A();
a.activateCounter("b");
test(a); // method:B
a.activateCounter("c");
test(a); // method:C
console.log('Counts:', a.b.count, a.c.count) // Counts: 1 1

Here a.method() should only be called after a counter was activated with a call of a.activateCounter. This is because the latter will (re)define the method method on a.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 trincot