どうも。つじけ(tsujikenzo)です。このシリーズでは、Enum(イーナム)について初級・中級の2回に渡ってお送りします。
2021年最後の投稿となりました。本年はたくさんの方の応援があり、年間170本の記事をUPしました。
来年もたくさん考察したいと思います。
前回のおさらい
さて、前回は、Enum初級編をお送りしました。そもそもEnumとはなにか、呼び出し方などを学びました。
今日は2回目で、Enum中級編についてお届けします。
今日のアジェンダ
- Enumを定義しよう
- Enumを凍結しよう
- Enumを強制しよう
Enumを定義しよう
前回、ファイルのコンテントタイプを呼び出すEnumは、このように実装されているだろう、というイメージをシェアしました。
const MimeType = {
JPEG:'JPEGとは何かを定義するありとあらゆる値',
GIF:'GIFの定義',
PDF:'PDFの定義'
}
console.log(MimeType.JPEG); // => 'JPEGとは何かを定義するありとあらゆる値'
Enumを使う理由は、プロジェクト全体で、定義が複数あったり、バラつきを防ぐためです。
JPEGの定義があちこちにあると、プロジェクトは破綻しますよね。
ここでは、注文情報に必要な、送料のEnumを定義してみましょう。
Enumは、プロジェクト全体から呼ばれるため、グローバル領域に定義することが多いでしょう。
/** グローバル領域 */
/**
* 送料を列挙するEnum
* @enum {number}
*/
const Soryo = {
HOKKAIDO: 1800,
TOHOKU: 1500,
KANTO: 1200,
KANSAI: 1200,
KYUSYU: 1500
};
function myFunction211231_01() {
/** Enum Soryoから送料を返す関数 */
const getShippingCost = area => Soryo[area];
// 注文情報
const purchaseOrder = {
productName: 'サバ味噌缶',
price: 1000,
tax: 0.8,
customerName: 'etau',
shippingCost: getShippingCost('KANTO')
}
// 送料の確認
console.log(purchaseOrder.shippingCost); // => 1200
}
しかしながら、このままだと、Enumはいともかんたんに書き換えられてしまいます。
/** グローバル領域 */
/**
* 送料を列挙するEnum
* @enum {number}
*/
const Soryo = {
HOKKAIDO: 1800,
TOHOKU: 1500,
KANTO: 1200,
KANSAI: 1200,
KYUSYU: 1500
};
function myFunction211231_02() {
//Enumを書き換える
Soryo.KANTO = 0;
// Enum Soryoから送料を返す関数
const getShippingCost = area => Soryo[area];
// 注文情報
const purchaseOrder = {
productName: 'サバ味噌缶',
price: 1000,
tax: 0.8,
customerName: 'etau',
shippingCost: getShippingCost('KANTO')
}
// 送料の確認
console.log(purchaseOrder.shippingCost); // => 0
}
Enumを凍結しよう
組み込みオブジェクトのObjectオブジェクトには、オブジェクトを凍結する、Object.freeze()メソッドが提供されています。
書き換えができないように、定義したEnumを凍結してしまいましょう。
/** グローバル領域 */
/**
* 送料を列挙するEnum
* @enum {number}
*/
const Soryo = {
HOKKAIDO: 1800,
TOHOKU: 1500,
KANTO: 1200,
KANSAI: 1200,
KYUSYU: 1500
};
//オブジェクト凍結する
Object.freeze(Soryo);
function myFunction211231_03() {
//Enumを書き換える
Soryo.KANTO = 0;
/** Enum Soryoから送料を返す関数 */
const getShippingCost = area => Soryo[area];
// 注文情報
const purchaseOrder = {
productName: 'サバ味噌缶',
price: 1000,
tax: 0.8,
customerName: 'etau',
shippingCost: getShippingCost('KANTO')
}
// 送料の確認
console.log(purchaseOrder.shippingCost); // => 1200
}
Enumを強制しよう
本来Enumとは、プロジェクト全体で、唯一無二の値の定義であるはずです。
以下のように、同名のプロパティや値が存在することは、あまり好ましくありません。
/** グローバル領域 */
/**
* 送料を列挙するEnum
* @enum {number}
*/
const Soryo = Object.freeze({
HOKKAIDO: 1800,
TOHOKU: 1500,
});
function myFunction211231_04() {
/**
* 送料を列挙するEnum
* @enum {number}
*/
const ShippingCost = Object.freeze({
HOKKAIDO: 1800,
TOHOKU: 1500,
});
//同名のプロパティ、値が存在することは、本来のEnumの役割を果たしていない
console.log(Soryo.HOKKAIDO === ShippingCost.HOKKAIDO); // => true(どちらも1800)
}
開発規模が大きくなればなるほど、「送料は必ずEnumから取得してください」というコーディングルールを守らずに、サボるメンバーが出ます。
/**
* 送料を列挙するEnum
* @enum {number}
*/
const Soryo = Object.freeze({
HOKKAIDO: 1800,
TOHOKU: 1500,
});
//人はサボろうとする
const po1 = { purchaseOrderId: 'po001', shippingCost: 1800 };
そこで、プロパティの値にSymbol型を定義します。
/**
* 送料を列挙するEnum
* @enum {Symbol}
*/
const Soryo = Object.freeze({
HOKKAIDO: Symbol(1800),
TOHOKU: Symbol(1500),
});
Symbol型は唯一無二の値ですので、送料をEnumから取得していなければ、例外を吐くように実装できます。
/** グローバル領域 */
/**
* 送料を列挙するEnum
* @enum {Symbol}
*/
const Soryo = Object.freeze({
HOKKAIDO: Symbol(1800),
TOHOKU: Symbol(1500),
});
function myFunction211231_05() {
//サボったコード
const po2 = { purchaseOrderId: 'po002', shippingCost: 1800 };
//Enumから取得していなければ例外を吐く
if (po2.shippingCost === Soryo.HOKKAIDO) {
console.log('ちゃんとEnumから取得してますね');
} else {
throw new Error('送料は必ずEnumから取得してください');
}
} // => Error: 送料は必ずEnumから取得してください
Symbol型のラベル値を使用するさいは、型を変換するなど、ひとてま掛かりますが、バグを早期に発見するために役立つかもしれません。
/** グローバル領域 */
/**
* 送料を列挙するEnum
* @enum {Symbol}
*/
const Soryo = Object.freeze({
HOKKAIDO: Symbol(1800),
TOHOKU: Symbol(1500),
});
function myFunction211231_06() {
//サボったコード
const po2 = { purchaseOrderId: 'po002', shippingCost: 1800 };
//Enumから取得していなければ例外を吐く
try {
if (po2.shippingCost === Soryo.HOKKAIDO) {
console.log('ちゃんとEnumから取得してますね');
} else {
throw new Error('送料は必ずEnumから取得してください');
};
} catch (e) {
console.log(e); //[Error: 送料は必ずEnumから取得してください]
}
//Enumから取得する
const po3 = { purchaseOrderId: 'po003', shippingCost: Soryo.HOKKAIDO };
if (po3.shippingCost === Soryo.HOKKAIDO) {
console.log('ちゃんとEnumから取得してますね');
} else {
throw new Error('送料は必ずEnumから取得してください');
}; //ちゃんとEnumから取得してますね
//中身のラベルの確認
console.log(Soryo.HOKKAIDO.description); //1800
console.log(typeof Soryo.HOKKAIDO.description); //string
//使う時はNumberに
console.log(typeof Number(Soryo.HOKKAIDO.description)); //number
}
まとめ
以上で、「Enumってなに?(中級編)」をお送りしました。
開発規模がちいさいうちは、Enumオブジェクトの凍結だけでもかまわないと思います。
Symbol型の実装は、開発メンバーが増えたり、規模がおおきくなったさいなどに検討してみましょう。