项目之间的类继承

时间:2017-04-27 12:53:40

标签: javascript oop

我正在管理小型产品线的用户界面。所有这些产品都是或多或少具有相同模块的硬件,但作为一个单元本身,可能具有额外的全局功能。这些全局功能与这些模块交互,通常为多个模块提供相同的功能。我正在努力维护具有逻辑类继承的OOP方法,但有时似乎不可能不必复制粘贴某些功能。

在这个例子中,我用LightSwitch来说明这个例子。 产品A 产品B 都可以有LightSwitch产品B 也有AlarmSwitch产品B LightSwitchAlarmSwitch的实施还需要timer 完全相同的功能产品A 不支持计时器。

我在这里看到的唯一解决方案是跨模块复制粘贴。在没有破坏模型的情况下,我想不出任何其他方法来扩展它。

// Common components

class Switch {
  constructor() {
    this.state = false;
  }
  open() {
    this.state = true;
  }
  close() {
    this.state = false;
  }
}

class LightSwitch extends Switch {
  constructor() {
    super();
    this.colour = "white";
  }
  changeColour(colour) {
    this.colour = colour;
  }
}

// Product A 

class LedSwitch extends LightSwitch {
  constructor() {
    super();
    this.blink = false;
  }
  blink(state) {
    this.blink = state;
  }
}

// Product B
// AlarmSwitch and ProductBLightSwitch both need a "timer"

class AlarmSwitch extends Switch {
  constructor() {
    super();
    this.sound = "alarm.wav";
    this.timer = 5000;
  }
  changeSound(sound) {
    this.sound = sound;
  }
  resetTimer() {
    this.timer = 0;
  }
}

class ProductBLightSwitch extends LightSwitch {
  costructor() {
    super();
    this.timer = 5000;
  }
  resetTimer() {
    this.timer = 0;
  }
}

1 个答案:

答案 0 :(得分:0)

有人可能会考虑将// Common components重构并分解为(I)基于函数的mixins / traits,这些mixins / traits包含行为,并分解为(II)类型的 hull 类,它们只负责一个类型的签名以及将现有代码粘合在一起(通过组合和继承来重用代码)。同时,人们也可能更关心如何访问/隐藏对象的当前状态。这种方法将通过OP的以下重构示例代码进行演示......

// Common components

var
  withSerializableTypeProxy = (function () {
    function toString(type) {
      return JSON.stringify(type);
    }
    function valueOf(type) {
      return JSON.parse(JSON.stringify(type))
    }
    let defineProperty = Object.defineProperty;

    return function trait (stateValue) {
      const
        compositeType = this;

      defineProperty(compositeType, 'toString', {
        value: (() => toString(stateValue))
      });
      defineProperty(compositeType, 'valueOf', {
        value: (() => valueOf(stateValue))
      });
    };
  }());


function withChangeableColorProxy(stateValue) {
  const
    compositeType = this;

  compositeType.changeColor = ((colorValue) => stateValue.color = colorValue);

  Object.defineProperty(compositeType, 'color', {
    enumerable: true,
    get: (() => stateValue.color)
  });
  stateValue.color = 'white'; // set default value.
}


class Switch {
  constructor(stateValue) {

    stateValue = ((stateValue != null) && (typeof stateValue == 'object') && stateValue) || {};
    stateValue.isOpen = true;

    const
      switchType = this;

    withSerializableTypeProxy.call(switchType, stateValue);

    switchType.open = (() => stateValue.isOpen = true);
    switchType.close = (() => stateValue.isOpen = false);

    Object.defineProperty(this, 'isOpen', {
      enumerable: true,
      get: (() => stateValue.isOpen)
    });
  }
}


class LightSwitch extends Switch {
  constructor(stateValue) {
    stateValue = ((stateValue != null) && (typeof stateValue == 'object') && stateValue) || {};

    super(stateValue);

    withChangeableColorProxy.call(this, stateValue);
  }
}


var
  lightSwitch = (new LightSwitch);

console.log("lightSwitch : ", lightSwitch);

