重构遗留的基于 mixin 的类层次结构

Posted

技术标签:

【中文标题】重构遗留的基于 mixin 的类层次结构【英文标题】:Refactoring legacy mixin-based class hierarchies 【发布时间】:2017-08-19 00:54:47 【问题描述】:

我目前正在开发一个巨大的 javascript 项目,该项目具有巨大的类层次结构并大量使用 mixin 来扩展基类的功能。下面是一个 mixin 的例子,我们使用 compose library 来创建类对象:

// 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);
    
  )
)

我们对这个解决方案有以下问题:

mixin 严重依赖彼此 - 您可以通过更改 mixin 在基类中的顺序来破坏某些东西。 没有简单的方法可以确保您可以安全地在类中包含一些 mixin,您可能需要实现其他方法/在其中包含其他 mixin。 子类有数百个方法,因为有各种 mixin。 在 Flow 或 Typescript 中重写 mixin 几乎是不可能的。

我正在寻找更好的类似插件的替代方案,允许逐步重构所有现有的 mixin,满足以下要求:

能够显式描述所有依赖项(即以某种方式描述PluginA 需要PluginBPluginC)。 插件不应使用其方法污染目标类。 他们应该能够以某种方式拦截基类逻辑(如在SameXAndY 中)。 插件应该是普通的 js 类。

我知道我的问题没有“简单”的答案,但我真的很想听听您对这个话题的看法。非常感谢设计模式名称、相关博客文章,甚至是源代码链接。

干杯, 弗拉基米尔。

【问题讨论】:

TypeScript 2.2 具有旨在支持 mixins 的功能。 @torazaburo 是的,我在更新日志中看到了这个。但是,由于它们引起的其他问题,我仍然想摆脱 mixins。此外,compose 库不支持正确的 js 类,所以我仍然需要重写所有代码库才能使用 ts mixins。 @pragma ... 1/2 ... 促销警报(有点)... 如果您从上周末开始给我的这个演示文稿,有机会进行 10 到 15 分钟的仔细检查/阅读,您可能会得出结论,演示文稿背后的库确实涵盖了您的每一个需求点。如果它看起来很有希望,我很乐意在这里从技术/架构的角度讨论这个话题...slides.com/petsel/javascript-talents... @pragma ... 2/2 ... 该库是出于研究目的而编写的,只是为了发现 JavaScript 中的特征可能/应该是什么样的而对其进行修改。它不是为生产代码设置的,也没有正式发布,即使它是稳定的并且能够运行您的系统。 【参考方案1】:

