如何避免副作用(Javascript)



我目前正在构建一个21点游戏,我的一个类叫做"甲板";。这个甲板需要做两件事:

  1. 返回一组其他对象可以使用的牌(这相当于分发这些牌(
  2. 将这些牌从牌组中移除(一旦发完牌,就应该将其移除(

目前,我已经通过创建两个方法实现了这两项功能:selectCards()removeIndexFromDeck。我首先调用返回所选卡的selectCards(),但也调用removeIndexFromDeck()

这种方法是否违反了最佳实践?看起来我的selectCards()函数既有返回值,也有副作用。

如果它确实违反了最佳实践,我该如何改变这些方法,但要确保我仍然能够返回所选的牌,并将其从牌组中移除。

谢谢!

class Deck {
constructor() {
this.deck = [];
['♦', '♣', '♥', '♠'].forEach(suit => {
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'].forEach(value => {
this.deck.push(`${value}${suit}`);
});
});
}
selectCards(numCards) {
let selectedCards = [];
while (numCards > 0) {
let randIndex = Math.floor(Math.random() * this.deck.length);
selectedCards.push(this.deck[randIndex]);
this.removeIndexFromDeck(randIndex);
numCards -= 1;
}
return selectedCards;
}
removeIndexFromDeck(index) {
this.deck.splice(index, 1);
}
}

确实,您通常应该避免同时发生突变和返回值,但好的规则总是有例外的。即使是本机Java脚本也有例外,Array#pop可能是最广为人知的一个。然而,许多人会同意pop在目前的工作中非常有用。另外,您在脚本中调用的Array#splice会对进行变异并返回信息。

除非你想放弃OOP而转向函数式编程,否则这种模式是可以的。我只想确保方法的名称尽可能少地对这种双重效应产生怀疑。因此,我将把selectCards称为extractCardspullCards。这提供了一个更强烈的暗示,甲板是变异的。

我还建议实现shuffle方法,而不是在选择卡片时使用随机索引。如果您支持私有属性,那么将deck数组定义为私有,以便对外部世界隐藏混洗的内容。

以下是我的意思(没有私人(:

class Deck {
constructor() {
this.deck = Array.from('♦♣♥♠', suit =>
['A','2','3','4','5','6','7','8','9','10','J','Q','K'].map(
value => `${value}${suit}`
)
).flat();
}
shuffle() { // mutates the deck, much like Array#sort mutates an array
let deck = this.deck;
for (let i = deck.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = deck[i];
deck[i] = deck[j];
deck[j] = temp;
}
}
extractCards(numCards) {  // Better name. Mutates & returns. 
// Perform a controlled Array#splice
if (typeof numCards !== "number" || numCards <= 0) throw "Invalid argument";
if (this.deck.length < numCards) throw "Deck does not have enough cards for this operation";
return this.deck.splice(-numCards);
}
}
let deck = new Deck();
deck.shuffle();
console.log(...deck.extractCards(4));

如果使用类,通常意味着存在副作用,因为数据是类的一部分。阿卡,你得到的方法没有返回值(通常意味着它们有副作用(,在适当的地方变异类状态。你能做的最接近的事情是让尽可能多的方法返回新的deck值,而不是使用突变/副作用数组方法,然后在你的父方法中使用this.deck = removeIndexFromDeck(index)来限制你有副作用的地方。

如果您放弃类,转而生成操作数据的函数,并将deck仅视为数据而非智能类,则函数本身将不会产生副作用。

您还可以使用在进行突变时返回新的Deck类实例的模式,这意味着Deck中的数据将始终是不可变的。不过,在这种情况下,我看不出这有什么好处。

最新更新