console.log("(lightSwitch instanceof LightSwitch) ? ", (lightSwitch instanceof LightSwitch));
console.log("(lightSwitch instanceof Switch) ? ", (lightSwitch instanceof Switch));

console.log("(lightSwitch + '') : ", (lightSwitch + ''));
console.log("lightSwitch.toString() : ", lightSwitch.toString());
console.log("lightSwitch.valueOf() : ", lightSwitch.valueOf());

console.log("Object.keys(lightSwitch) : ", Object.keys(lightSwitch));

console.log("lightSwitch.isOpen : ", lightSwitch.isOpen);
console.log("lightSwitch.close() : ", lightSwitch.close());
console.log("lightSwitch.isOpen : ", lightSwitch.isOpen);
console.log("lightSwitch.open() : ", lightSwitch.open());
console.log("lightSwitch.isOpen : ", lightSwitch.isOpen);

console.log("lightSwitch.color : ", lightSwitch.color);
console.log("lightSwitch.changeColor('pink') : ", lightSwitch.changeColor('pink'));
console.log("lightSwitch.color : ", lightSwitch.color);
console.log("lightSwitch.changeColor('light-blue') : ", lightSwitch.changeColor('light-blue'));
console.log("lightSwitch.color : ", lightSwitch.color);
.as-console-wrapper { max-height: 100%!important; top: 0; }

然后可以使用// Product A示例对LedSwitch重复应用同样的方法,最后对// Product BAlarmSwitch ProductBLightSwitch的两个示例应用两者都是OP问题的真正目标。最后的结果可能看起来像......

// Common components

var
  withSerializableTypeProxy = (function () {
    function toString(type) {
      return JSON.stringify(type);
    }
    function valueOf(type) {
      return JSON.parse(JSON.stringify(type))
    }
    let defineProperty = Object.defineProperty;

    return function trait (stateValue) {
      const
        compositeType = this;

      defineProperty(compositeType, 'toString', {
        value: (() => toString(stateValue))
      });
      defineProperty(compositeType, 'valueOf', {
        value: (() => valueOf(stateValue))
      });
    };
  }());


function withChangeableColorProxy(stateValue) {
  const
    compositeType = this;

  compositeType.changeColor = ((colorValue) => stateValue.color = colorValue);

  Object.defineProperty(compositeType, 'color', {
    enumerable: true,
    get: (() => stateValue.color)
  });
  stateValue.color = 'white'; // set default value.
}


class Switch {
  constructor(stateValue) {

    stateValue = ((stateValue != null) && (typeof stateValue == 'object') && stateValue) || {};
    stateValue.isOpen = true;

    const
      switchType = this;

    withSerializableTypeProxy.call(switchType, stateValue);

    switchType.open = (() => stateValue.isOpen = true);
    switchType.close = (() => stateValue.isOpen = false);

    Object.defineProperty(this, 'isOpen', {
      enumerable: true,
      get: (() => stateValue.isOpen)
    });
  }
}


class LightSwitch extends Switch {
  constructor(stateValue) {
    stateValue = ((stateValue != null) && (typeof stateValue == 'object') && stateValue) || {};

    super(stateValue);

    withChangeableColorProxy.call(this, stateValue);
  }
}


// Product A

var
  withBlinkBehaviorProxy = (function () {
    function setBlinkValue(stateValue, blinkValue) {
      return stateValue.blink = blinkValue;
    }

    return function trait (stateValue) {
      const
        compositeType = this;

      Object.defineProperty(compositeType, 'blink', {
        enumerable: true,
        get: (() => stateValue.blink),
        set: ((blinkValue) => setBlinkValue(stateValue, blinkValue))
      });
      setBlinkValue(stateValue, false); // set default value.
    };
  }());


class LedSwitch extends LightSwitch {
  constructor(stateValue) {
    stateValue = ((stateValue != null) && (typeof stateValue == 'object') && stateValue) || {};

    super(stateValue);

    withBlinkBehaviorProxy.call(this, stateValue);
  }
}


var
  ledSwitch = (new LedSwitch);

