Skip to content

クラス

ES5のコンストラクタ関数よりES2015/ES6クラスを推奨

古典的なES5クラスでは可読性の高いクラス継承・コンストラクタ・メソッド定義を実現するのは非常に困難です。継承が必要な場合(実際には必要ない可能性があることを認識してください)、ES2015/ES6クラスを優先してください。ただし、大規模で複雑なオブジェクトが必要となるまで、クラスよりも小さな関数を優先することを推奨します。

悪い例:

javascript
const Animal = function(age) {
  if (!(this instanceof Animal)) {
    throw new Error("Instantiate Animal with `new`");
  }

  this.age = age;
};

Animal.prototype.move = function move() {};

const Mammal = function(age, furColor) {
  if (!(this instanceof Mammal)) {
    throw new Error("Instantiate Mammal with `new`");
  }

  Animal.call(this, age);
  this.furColor = furColor;
};

Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};

const Human = function(age, furColor, languageSpoken) {
  if (!(this instanceof Human)) {
    throw new Error("Instantiate Human with `new`");
  }

  Mammal.call(this, age, furColor);
  this.languageSpoken = languageSpoken;
};

Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};

良い例:

javascript
class Animal {
  constructor(age) {
    this.age = age;
  }

  move() {
    /* ... */
  }
}

class Mammal extends Animal {
  constructor(age, furColor) {
    super(age);
    this.furColor = furColor;
  }

  liveBirth() {
    /* ... */
  }
}

class Human extends Mammal {
  constructor(age, furColor, languageSpoken) {
    super(age, furColor);
    this.languageSpoken = languageSpoken;
  }

  speak() {
    /* ... */
  }
}

メソッドチェーンを活用

jQueryやLodashなど多くのライブラリで用いられているメソッドチェーンは、コードを表現力豊かにし冗長性を低減します。この利点から、メソッドチェーンの使用を推奨します。クラス関数の末尾でthisを返すようにすれば、メソッドチェーンが可能になり、コードの簡潔さを実現できます。

悪い例:

javascript
class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
  }

  setModel(model) {
    this.model = model;
  }

  setColor(color) {
    this.color = color;
  }

  save() {
    console.log(this.make, this.model, this.color);
  }
}

const car = new Car("Ford", "F-150", "red");
car.setColor("pink");
car.save();

良い例:

javascript
class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
    // NOTE: Returning this for chaining
    return this;
  }

  setModel(model) {
    this.model = model;
    // NOTE: Returning this for chaining
    return this;
  }

  setColor(color) {
    this.color = color;
    // NOTE: Returning this for chaining
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    // NOTE: Returning this for chaining
    return this;
  }
}

const car = new Car("Ford", "F-150", "red").setColor("pink").save();

継承よりコンポジションを優先

GoFの「デザインパターン」で提唱されているように、可能な限り継承よりもコンポジションを優先すべきです。継承とコンポジションにはそれぞれ適切な使用場面がありますが、重要なのは継承を直感的に選択する前に、コンポジションで問題をより適切にモデル化できないか検討することです。多くの場合、コンポジションが有効な解決策となります。

「では継承はいつ使うべきか?」という疑問に対し、状況によって異なりますが継承が有効なケースを以下に示します:

  1. 継承関係が「has-a」関係ではなく「is-a」関係を表す場合(例:Human→Animal 対 User→UserDetails)
  2. 基底クラスからコードを再利用できる場合(例:人間が他の動物と同様に移動可能)
  3. 基底クラスの変更で派生クラス全体に変更を適用したい場合(例:全ての動物の移動時のカロリー消費量を変更)

悪い例:

javascript
class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  // ...
}

// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
  constructor(ssn, salary) {
    super();
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

良い例:

javascript
class EmployeeTaxData {
  constructor(ssn, salary) {
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  setTaxData(ssn, salary) {
    this.taxData = new EmployeeTaxData(ssn, salary);
  }
  // ...
}