我确实重构了 OP 的示例,它完成了所要求的工作。 如果我有什么问题,请告诉我。

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 

  // - 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);

    withSameInternalXAndYState.call(this, stateValue);
  



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) 
    var
      stateValue = payloadList[0];

    stateValue.y = argsArray[0];  // same y from x.
  
  function afterReturningStateChangeYHandler(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);
  

  applicator(function sameXAndYBehavior (stateValue) 

    // no additional trait specific behavior within the applicator

  ).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>(function(r)function g(a)var c="none",b;Y(a)&&(ta(a)?c=ua:(b=Z(a),t("^class\\s+"+a.name+"\\s+\\").test(b)?c=va:t("\\([^)]*\\)\\s+=>\\s+\\(").test(b)?c=wa:z(a)&&(c=xa(a)?ya:za(a)||Aa(a)||Ba(a)?Ca:aa)));return c===aafunction C(a)return["^\\[object\\s+",a,"\\]$"].join("")function v(a,c)var b=Da[c];return!!b&&0<=b.indexOf(a)function M(a,c,b)return function()var d=arguments;c.apply(b,d);return a.apply(b,d)function N(a,c,b)return function()var d=arguments,e=a.apply(b,d);c.apply(b,d);return efunction Ea(a,c,b,d)return function()var e=arguments;c.call(b,e,d);return a.apply(b,e)function Fa(a,c,b,d)return function()var e=arguments,f=a.apply(b,e);c.call(b,e,d);return ffunction Ga(a,c,b,d)return function()var e=arguments,f=a.apply(b,e);c.call(b,f,e,d);return ffunction Ha(a,c,b,d)return function()var e=arguments,f;tryf=a.apply(b,e)catch(l)c.call(b,l,e,d)return ffunction Ia(a,c,b,d)return function()var e=arguments,f,g;tryf=a.apply(b,e)catch(m)g=mc.call(b,g||f,e,d);return ffunction Ja(a,c,b,d)return function()return c.call(b,a,c,arguments,d)function Ka(a,c)return function()var b=this[a];g(b)||(b=u);this[a]=M(b,c,this)function La(a,c)return function()var b=this[a];g(b)||(b=u);this[a]=N(b,c,this)function Ma(a,c)return function(b)var d=this[a];g(d)||(d=u);this[a]=Ea(d,c,this,b)function Na(a,c)return function(b)var d=this[a];g(d)||(d=u);this[a]=Fa(d,c,this,b)function Oa(a,c)return function(b)var d=this[a];g(d)||(d=u);this[a]=Ga(d,c,this,b)function Pa(a,c)return function(b)var d=this[a];g(d)||(d=u);this[a]=Ha(d,c,this,b)function Qa(a,c)return function(b)var d=this[a];g(d)||(d=u);this[a]=Ia(d,c,this,b)function Ra(a,c)return function(b)var d=this[a];g(d)||(d=u);this[a]=Ja(d,c,this,b)function ba(a,c,b)a.useTraits.rules.push(new A(type:"without",traitList:c,methodNameList:b))function ca(a,c,b,d)a.useTraits.rules.push(new A(type:"as",trait:c,methodName:b,methodAlias:d))function da(a)return ea(p(a)).reduce(function(a,b)q(b)&&a.push(F(b));return a,[])function fa()var a,c,b,d,e;if(O(this))if(d=this.valueOf(),e=d.parentLink,w(e))if(b=e.getSetup(),a=b.chainData,c=a.recentCalleeName,v(c,"without"))if(c=da(arguments),1<=c.length)a.recentCalleeName="without",ba(b,d.traitList,c);else throw new h("Not even a single <String> type was passed to 'without' as to be excluded method name.");elsea=["\u2026 invalid chaining of '",c,"().without()'"].join("");if("applyBehavior"==c)throw new n([a," in case of excluding one or more certain behaviors."].join(""));throw new n(a);else throw new k("Please do not spoof the context of 'use'.");else throw new k("Please do not spoof the context of 'apply'.");return efunction P(a,c)var b=a.getSetup();if(Sa(c,a.valueOf().traitList))b.chainData.recentCalleeName="applyTraits",b=new Q(traitList:c,parentLink:a),a.putChild(b);else throw new h("Any trait that got passed to 'apply' needs to be registered before via 'use'.");return bfunction Sa(a,c)return a.every(function(a)return 0<=c.indexOf(a))function Ta()var a;a=this.getSetup();var c=this.valueOf().traitList;I(this,a,a.chainData.recentCalleeName);a=P(this,c);return fa.apply(a,arguments)function Ua()var a=this.getSetup(),c=this.valueOf().traitList;I(this,a,a.chainData.recentCalleeName);return P(this,c)function ga()var a,c,b;b=p(arguments);if(w(this))if(a=this.getSetup(),c=a.chainData,c=c.recentCalleeName,I(this,a,c),a=["\u2026 invalid chaining of '",c,"().apply()'"].join(""),v(c,"apply"))if(x(b[0]))if(q(b[1]))if(v(c,"applyBehavior"))c=b[0];b=F(b[1]);a=this.getSetup();var d;if(0<=this.valueOf().traitList.indexOf(c))if(d=,c.call(d),g(d[b]))a.chainData.recentCalleeName="applyBehavior",b=new R(trait:c,methodName:b,parentLink:this),this.putChild(b);else throw new k(["The requested '",b,"' method has not been implemented by the trait that got passed to 'apply'."].join(""));else throw new h("Any trait that got passed to 'apply' needs to be registered before via 'use'.");else throw new n([a," in case of applying just a certain behavior."].join(""));else if(p(b).every(x))if(v(c,"applyTraits"))b=P(this,b);else throw new n([a," in case of applying from one or more traits."].join(""));else throw new h("'apply(\u2026)' excepts either, as its 2nd delimiting argument, just a 'String' type or exclusively one or more 'Trait' and 'Function' types.");else throw new h("'apply(<Trait|Function>, \u2026)' excepts as its 1st argument explicitly either a 'Trait' or a 'Function' type.");else throw new n(a);else throw new k("'use(\u2026).apply(\u2026)' only works within a validly chained context, please do not try spoofing the latter.");return bfunction ha()var a;a=this;if(O(a)||G(a))a=a.valueOf(),a=a.parentLink,a=ga.apply(a,arguments);else throw new k("Please do not spoof the context of 'apply'.");return afunction I(a,c,b)var d,e;a&&!c.chainData.isTerminated&&(d=a.getChild())&&(e=d.valueOf())&&("applyTraits"==b&&O(d)?ba(c,e.traitList,[]):"applyBehavior"==b&&G(d)&&(a=e.methodName,ca(c,e.trait,a,a)));c.chainData.recentCalleeName="apply"function R(a)this.valueOf=function()returntrait:a.trait,methodName:a.methodName,parentLink:a.parentLink;this.toString=function()return["ApplyLink::singleBehavior :: ",S.stringify(a)].join("");return thisfunction G(a)var c;return null!=a&&(a instanceof R||g(a.as)&&g(a.after)&&g(a.before)&&g(a.valueOf)&&(c=a.valueOf())&&x(c.trait)&&q(c.methodName)&&w(c.parentLink))function Q(a)this.valueOf=function()returntraitList:a.traitList,parentLink:a.parentLink;this.toString=function()return["ApplyLink::fromTraits :: ",S.stringify(a)].join("");return thisfunction O(a)var c;return null!=a&&(a instanceof Q||g(a.without)&&g(a.valueOf)&&(c=a.valueOf())&&T(c.traitList)&&w(c.parentLink))function U(a,c)var b=null;this.getSetup=function()return c;this.deleteChild=function()b=null;this.putChild=function(a)b=a;this.getChild=function()return b;this.valueOf=function()returntraitList:p(a.traitList);this.toString=function()return["UseRoot :: ",S.stringify(a)].join("");this.apply=ga.bind(this);this.apply.all=Ua.bind(this);this.apply.all.without=Ta.bind(this);return thisfunction w(a)var c;return null!=a&&(a instanceof U||g(a.apply)&&g(a.valueOf)&&g(a.getSetup)&&(c=a.valueOf())&&T(c.traitList)&&(c=a.getSetup())&&ia(c))function Va(a,c)var b=this.applicator,d=b.root,e=b.modifiers;b.canRequire=!1;if(q(a))if(g(c))e.push(Ka(a,c));else throw new h("'before(\u2026, <Function>)' excepts as its 2nd argument just a 'Function' type.");else throw new h("'before(<String>, \u2026)' excepts as its 1st argument just a 'String' type.");return dfunction Wa(a,c)var b=this.applicator,d=b.root,e=b.modifiers;b.canRequire=!1;if(q(a))if(g(c))e.push(Ma(a,c));else throw new h("'before.stateful(\u2026, <Function>)' excepts as its 2nd argument just a 'Function' type.");else throw new h("'before.stateful(<String>, \u2026)' excepts as its 1st argument just a 'String' type.");return dfunction Xa(a,c)var b=this.applicator,d=b.root,e=b.modifiers;b.canRequire=!1;if(q(a))if(g(c))e.push(La(a,c));else throw new h("'after(\u2026, <Function>)' excepts as its 2nd argument just a 'Function' type.");else throw new h("'after(<String>, \u2026)' excepts as its 1st argument just a 'String' type.");return dfunction Ya(a,c)var b=this.applicator,d=b.root,e=b.modifiers;b.canRequire=!1;if(q(a))if(g(c))e.push(Na(a,c));else throw new h("'after.stateful(\u2026, <Function>)' excepts as its 2nd argument just a 'Function' type.");else throw new h("'after.stateful(<String>, \u2026)' excepts as its 1st argument just a 'String' type.");return dfunction Za(a,c)var b=this.applicator,d=b.root,e=b.modifiers;b.canRequire=!1;if(q(a))if(g(c))e.push(Oa(a,c));else throw new h("'afterReturning(\u2026, <Function>)' excepts as its 2nd argument just a 'Function' type.");else throw new h("'afterReturning(<String>, \u2026)' excepts as its 1st argument just a 'String' type.");return dfunction $a(a,c)var b=this.applicator,d=b.root,e=b.modifiers;b.canRequire=!1;if(q(a))if(g(c))e.push(Pa(a,c));else throw new h("'afterThrowing(\u2026, <Function>)' excepts as its 2nd argument just a 'Function' type.");else throw new h("'afterThrowing(<String>, \u2026)' excepts as its 1st argument just a 'String' type.");return dfunction ab(a,c)var b=this.applicator,d=b.root,e=b.modifiers;b.canRequire=!1;if(q(a))if(g(c))e.push(Qa(a,c));else throw new h("'afterFinally(\u2026, <Function>)' excepts as its 2nd argument just a 'Function' type.");else throw new h("'afterFinally(<String>, \u2026)' excepts as its 1st argument just a 'String' type.");return dfunction bb(a,c)var b=this.applicator,d=b.root,e=b.modifiers;b.canRequire=!1;if(q(a))if(g(c))e.push(Ra(a,c));else throw new h("'around(\u2026, <Function>)' excepts as its 2nd argument just a 'Function' type.");else throw new h("'around(<String>, \u2026)' excepts as its 1st argument just a 'String' type.");return dfunction cb()var a=this.applicator,c=a.root,b=a.didRequire;if(a.canRequire)if(a.canRequire=!1,a.didRequire=!0,b=da(arguments),1<=b.length)a.requires=b;else throw new h("Not even a single <String> type was passed to 'requires' as method name that a trait, at its apply time, expects to be present at least.");elseif(b)throw new n("'requires' can be invoked exactly once, right after having executed a trait's 'applicator' method.");throw new n("'requires' can not bee invoked after a modifier method, but has to be the direct follower of a trait's 'applicator' method.");return cfunction db(a)I(this.useTraits.root,this,this.chainData.recentCalleeName);this.chainData.isTerminated=!0;var c=this.applicator,b=this.useTraits.rules,d=0>=b.length;if(!d&&!ja(b))throw new k("The trait composition language's rule set does not result in applicable behavior. This happens e.g. if all behavior has been deleted via 'without'.");if(null!=a)if(g(a=F(a)))c.body=a,a.call(c.proxy);else throw new h("The applicator should either stay empty or receive a sole applicable type like a 'Trait' or a 'Function' type.");else d&&0>=c.modifiers.length&&D.warn("The trait descriptor till now did neither receive an applicable function via the 'applicator' method nor any composition rule via 'use(\u2026).apply(\u2026)' nor any method modifier.");return c.rootfunction eb()var a;a=ea(p(arguments)).filter(function(a)return x(a));if(1<=a.length)a=new U(traitList:a,this);else throw new h("Not even a single valid 'Trait' or 'Function' type has been provided to 'use(<Trait|Function>[, \u2026])'.");return this.useTraits.root=afunction y(a,c)return a.findIndex(function(a)return a.trait===c)function ka(a)returntype:a.type,trait:a.trait,methodName:a.methodNamefunction la(a)returntype:a.type,trait:a.trait,fromTrait:a.fromTrait,methodName:a.methodNamefunction A(a)this.valueOf=function()return fb[a.type](a)function ja(a)var c=!0;if(1<=a.length)var b=,c=ma(a),d=na(c);d.forEach(function(a)a.trait.call(a.proxy));a.forEach(function(a)a.execute(b,d));c=1<=J(b).lengthreturn cfunction gb(a,c,b)var d;if(b[0]!==oa&&(d=c.reduce(function(b,c)g(a[c])||b.push(c);return b,[]),1<=d.length))throw new h("The type misses following required method(s) \u2026 '"+d.join("', '")+"'.");return dfunction hb(a,c,b)a.forEach(function(a)a.call(c,b))function ib(a)this.call=function()var c=p(arguments),b;b=c.shift();b=null!=b&&b||null;a.apply(b,c);return b;this.apply=function(c,b)null==b&&(b=[]);c=null!=c&&c||null;a.apply(c,b);return c;return thisfunction jb(a)this.requires=function()return p(a);return thisfunction kb(a)var c=[],b=;a.call(b,oa);c=J(b).sort(function(a,b)return a<b&&-1||a>b&&1||0);b=null;this.keys=function()return p(c);return thisfunction pa(a,c)this.toString=function()return"[object Trait]";this.valueOf=function()return a;ib.call(this,a);jb.call(this,c);kb.call(this,a);return thisfunction qa(a)var c;if(c=!!a)(c=a instanceof pa)||(c="object"==typeof a&&g(a.call)&&g(a.apply)&&g(a.valueOf)&&g(a.valueOf()));return cfunction x(a)return qa(a)||g(a)function ma(a)return a.reduce(function(a,b)var c=b.valueOf(),e=c.trait,f=c.fromTrait;e&&0>a.indexOf(e)&&a.push(e);f&&0>a.indexOf(f)&&a.push(f);(c.traitList||[]).forEach(function(b)0>a.indexOf(b)&&a.push(b));return a,[])function na(a)return a.map(function(a)returntrait:a,proxy:)function lb(a,c,b,d)var e=[],f=;if(!(0>=c.length||ja(c)))throw new k("The trait composition language's rule set does not result in applicable behavior. This happens e.g. if all behavior has been deleted via 'without'.");g(b)&&(b.call(f),e=J(f));return[a,c,e,d].some(function(a)return 1<=a.length)function mb(a)var c,b=a.useTraits.rules,d=a.applicator;a=d.body;var e=d.requires,d=d.modifiers,f=ma(b);lb(f,b,a,d)&&(d=function(a,b,c,d,e)return function()var f=this,h=p(arguments),l=na(a);l.forEach(function(a)a.trait.apply(a.proxy,h));b.forEach(function(a)a.execute(f,l));gb(f,c,h);g(d)&&d.apply(f,h);hb(e,f,h);return f(f,b,e,a,d),c=new pa(d,e));return cvar V=r.Function,H=r.Object,B=r.Array,t=r.RegExp,S=r.JSON,h=r.TypeError,k=r.ReferenceError,n=r.SyntaxError,nb=V.prototype,K=H.prototype,ra=U.prototype,sa=Q.prototype,L=R.prototype,oa=,aa="applicable-function",ya="buildin-constructor",va="class-constructor",wa="arrow-function",ua="generator-function",Ca="interface-type",Da=without:["apply","applyTraits"],as:["apply","applyBehavior"],after:["apply","applyBehavior","after","before"],before:["apply","applyBehavior","after","before"],apply:"apply applyBehavior applyTraits without as after before use".split(" "),applyTraits:"apply applyBehavior applyTraits without as after before use".split(" "),applyBehavior:"apply applyBehavior applyTraits without as after before use".split(" "),fb=beforeWrap:ka,afterWrap:ka,beforeApply:la,afterApply:la,without:function(a)returntype:a.type,traitList:p(a.traitList),methodNameList:p(a.methodNameList),as:function(a)returntype:a.type,trait:a.trait,methodName:a.methodName,methodAlias:a.methodAlias,ob=beforeWrap:function(a,c,b)var d=y(b,a.trait);b=b[d];a=a.methodName;c[a]=M((b&&b.proxy||)[a],c[a],c),afterWrap:function(a,c,b)var d=y(b,a.trait);b=b[d];a=a.methodName;c[a]=N((b&&b.proxy||)[a],c[a],c),beforeApply:function(a,c,b)var d=y(b,a.trait),e=y(b,a.fromTrait),d=b[d],e=b[e];b=d&&d.proxy||;e=e&&e.proxy||;a=a.methodName;a in c&&delete c.methodName;c[a]=M(b[a],e[a],c),afterApply:function(a,c,b)var d=y(b,a.trait),e=y(b,a.fromTrait),d=b[d],e=b[e];b=d&&d.proxy||;e=e&&e.proxy||;a=a.methodName;a in c&&delete c.methodName;c[a]=N(b[a],e[a],c),without:function(a,c,b)var d=a.traitList,e=a.methodNameList.reduce(function(a,b)a[b]=null;return a,);d.forEach(function(a)a=y(b,a);var d=(a=b[a])&&a.proxy||;J(d).forEach(function(a)a in e||(a in c&&delete c.methodName,c[a]=function(a,b)return function()return a.apply(b,arguments)(d[a],c))));a=b=d=e=null,as:function(a,c,b)var d=y(b,a.trait);b=(b=b[d])&&b.proxy||;d=a.methodAlias;a=a.methodName;d in c&&delete c.methodAlias;c[d]=function(a,b)return function()return a.apply(b,arguments)(b[a],c);a=b=d=b=b=d=a=null,F=function(a,c)return function(b)return b==c?b:a.call(b).valueOf()(K.valueOf,null),E=function(a)return function(c)return a.call(c)(K.toString),Z=function(a)return function(c)return a.call(c)(nb.toString),pb=function(a)trya.call(null,"length"),a=function(a,b)return function(c,e)return c!=b&&a.call(c,e)(a,null)catch(c)a=function(a,c)return function(b,d)var e=b!=c;if(e)trye=a.call(b,d)catch(m)e=!0return e(a,null)return a(K.propertyIsEnumerable),Y=function(a)return function(c)return typeof c==a(typeof V),z=function(a)return function(c)return Y(c)&&typeof c.call==a&&typeof c.apply==a(typeof V),qb=function(a)return function(c)return t(a).test(E(c))(C("Function")),ia=function(a)return function(c)return t(a).test(E(c))(C("Object")),rb=z(H.getPrototypeOf)&&H.getPrototypeOf||function(a)var c=a&&a.__proto__;return c||null===c?c:qb(a.constructor)?a.constructor.prototype:a instanceof H?K:null,W=function(a)return function(c)return(c=t(a).exec(Z(c)))&&c[1]("^function\\s+([^(]+)\\("),xa=function(a)return function(c)return t(a).test(W(c))("^(?:Array|ArrayBuffer|AsyncFunction|Atomics|Boolean|DataView|Date|Error|EvalError|Float32Array|Float64Array|Function|Generator|GeneratorFunction|Int16Array|Int32Array|Int8Array|InternalError|Collator|DateTimeFormat|NumberFormat|Iterator|Map|Number|Object|Promise|Proxy|RangeError|ReferenceError|RegExp|Bool16x8|Bool32x4|Bool64x2|Bool8x16|Float32x4|Float64x2|Int16x8|Int32x4|Int8x16|Uint16x8|Uint32x4|Uint8x16|Set|SharedArrayBuffer|StopIteration|String|Symbol|SyntaxError|TypeError|TypedArray|URIError|Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|WeakMap|WeakSet)$"),X=function(a,c)var b=t(c);return b.test(W(a))||b.test(W(rb(a))),za=function(a)return function(c)return X(c,a)("^(?:Node|CharacterData|Event|DOMError|DOMException|DOMImplementation|DOMStringList|DOMTokenList|EventTarget|htmlCollection|MutationObserver|MutationRecord|NodeFilter|NodeIterator|NodeList|Range|TreeWalker|URL|Document)$"),Aa=function(a)return function(c)return X(c,a)("^(?:HTMLElement|HTMLMediaElement|Element)$"),Ba=function(a)return function(c)return X(c,a)("^(?:CanvasRenderingContext2D|CanvasGradient|CanvasPattern|TextMetrics|ImageData|DOMStringMap|MediaError|HTMLCollection|NodeList)$"),ta=function(a)return function(c)return t(a).test(E(c))(C("GeneratorFunction")),q=function(a)return function(c)return t(a).test(E(c))(C("String")),T=z(B.isArray)&&B.isArray||function(a)return function(c)return t(a).test(E(c))(C("Array")),sb=z(B.isArguments)&&B.isArguments||function(a,c)var b=function(b)return t(a).test(E(b));b(arguments)||(b=function(a)return ia(a)&&"number"==typeof a.length&&c(a.length)&&!pb(a,"length"));return b(C("Arguments"),r.Number.isFinite),p=z(B.from)&&B.from||function(a)return function(c)return a.call(c)(B.prototype.slice),ea=function c(b)b=sb(b)&&p(b)||b;T(b)&&(b=b.reduce(function(b,e)return b.concat(c(e)),[]));return b,J=H.keys,u=function(),D;D=(D=r.console)&&z(D.warn)&&z(D.log)&&D||warn:u,log:u;L.as=function(c)var b,d,e,f,g;if(G(this))if(f=this.valueOf(),g=f.parentLink,w(g))if(e=g.getSetup(),d=e.chainData,b=d.recentCalleeName,v(b,"as"))if(q(c=F(c)))if(b=f.trait,f=f.methodName,f!==c)d.recentCalleeName="as",ca(e,b,f,c);else throw new h("Using identical method names in case of aliasing is considered to be a rule violating contradiction.");else throw new h("'as(<String>)' excepts as its sole argument just a 'String' type.");elsec=["\u2026 invalid chaining of '",b,"().as()'"].join("");if("applyTraits"==b)throw new n([c," in case of aliasing just a certain behavior."].join(""));throw new n(c);else throw new k("Please do not spoof the context of 'use'.");else throw new k("Please do not spoof the context of 'apply'.");return g;L.after=function(c)var b,d,e,f,l,m;if(G(this))if(f=this.valueOf(),m=f.parentLink,w(m))if(e=m.getSetup(),d=e.chainData,b=d.recentCalleeName,v(b,"after"))if(x(c))if(b=m.valueOf(),b=b.traitList,0<=b.indexOf(c))if(b=f.trait,b!==c)if(l=f.methodName,f=,c.call(f),g(f[l]))d.recentCalleeName="after",e.useTraits.rules.push(new A(type:"afterApply",trait:c,fromTrait:b,methodName:l));else throw new k(["Please consider applying '",l,"' directly. This expected behavior has not been implemented by the trait that got passed to 'after'."].join(""));else throw new k("Passing identical trait references to both 'apply' and 'after' is a contradiction that violates the composition rules.");else throw new h("Any trait passed to 'after' has to be registered before via 'use'.");else throw new h("'after(<Trait|Function>)' excepts as its sole argument explicitly either a 'Trait' or a 'Function' type.");else throw new n(["\u2026 invalid chaining of '",b,"().after()'"].join(""));else throw new k("Please do not spoof the context of 'use'.");else throw new k("Please do not spoof the context of 'apply'.");return m;L.before=function(c)var b,d,e,f,l,m;if(G(this))if(f=this.valueOf(),m=f.parentLink,w(m))if(e=m.getSetup(),d=e.chainData,b=d.recentCalleeName,v(b,"before"))if(x(c))if(b=m.valueOf(),b=b.traitList,0<=b.indexOf(c))if(b=f.trait,b!==c)if(l=f.methodName,f=,c.call(f),g(f[l]))d.recentCalleeName="before",e.useTraits.rules.push(new A(type:"beforeApply",trait:c,fromTrait:b,methodName:l));else throw new k(["Please consider applying '",l,"' directly. This expected behavior has not been implemented by the trait that got passed to 'before'."].join(""));else throw new k("Passing identical trait references to both 'apply' and 'before' is a contradiction that violates the composition rules.");else throw new h("Any trait passed to 'before' has to be registered before via 'use'.");else throw new h("'before(<Trait|Function>)' excepts as its sole argument explicitly either a 'Trait' or a 'Function' type.");else throw new n(["\u2026 invalid chaining of '",b,"().before()'"].join(""));else throw new k("Please do not spoof the context of 'use'.");else throw new k("Please do not spoof the context of 'apply'.");return m;L.apply=ha;sa.without=fa;sa.apply=ha;ra.after=function(c)var b,d,e,f;if(w(this))if(e=this.getSetup(),d=e.chainData,b=d.recentCalleeName,v(b,"after"))if(x(c))if(b=this.valueOf(),b=b.traitList,0<=b.indexOf(c))if(b=this.getChild(),f=b.valueOf(),b=f.trait,b!==c)if(f=f.methodName,b=,c.call(b),g(b[f]))d.recentCalleeName="after",e.useTraits.rules.push(new A(type:"afterWrap",trait:c,methodName:f));else throw new k(["Please consider applying '",f,"' directly. This expected behavior has not been implemented by the trait that got passed to 'after'."].join(""));else throw new k("Passing identical trait references to both 'apply' and 'after' is a contradiction that violates the composition rules.");else throw new h("Any trait passed to 'after' has to be registered before via 'use'.");else throw new h("'after(<Trait|Function>)' excepts as its sole argument explicitly either a 'Trait' or a 'Function' type.");else throw new n(["\u2026 invalid chaining of '",b,"().after()'"].join(""));else throw new k("Please do not spoof the context of 'use'.");return this;ra.before=function(c)var b,d,e,f;if(w(this))if(e=this.getSetup(),d=e.chainData,b=d.recentCalleeName,v(b,"before"))if(x(c))if(b=this.valueOf(),b=b.traitList,0<=b.indexOf(c))if(b=this.getChild(),f=b.valueOf(),b=f.trait,b!==c)if(f=f.methodName,b=,c.call(b),g(b[f]))d.recentCalleeName="before",e.useTraits.rules.push(new A(type:"beforeWrap",trait:c,methodName:f));else throw new k(["Please consider applying '",f,"' directly. This expected behavior has not been implemented by the trait that got passed to 'before'."].join(""));else throw new k("Passing identical trait references to both 'apply' and 'before' is a contradiction that violates the composition rules.");else throw new h("Any trait passed to 'before' has to be registered before via 'use'.");else throw new h("'before(<Trait|Function>)' excepts as its sole argument explicitly either a 'Trait' or a 'Function' type.");else throw new n(["\u2026 invalid chaining of '",b,"().before()'"].join(""));else throw new k("Please do not spoof the context of 'use'.");return this;A.prototype.execute=function(c,b)var d=this.valueOf();ob[d.type](d,c,b);return r.Trait=create:function(c)var b,d,e;g(c=F(c))&&(e=chainData:isTerminated:!1,recentCalleeName:"use",useTraits:root:null,rules:[],applicator:body:null,proxy:,root:,modifiers:[],requires:[],canRequire:!0,didRequire:!1,b=e.applicator.root,b.requires=cb.bind(e),b.before=Va.bind(e),b.after=Xa.bind(e),b.before.stateful=Wa.bind(e),b.after.stateful=Ya.bind(e),b.afterReturning=Za.bind(e),b.afterThrowing=$a.bind(e),b.afterFinally=ab.bind(e),b.around=bb.bind(e),b=eb.bind(e),d=db.bind(e),c(b,d),e=mb(e));return e,isTrait:qa,isApplicable:x)(Function("return this")());</script>

