重新构建基于mixin的类层次结构 [英] Refactoring legacy mixin-based class hierarchies

查看:103
本文介绍了重新构建基于mixin的类层次结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究一个巨大的JavaScript项目,它有一个巨大的类层次结构,并大量使用mixins来扩展基类的功能。以下是mixin的一个例子,我们使用撰写图书馆来创建类似类的对象:

  // 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 requires PluginB and PluginC).
  • 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