Refactoring legacy mixin-based class hierarchies

一曲冷凌霜 提交于 2020-01-19 04:04:05

问题


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.


回答1:


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 {

  // - 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===aa}function 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 e}}function 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 f}}function Ga(a,c,b,d){return function(){var e=arguments,f=a.apply(b,e);c.call(b,f,e,d);return f}}function Ha(a,c,b,d){return function(){var e=arguments,f;try{f=a.apply(b,e)}catch(l){c.call(b,l,e,d)}return f}}function Ia(a,c,b,d){return function(){var e=arguments,f,g;try{f=a.apply(b,e)}catch(m){g=m}c.call(b,g||f,e,d);return f}}function 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.");else{a=["\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 e}function 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 b}function 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 b}function 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 a}function 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(){return{trait:a.trait,methodName:a.methodName,parentLink:a.parentLink}};this.toString=function(){return["ApplyLink::singleBehavior :: ",S.stringify(a)].join("")};return this}function 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(){return{traitList:a.traitList,parentLink:a.parentLink}};this.toString=function(){return["ApplyLink::fromTraits :: ",S.stringify(a)].join("")};return this}function 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(){return{traitList: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 this}function 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 d}function 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 d}function 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 d}function 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 d}function 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 d}function $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 d}function 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 d}function 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 d}function 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.");else{if(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 c}function 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.root}function 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=a}function y(a,c){return a.findIndex(function(a){return a.trait===c})}function ka(a){return{type:a.type,trait:a.trait,methodName:a.methodName}}function la(a){return{type:a.type,trait:a.trait,fromTrait:a.fromTrait,methodName:a.methodName}}function 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).length}return c}function 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 d}function 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 this}function jb(a){this.requires=function(){return p(a)};return this}function 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 this}function 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 this}function 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 c}function 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){return{trait: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 c}var 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){return{type:a.type,traitList:p(a.traitList),methodNameList:p(a.methodNameList)}},as:function(a){return{type: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){try{a.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)try{e=a.call(b,d)}catch(m){e=!0}return 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.");else{c=["\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>


来源:https://stackoverflow.com/questions/43027388/refactoring-legacy-mixin-based-class-hierarchies

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!