【讨论】:

哇,这真是一项巨大的工作,非常感谢!我需要一些时间来熟悉您的介绍和基本概念,但看起来很有希望。 @pragma ...不客气。请不要犹豫。现实世界的用例正是我正在寻找的,以证明 JavaScript 是否需要像这里使用的组合模式这样的特征。 @pragma ...我很抱歉,我刚刚意识到我一直使用谷歌登录,同时从那里提供提供的库。因此,没有注意到所提供的示例对阅读此处的任何人都没有用/帮助。我现在确实将脚本切换到另一个提供程序,使其可供所有人访问,并且也可由浏览器运行(已证明适用于 chrome、firefox、safari 和 msie,以防万一能够上课)。玩得开心,玩得开心。 @pragma ...您可能还会查看另一个基于特征的组合代码,我同时进行了重构以寻找可以证明这种方法的示例...Mixins for ES6 classes, transpiled with babel

以上是关于重构遗留的基于 mixin 的类层次结构的主要内容,如果未能解决你的问题,请参考以下文章

如何(visual studio 2008 / Resharper)重构/自动化mixin模式

由学习《软件设计重构》所想到的代码review

Eclipse 重构功能的使用与重构快捷键

大话重构连载:遗留系统——软件工业时代的痛

重构

Mockito - 嘲弄遗留类构造函数