合并类似功能 DRY

Consolidate Similar Functions DRY

我有这两个非常相似的功能。我考虑过使用字符串和 eval('Man'),但如果可能的话,我很想避免这种情况。怎么把两者抽象出来,就只有一个功能?

function showMan() {
    widget.classList.add(toggle);
    fx.wipeOut({
        node: calWoman
    }).play();
    fx.wipeOut({
        node: pointsWoman
    }).play();

    pointsMan.classList.remove(toggle);
    fx.wipeIn({
        node: calMan
    }).play();
    fx.wipeIn({
        node: pointsMan
    }).play();
    mtFields();
    inputMan.focus();
}

function showWoman() {
    widget.classList.add(toggle);
    fx.wipeOut({
        node: calMan
    }).play();
    fx.wipeOut({
        node: pointsMan
    }).play();

    pointsWoman.classList.remove(toggle);
    fx.wipeIn({
        node: calWoman
    }).play();
    fx.wipeIn({
        node: pointsWoman
    }).play();
    mtFields();
    inputWoman.focus();
}
/** @todo abstract and create one function - showPerson('Man') */
function showPerson(calPerson, pointsPerson, inputPerson) {
   //replace calWoman and calMan with calPerson

   //replace pointsMan and pointsWoman with pointsPerson

   //replace inputMan and inputWoman with inputPerson

}

如果您不能传入它,您可以使用闭包来初始化带有这些引用的函数。

function showPerson(calPerson, pointsPerson, inputPerson) {
   function showingPersonWithReferences() {
     //replace calWoman and calMan with calPerson

     //replace pointsMan and pointsWoman with pointsPerson

     //replace inputMan and inputWoman with inputPerson
   }

   return showingPersonWithReferences;
}

这是javascript中的一点函数式编程。

创建函数 showPerson,它接受 gender 作为参数。

从那里,您可以创建一个对象,在其中封装特定性别的函数,然后引用它的相反函数:

function showPerson(gender) {
    var optionsObj = {
        man: {
            cal: calMan,
            points: pointsMan,
            input: inputMan.focus,
        },
        woman: {
            cal: calWoman,
            points: pointsWoman,
            input: inputWoman.focus,
        }
    }

    gender === 'man' ? optionsObj[gender].opposite = optionsObj.woman : optionsObj[gender].opposite = optionsObj.man;

    widget.classList.add(toggle)
    fx.wipeOut({
        node: optionsObj[gender].cal,
    }).play();
    fx.wipeOut({
        node: optionsObj[gender].points
    }).play();

    optionsObj[gender].points.classList.remove(toggle);
    fx.wipeIn({
        node: optionsObj[gender].opposite.cal
    }).play();
    fx.wipeIn({
        node: optionsObj[gender].opposite.points
    }).play();
    mtFields();
    optionsObj[gender].input()
}

如果将所有变量合并到一个对象中会更容易。

function showPerson(sex) {
    var people = {
        woman: {
            cal: calWoman,
            points: pointsWoman,
            input: inputWoman
        },
        man: {
            cal: calMan,
            points: pointsMan,
            input: inputMan
        }
    };

    sex = sex.toLowerCase();
    var otherSex = sex === 'man' ? 'woman' : 'man';  
    var show = people[sex];
    var hide = people[otherSex]

    widget.classList.add(toggle);
    fx.wipeOut({
        node: hide.cal
    }).play();
    fx.wipeOut({
        node: hide.points
    }).play();

    show.points.classList.remove(toggle);
    fx.wipeIn({
        node: show.cal
    }).play();
    fx.wipeIn({
        node: show.points
    }).play();
    mtFields();

    show.input.focus();
}

// usage
showPerson('man');
showPerson('woman');

考虑这里所有内容的顺序有点令人困惑,但诀窍不是将两个巨大的功能分解为一个,而是将您的系统分解为可以更好地组合的更小部分。

function hideNode ( node ) { fx.wipeOut({ node: node }).play(); }
function revealNode ( node ) { fx.wipeIn({ node: node }).play(); }

