我正在管理小型产品线的用户界面。所有这些产品都是或多或少具有相同模块的硬件,但作为一个单元本身,可能具有额外的全局功能。这些全局功能与这些模块交互,通常为多个模块提供相同的功能。我正在努力维护具有逻辑类继承的OOP方法,但有时似乎不可能不必复制粘贴某些功能。
在这个例子中,我用LightSwitch
来说明这个例子。 产品A 和产品B 都可以有LightSwitch
。 产品B 也有AlarmSwitch
。 产品B LightSwitch
和AlarmSwitch
的实施还需要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;
}
}
答案 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 B
和AlarmSwitch
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; }