この記事は J2complexed Advent Calendar 2016 の1日目です。
突然、書くことになったのでJavaScriptのポエムを書かせてもらいました。
JavaScriptでSingletonパターン
Singletonパターンはあるクラスを実体が1つしかできないようにするデザインパターンです。
あっちでも、こっちでも使うけど、同じものを参照したいってときに使います。よくSingletonを使うとユニットテストがしにくくなるとか、コードが読みづらくなるとか言われてますね。多用しすぎなければ、便利かなーと思ってますが、ダメですか?
たぶん、いろんなやり方があると思うので、これがベストってわけじゃないです。
他にもこんな書き方のほうがキレイだよ、とかあれば、教えてください。
ただ個人的にはこの書き方で安定してます。
例を書きました
以下に書いたScriptは例として、名簿リスト(名前と誕生日をセットで持っています)をデータに持って、各クラスから返すというものです。
※ 前提条件として、ES2015をbabelで書いてます。
こんな感じでSingletonのクラスを書きます。
/**
* NamesManager
*
* Usage:
* ---
* import NamesManager from 'names_manager'
* const namesManager = NamesManager.instance
* ---
*/
import NamesList from 'yaml/names_list'
import 'babel-polyfill'
const singleton = Symbol()
const singleton_enforcer = Symbol()
export default class NamesManager {
/**
* コンストラクタ
* @param {symbol} enforcer
*/
constructor (enforcer) {
if (enforcer !== singleton_enforcer) {
throw "Error"
}
this.clear()
}
/**
* インスタンスを返す
* @returns {Object} インスタンス
*/
static get instance () {
if (!this[singleton]) {
this[singleton] = new NamesManager(singleton_enforcer)
}
return this[singleton]
}
get currentData() {
return NamesList[this.index]
}
next() {
this.change(this.getNextIndex())
}
change(index) {
this.index = index
}
isLast() {
return this.index >= NamesList.length - 1
}
getNextIndex() {
return this.isLast() ? 0 : this.index + 1
}
clear() {
this.index = 0
}
}
こんな感じのデータを用意します。
- name: T.K
birthday: 12月3日
- name: M.H
birthday: 5月14日
- name: M.O
birthday: 1月20日
こんな感じの名前を返すクラスと・・・
import NamesManager from 'names_manager'
const namesManager = NamesManager.instance
export default class Name {
constructor(selector) {
}
get current() {
return namesManager.currentData.name
}
}
こんな感じの誕生日を返すクラスを用意します。
import NamesManager from 'names_manager'
const namesManager = NamesManager.instance
export default class Birthday {
constructor(selector) {
}
get current() {
return namesManager.currentData.birthday
}
}
で、それを使って書いてみると、こうなります。
import NamesManager from 'names_manager'
import Name from 'name'
import Birthday from 'birthday'
const namesManager = NamesManager.instance
const name = new Name()
const birthday = new Birthday()
console.log('current name is %s', name.current)
console.log('current birthday is %s', birthday.current)
// current name is T.K
// current birthday is 12月3日
namesManager.next()
console.log('current name is %s', name.current)
console.log('current birthday is %s', birthday.current)
// current name is M.H
// current birthday is 5月14日
functionsでnamesManagerをnext()すると、nameやbirthdayでも同じnamesManagerを参照しているので、currentが変わってますよね?
何が言いたいかというと、いろんなところから呼ばれそうなクラスをSingletonで作っておけば、どこから呼び出しても同じ状態を持っているので便利ってことです。
※ 適当に例を書いただけなので、上記のコードは動かないかもしれません。
まとめ
上の例を見ていただくとわかると思いますが、Nameクラスなどのcurrent()はユニットテストをしようと思うとNamesManagerに依存してしまってテストしにくくなってしまっています。
(何が返ってくるかは、NamesManagerの状態次第になってますよね?)
なので、Singletonはダメだーって言われているんだと思いますが、文字列が返ってればOKってテストにしてしまえば、いいんじゃないかなーと思っています。ユニットテストは簡単なものがいい派です。
Singletonパターンとは何なのか、とか問題点は何なのか、とかは難しいので完全には理解できてませんので、こんな感じで使ってるよーという紹介ポエムでした。