function showPerson (removalList, revealList, points, input) {
  widget.classList.add(toggle);
  removalList.forEach(hideNode);
  points.classList.remove(toggle);
  revealList.forEach(revealNode);
  input.focus();
}

// somewhere NORTH of your current function
var womanList = [calWoman, pointsWoman];
var manList = [calMan, pointsMan];
var revealList, removalList, input, points;

if (man) {
   removalList = womanList;
   revealList = manList;
   input = inputMan;
   points = pointsMan;
} else {
   removalList = manList;
   revealList = womanList;
   input = inputWoman;
   points = pointsWoman;
}

// alternatively, these can be stored in an object this way, and pulled by a flag, rather than by an if statement...

showPerson( removalList, revealList, points, input );

如果您注意到,逻辑非常简单,依靠将参数传递到我的函数中。
选择值的逻辑也很简单,但我已经将选择的需要移到了函数之外;那是其他人关心的问题(即使它是一个新函数,除了选择并传递适当的值外什么都不做)。

我已经在函数中执行了重复步骤,并将它们分解为更小的函数,并且我正在使用列表(甚至只是两个列表),这样我就可以忽略某件事重复了多少次的细节被调用... ...它被调用的次数与你派给我的人一样多。
我还没有解决 "Who calls showPerson, with what and what access...",但这是解决更具体问题的方法;总结使用 if () { } 和值 setting/passing 的人非常快,从这里开始。

这远非干净的成品,但它所做的 提供的是了解如何在更大的项目中处理这些移动部分。

编辑

为了更进一步,并表明您不需要在重写方面做太多事情,在这里(至少一开始不需要),我将进一步添加:

function getPersonConfig ( type ) {
  var config = {
    man: { /* ... */ },
    woman: { /* ... */ }
  };

  return config[type];
}

function showMan () {
  var config = getPersonConfig( "man" );
  showPerson( config );
}

function showWoman () {
  var config = getPersonConfig( "woman" );
  showPerson( config );
}

我正在使用稍微我的showPerson功能的不同版本...
在其中,我传递了一个对象(而不是单独的变量),但这就是我需要更改的全部内容。

看看这些可组合的小函数给我买了什么... 您根本不需要更改 showManshowWoman
相反,您正在劫持它们过去的样子,并使用它们来调用更小的函数,这些函数执行相同的工作,但现在更可分离和可重用。

这是一个 "facade" 或 "veneer",通常在工程中,它是一个 API 或正在谈论的服务,但它的基本意思是当你想要要更新代码,如果您可以保持方法名称、输入和输出以及所有结果相同(从外界可以看出),那么您可以随心所欲地更改代码的内部结构。

showMan 仍在调用,它正在做的事情现在仍在做,没有添加或删除任何内容。
showWoman.
相同 所以你已经成功地保留了外观,同时更换了内部结构。

外立面也可以反过来;按照您希望它向外界展示的方式编写新的 service/API,并以将旧代码挂钩到新结构所需的最低限度将其发布给世界,然后将新功能填充为你去吧。

希望对您有所帮助。

尽管上面提供了出色的解决方案和建议,但这是我最终所做的,它最适合我的情况(最少重写,最干燥)。非常感谢您回答问题。

function showPerson() {
  /**
   * the element being hidden should be placed in the first parameter
   * @param {object} woman - HTMLFormElement (pointsWoman)
   * @param {object} man - HTMLFormElement (pointsMan)
   * @example showPerson(pointsWoman, pointsMan);
   */

  if (arguments[0] instanceof HTMLFormElement && arguments[1] instanceof HTMLFormElement) {
    widget.classList.add(toggle);

    fx.wipeOut({
      node: arguments[0]
    }).play();

    arguments[1].classList.remove(toggle);

    fx.wipeIn({
      node: arguments[1]
    }).play();

    mtFields();
    arguments[1][0].focus();
  }
  else {
    throw new Error('You need to provide the forms to show/hide');
  }
}