重新构建基于mixin的类层次结构 [英] Refactoring legacy mixin-based class hierarchies
问题描述
// Base.js
var Base = compose({
setX:function(x) {
this.x = x;
},
setY:function(y){
this.y = y;
},
setPosition:function(x,y){
this.setX(x);
this.setY(y);
}
})
// SameXAndY.js - mixin
var SameXAndY = compose({
//在Base.js中执行setX之后
setX:compose.after(function(x) {
this.y = x;
}),
//在Base.js中执行setY后执行
setY:compose.after(function(y){
this.x = y;
}),
//覆盖Base.js中的setPosition
setPosition:compose.around(function(base){
return function(x,y){
if(x!== y){
throw'x!== y';
}
retu rn base.call(this,x,y);
}
})
})
我们有以下这个解决方案的问题:
- Mixins在很大程度上依赖于彼此 - 你可以通过改变基类中的mixins的顺序来破解某些东西。 >
- 没有一个简单的方法可以确保您可以安全地在课程中包含一些mixin,您可能需要实现其他方法/包含其他mixins。
- 几乎不可能在Flow或Typescript中重写mixin。
我正在寻找更好的插件式替代方案,允许逐渐重构所有现有的mixins,具有以下要求:
- 能够明确描述所有依赖关系(即某种方式描述
PluginA
需要PluginB
和PluginC
)。 - 插件不应该污染目标类机智
- 他们应该能够以某种方式截取基类逻辑(如
SameXAndY
)。 - 插件应该是简单的js类。
我明白我的问题没有简单的答案,我真的很想听听你对这个话题的想法。
干杯,
弗拉基米尔。
我重构了OP的例子,它是要求的。
如果我有问题,请通知我。
class BaseXYType {constructor(stateValue){//注入状态对象。 this.setX = function setX(x){return(stateValue.x = x); }; this.setY = function setY(y){return(stateValue.y = y); }; Object.defineProperty(this,x,{get:function getX(){return stateValue.x;},enumerable:true}); Object.defineProperty(this,y,{get:function getY(){return stateValue.y;},enumerable:true}); Object.defineProperty(this,'valueOf',{value:function valueOf(){return Object.assign({},stateValue);}}); Object.defineProperty(this,'toString',{value:function toString(){return JSON.stringify(stateValue);}}); } setPosition(x,y){// prototypal方法。 this.setX(X); this.setY(Y); return this.valueOf(); }}类SameXYType扩展BaseXYType {// - 这种方法不会在抽象类级别混合类和基于特征的组合//。 JavaScript中的特征应该适用//仅仅是特征和对象组合//规则容器的类型/对象,这些规则将专门在特征的应用时间执行。构造函数(stateValue){//注入状态对象。超级(stateValue); // - 应用trait特定行为withSameInternalXAndYState.call(this,stateValue); //在实例级的施工时间。 }} var withSameInternalXAndYState = Trait.create(function(use,Applicator){//本地函数为了启用共享代码,从而实现更少的内存消耗//函数afterReturningStateChangeXHandler(returnValue,argsArray,payloadList){//console.log (after :: setX - [this,returnValue,argsArray,payloadList]:,this,returnValue,argsArray,payloadList); var stateValue = payloadList [0]; stateValue.y = argsArray [0]; // same y from x。} function afterReturningStateChangeYHandler(returnValue,argsArray,payloadList){//console.log(\"after::setY - [this,returnValue,argsArray,payloadList]:,this,returnValue,argsArray,payloadList); var stateValue = payloadList [0]; stateValue.x = argsArray [0]; //与y相同的x。} function setPositionInterceptor(proceedSetPosition,interceptor,argsArray,payloadList){var x = argsArray [0],y = argsArray [1]; if x!== y){throw(new TypeError([x,!==,y] .join()));} r eturn proceedSetPosition.call(this,x,y); // return proceedSetPosition.apply(this,argsArray); } appl(function sameXAndYBehavior(stateValue){//注入(ed)有效的状态特征//它可以被每个修饰符的回调访问// //通过其总是最后一个payloadList参数在//方法中没有额外的特征特定行为。 //应用程序...但是相当好的数量//特征特定的方法修改// console.log(withSameXAndYState :: appl - [this,stateValue]:,this,stateValue);})。 (setX,setX,setPosition])。afterReturning(setX,afterReturningStateChangeXHandler).afterReturning(setY,afterReturningStateChangeYHandler).around(setPosition,setPositionInterceptor);}); var base_1 = new BaseXYType({x:7,y:11}),base_2 = new BaseXYType({x:99,y:1}),same_1 = new SameXYType({x:13,y:5}),same_2 = new SameXYType {x:99,y:1}); console.log('(+ base_1):',(+ base_1)); console.log (+ base_2)); console.log('(+ same_1)); :',(+ same_2)); console.log('base_1.valueOf():',base_1.valueOf()); console.log('base_2.valueOf():',base_2.valueOf()) ; console.log('same_1.valueOf():',same_1.valueOf()); console.log('same_2.valueOf():',same_2.valueOf()); console.log('base_1.x: ',base_1.x); console.log('(base_1.x =foo):',(base_1.x =foo)); console.log('base_1.x:',base_1.x) ; console.log('base_1.y:',base_1.y); console.log('(base_1.y =bar):',(base_1.y =bar)); console.log(' base_1.y:',base_1.y); console.log('same_2.x:',same_2.x); console.log('(same_2.x =biz):',(same_2.x = biz)); console.log('same_2.x:',same_2.x); console.log('same_2.y:',same_2.y); console.log('(same_2.y =baz ):',(same_2.y =baz)); console.log('same_2.y:',same_2.y); console.log('base_1.setY(foo):',base_1.setY ( 富)); console.log('base_1.y:',base_1.y); console.log('base_2.setX(bar):',base_2.setX(bar)); console.log('base_2.x :',base_2.x); console.log('base_1.setPosition(brown,fox):',base_1.setPosition(brown,fox)); console.log(' + base_1):',(+ base_1)); console.log('base_2.setPosition(lazy,dog):',base_2.setPosition(lazy,dog));控制台。 log('(+ base_2):',(+ base_2)); console.log('same_1.setY(543):',same_1.setY(543)); console.log('same_1.x :',same_1.x); console.log('same_1.y:',same_1.y); console.log('same_1.valueOf():',same_1.valueOf()); console.log('same_2 .setY(79):',same_2.setY(79)); console.log('same_2.x:',same_2.x); console.log('same_2.y:',same_2.y);控制台。 log('same_2.valueOf():',same_2.valueOf()); console.log('same_1.setPosition(77,77):',same_1.setPosition(77,77)); console.log('( + same_1):',(+ same_1)); console.log('same_2.setPosition(42,42):',sam e_2.setPosition(42,42)); console.log('(+ same_2):',(+ same_2)); console.log('same_1.setPosition(apple,pear ',same_1.setPosition(apple,pear)); console.log('(+ same_1):',(+ same_1)); console.log('same_1.setPosition(apple ,pear):',same_1.setPosition(prune,prune)); console.log('(+ same_1):',(+ same_1));
.as-console-wrapper {max-height:100%!顶部:0; }
< script type =text / javascriptsrc = http://yourjavascript.com/30132320357/esx-trait.js >< /脚本>
I'm currently working on a huge javascript project which has a huge class hierarchy and heavily uses mixins to extend functionality of base classes. Here is an example of how mixin looks like, we're using compose library to create class-like objects:
// Base.js
var Base = compose({
setX: function (x) {
this.x = x;
},
setY: function (y) {
this.y = y;
},
setPosition: function (x, y) {
this.setX(x);
this.setY(y);
}
})
// SameXAndY.js - mixin
var SameXAndY = compose({
// Executes after setX in Base.js
setX: compose.after(function (x) {
this.y = x;
}),
// Executes after setY in Base.js
setY: compose.after(function (y) {
this.x = y;
}),
// Overrides setPosition in Base.js
setPosition: compose.around(function (base) {
return function (x, y) {
if (x !== y) {
throw 'x !== y';
}
return base.call(this, x, y);
}
})
})
We have the following problems with this solution:
- Mixins heavily depend on each other - you can break something by changing mixins' order in base classes.
- There is no easy way to ensure that you can safely include some mixin in your class, you may need to implement additional methods / include additional mixins into it.
- Child classes have hundreds of methods because of the various mixins.
- It's almost impossible to rewrite mixin in Flow or Typescript.
I'm looking for better plugin-like alternatives which allow to gradually refactor all existing mixins with the following requirements:
- Ability to explicitly describe all dependencies (i.e. somehow describe that
PluginA
requiresPluginB
andPluginC
). - Plugin should not pollute target class with its methods.
- They should be able to somehow intercept Base class logic (like in
SameXAndY
). - Plugins should be plain js classes.
I understand that there is no "easy" answer to my question, but I would really love to hear your thought on this topic. Design pattern names, relevant blog posts, or even links to the source code will be greatly appreciated.
Cheers, Vladimir.
I did refactor the OP's example, and it does what was ask for. Let me know if I got something wrong.
class BaseXYType {
constructor(stateValue) { // injected state object.
this.setX = function setX (x) {
return (stateValue.x = x);
};
this.setY = function setY (y) {
return (stateValue.y = y);
};
Object.defineProperty(this, "x", {
get: function getX () {
return stateValue.x;
},
enumerable: true
});
Object.defineProperty(this, "y", {
get: function getY () {
return stateValue.y;
},
enumerable: true
});
Object.defineProperty(this, 'valueOf', {
value: function valueOf () {
return Object.assign({}, stateValue);
}
});
Object.defineProperty(this, 'toString', {
value: function toString () {
return JSON.stringify(stateValue);
}
});
}
setPosition(x, y) { // prototypal method.
this.setX(x);
this.setY(y);
return this.valueOf();
}
}
class SameXYType extends BaseXYType {
// - this approach does not intermingle classes and trait based composition
// at an abstract class level. Traits in JavaScript should be applicable
// types/objects that are just containers of trait and object composition
// rules which exclusively will be executed at a trait's apply time.
constructor(stateValue) { // injected state object.
super(stateValue);
// - apply trait specific behavior
withSameInternalXAndYState.call(this, stateValue); // at construction time at instance level.
}
}
var withSameInternalXAndYState = Trait.create(function (use, applicator) {
// local functions in order to enable shared code, thus achieving less memory consumption.
//
function afterReturningStateChangeXHandler(returnValue, argsArray, payloadList) {
//console.log("after::setX - [this, returnValue, argsArray, payloadList] : ", this, returnValue, argsArray, payloadList);
var
stateValue = payloadList[0];
stateValue.y = argsArray[0]; // same y from x.
}
function afterReturningStateChangeYHandler(returnValue, argsArray, payloadList) {
//console.log("after::setY - [this, returnValue, argsArray, payloadList] : ", this, returnValue, argsArray, payloadList);
var
stateValue = payloadList[0];
stateValue.x = argsArray[0]; // same x from y.
}
function setPositionInterceptor(proceedSetPosition, interceptor, argsArray, payloadList) {
var
x = argsArray[0],
y = argsArray[1];
if (x !== y) {
throw (new TypeError([x, "!==", y].join(" ")));
}
return proceedSetPosition.call(this, x, y);
//return proceedSetPosition.apply(this, argsArray);
}
applicator(function sameXAndYBehavior (stateValue) { // inject(ed) payload for stateful traits.
// it can be accessed by each modifier's callback
// no additional trait specific behavior within // method via its always last `payloadList` argument.
// the applicator ... but a fairly good amount
// of of trait specific method modifications.
//
console.log("withSameXAndYState::applicator - [this, stateValue] : ", this, stateValue);
}).requires([
"setX",
"setY",
"setPosition"
]).afterReturning(
"setX", afterReturningStateChangeXHandler
).afterReturning(
"setY", afterReturningStateChangeYHandler
).around(
"setPosition", setPositionInterceptor
);
});
var
base_1 = new BaseXYType({ x: 7, y: 11 }),
base_2 = new BaseXYType({ x: 99, y: 1 }),
same_1 = new SameXYType({ x: 13, y: 5 }),
same_2 = new SameXYType({ x: 99, y: 1 });
console.log('("" + base_1) : ', ("" + base_1));
console.log('("" + base_2) : ', ("" + base_2));
console.log('("" + same_1) : ', ("" + same_1));
console.log('("" + same_2) : ', ("" + same_2));
console.log('base_1.valueOf() : ', base_1.valueOf());
console.log('base_2.valueOf() : ', base_2.valueOf());
console.log('same_1.valueOf() : ', same_1.valueOf());
console.log('same_2.valueOf() : ', same_2.valueOf());
console.log('base_1.x : ', base_1.x);
console.log('(base_1.x = "foo") : ', (base_1.x = "foo"));
console.log('base_1.x : ', base_1.x);
console.log('base_1.y : ', base_1.y);
console.log('(base_1.y = "bar") : ', (base_1.y = "bar"));
console.log('base_1.y : ', base_1.y);
console.log('same_2.x : ', same_2.x);
console.log('(same_2.x = "biz") : ', (same_2.x = "biz"));
console.log('same_2.x : ', same_2.x);
console.log('same_2.y : ', same_2.y);
console.log('(same_2.y = "baz") : ', (same_2.y = "baz"));
console.log('same_2.y : ', same_2.y);
console.log('base_1.setY("foo") : ', base_1.setY("foo"));
console.log('base_1.y : ', base_1.y);
console.log('base_2.setX("bar") : ', base_2.setX("bar"));
console.log('base_2.x : ', base_2.x);
console.log('base_1.setPosition("brown", "fox") : ', base_1.setPosition("brown", "fox"));
console.log('("" + base_1) : ', ("" + base_1));
console.log('base_2.setPosition("lazy", "dog") : ', base_2.setPosition("lazy", "dog"));
console.log('("" + base_2) : ', ("" + base_2));
console.log('same_1.setY(543) : ', same_1.setY(543));
console.log('same_1.x : ', same_1.x);
console.log('same_1.y : ', same_1.y);
console.log('same_1.valueOf() : ', same_1.valueOf());
console.log('same_2.setY(79) : ', same_2.setY(79));
console.log('same_2.x : ', same_2.x);
console.log('same_2.y : ', same_2.y);
console.log('same_2.valueOf() : ', same_2.valueOf());
console.log('same_1.setPosition(77, 77) : ', same_1.setPosition(77, 77));
console.log('("" + same_1) : ', ("" + same_1));
console.log('same_2.setPosition(42, 42") : ', same_2.setPosition(42, 42));
console.log('("" + same_2) : ', ("" + same_2));
console.log('same_1.setPosition("apple", "pear") : ', same_1.setPosition("apple", "pear"));
console.log('("" + same_1) : ', ("" + same_1));
console.log('same_1.setPosition("apple", "pear") : ', same_1.setPosition("prune", "prune"));
console.log('("" + same_1) : ', ("" + same_1));
.as-console-wrapper { max-height: 100%!important; top: 0; }
<script type="text/javascript" src="http://yourjavascript.com/30132320357/esx-trait.js"></script>
这篇关于重新构建基于mixin的类层次结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!