console.log("ledSwitch : ", ledSwitch);

console.log("(ledSwitch instanceof LedSwitch) ? ", (ledSwitch instanceof LedSwitch));
console.log("(ledSwitch instanceof LightSwitch) ? ", (ledSwitch instanceof LightSwitch));
console.log("(ledSwitch instanceof Switch) ? ", (ledSwitch instanceof Switch));

console.log("ledSwitch.valueOf() : ", ledSwitch.valueOf());
console.log("Object.keys(ledSwitch) : ", Object.keys(ledSwitch));

console.log("(ledSwitch.blink = 'blink') : ", (ledSwitch.blink = 'blink'));
console.log("ledSwitch.blink : ", ledSwitch.blink);
console.log("(ledSwitch.blink = true) : ", (ledSwitch.blink = true));
console.log("ledSwitch.blink : ", ledSwitch.blink);


// Product B
// AlarmSwitch and ProductBLightSwitch both need a "timer"

function withPlayerBehaviorProxy(stateValue) {
  const
    compositeType = this;

  compositeType.changeSound = ((soundValue) => stateValue.sound = soundValue);

  Object.defineProperty(compositeType, 'sound', {
    enumerable: true,
    get: (() => stateValue.sound)
  });
  stateValue.sound = 'alarm.wav'; // set default value.
}

function withTimerBehaviorProxy(stateValue) {
  const
    compositeType = this;

  compositeType.resetTimer = (() => stateValue.timer = 0);

  Object.defineProperty(compositeType, 'timer', {
    enumerable: true,
    get: (() => stateValue.timer)
  });
  stateValue.timer = 5000; // set default value.
}


class AlarmSwitch extends Switch {
  constructor(stateValue) {
    stateValue = ((stateValue != null) && (typeof stateValue == 'object') && stateValue) || {};

    super(stateValue);

    withPlayerBehaviorProxy.call(this, stateValue);
    withTimerBehaviorProxy.call(this, stateValue);
  }
}

class ProductBLightSwitch extends LightSwitch {
  constructor(stateValue) {
    stateValue = ((stateValue != null) && (typeof stateValue == 'object') && stateValue) || {};

    super(stateValue);

    withTimerBehaviorProxy.call(this, stateValue);
  }
}


var
  lightSwitch_B = (new ProductBLightSwitch),
  alarmSwitch = (new AlarmSwitch);

console.log("lightSwitch_B : ", lightSwitch_B);
console.log("alarmSwitch : ", alarmSwitch);

console.log("(lightSwitch_B instanceof ProductBLightSwitch) ? ", (lightSwitch_B instanceof ProductBLightSwitch));
console.log("(alarmSwitch instanceof AlarmSwitch) ? ", (alarmSwitch instanceof AlarmSwitch));

console.log("(lightSwitch_B instanceof LightSwitch) ? ", (lightSwitch_B instanceof LightSwitch));
console.log("(alarmSwitch instanceof LightSwitch) ? ", (alarmSwitch instanceof LightSwitch));

console.log("(lightSwitch_B instanceof Switch) ? ", (lightSwitch_B instanceof Switch));
console.log("(alarmSwitch instanceof Switch) ? ", (alarmSwitch instanceof Switch));

console.log("lightSwitch_B.valueOf() : ", lightSwitch_B.valueOf());
console.log("alarmSwitch.valueOf() : ", alarmSwitch.valueOf());

console.log("Object.keys(lightSwitch_B) : ", Object.keys(lightSwitch_B));
console.log("Object.keys(alarmSwitch) : ", Object.keys(alarmSwitch));

console.log("lightSwitch_B.resetTimer() : ", lightSwitch_B.resetTimer());
console.log("lightSwitch_B.timer : ", lightSwitch_B.timer);

console.log("alarmSwitch.changeSound('ringtone.wav') : ", alarmSwitch.changeSound('ringtone.wav'));
console.log("alarmSwitch.sound : ", alarmSwitch.sound);
.as-console-wrapper { max-height: 100%!important; top: 0; }