初めに
typeScript(またはjavaScript)ではJavaの用にPolymorphismを使ってインターフェースや抽象クラスによるコーディングが可能であるが、これに着目した記事はあまり多くない。筆者はこの部分に着目・整理することを目的として本記事を執筆している。抽象・具象の考え方はプログラマとして必須の概念であり、開発時の生産性や保守運用時のメンテナンスビリティに直接作用するので効果は大きいものと考える。
この記事では変数や定数の型定義がより厳格であるtype Scriptを用いて説明する。
また、Polymorphismを活用したデザインパターンとしてFactory Patternについても言及する。
前提
- node --version v14.18.3
- npm --version 6.14.15
- yarn --version 1.22.17
モジュール作成導入手順
検証コードの取得
- Gitから当リポジトリの配布ブランチをチェックアウトする
ライブラリ導入
npm install
typescriptソースのコンパイル
yarn build
- distディレクトリにjsファイルが生成される。
起動検証手順
node dist/testDriver01.js Apple
または
yarn start Apple
- 以下のログが出ればOK
% yarn start Apple
yarn run v1.22.17
$ node dist/testDriver01.js Apple
.envファイルの設定を読み込み
{ LOG_LEVEL: 'info' }
log level is : info
[2022-03-20T16:16:14.631] [INFO] farmProduct - [Farm] [registerProductNames] 具象クラスの登録
[2022-03-20T16:16:14.635] [INFO] farmProduct - [TestFactory] [exec] テストドライバ開始
[2022-03-20T16:16:14.636] [INFO] farmProduct - [TestFactory] [exec] ----------------------------------------
[2022-03-20T16:16:14.636] [INFO] farmProduct - [TestFactory] [exec] 生産品名: りんご
[2022-03-20T16:16:14.636] [INFO] farmProduct - [TestFactory] [exec] 種類: 果物
[2022-03-20T16:16:14.636] [INFO] farmProduct - [TestFactory] [exec] 価格: 100
[2022-03-20T16:16:14.636] [INFO] farmProduct - [TestFactory] [exec] +++ サマリ +++
[2022-03-20T16:16:14.637] [INFO] farmProduct - [TestFactory] [exec] 初源 : 植物 種別 : 果物 名称 : りんご 価格 : 100
[2022-03-20T16:16:14.637] [INFO] farmProduct - [TestFactory] [exec] ----------------------------------------
[2022-03-20T16:16:14.637] [INFO] farmProduct - [TestFactory] [exec] テストドライバ終了
✨ Done in 0.59s.
テストドライバの説明
code
// .envファイルの設定を読み込み
import dotenv from 'dotenv';
const result = dotenv.config();
console.log(`.envファイルの設定を読み込み`);
console.log(result.parsed);
//ログ出力設定
import { LoggingClass } from './libs/logger';
const logger = new LoggingClass();
//農場生産物のインターフェース
import { Product } from './product/productPolymorphism';
//ファクトリ機能のインポート
import { Farm } from './factory/farm';
//具象クラスの登録
Farm.registerProductNames();
class TestFactoryActor {
private tsName: string = "TestFactory";
public exec(productName: string) { // 引数には生産品名を指定する
const funcName = 'exec';
logger.info(this.tsName, funcName, `テストドライバ開始`);
try {
const commodity : Product = Farm.getProduct(productName);
logger.info(this.tsName, funcName, `----------------------------------------`);
logger.info(this.tsName, funcName, `生産品名: ${commodity.getName()}`);
logger.info(this.tsName, funcName, `種類: ${commodity.getType()}`);
logger.info(this.tsName, funcName, `価格: ${commodity.getPrice()}`);
logger.info(this.tsName, funcName, `+++ サマリ +++`);
logger.info(this.tsName, funcName, `${commodity.getSummary()}`);
logger.info(this.tsName, funcName, `----------------------------------------`);
logger.info(this.tsName, funcName, `テストドライバ終了`);
} catch (err) {
logger.error(this.tsName, funcName, `${err}`);
}
}
}
// テストドライバ実行
let testFactoryActor = new TestFactoryActor();
testFactoryActor.exec(process.argv[2]);
- 固有の生産品名はテストドライバの引数で渡す。(例;Apple)
- 固有の生産品はFarmファクトリに事前に登録されている。
- 固有生産品は生産品インターフェースを実装している。(ポリモーフィズム)
- テストドライバは生産品名に対応した生産品インターフェースのインスタンスを取得する。
- テストドライバはインスタンスに定義された操作を実行する。
ポリモーフィズムの概要
概要図
定義済みの固有生産品
- Apple
- 種別は果物
- Onion
- 種別は野菜
未定義の固有生産品を指定した場合
- Farmファクトリからのインスタンス取得が失敗する
% yarn start Banana
yarn run v1.22.17
$ node dist/testDriver01.js Banana
.envファイルの設定を読み込み
{ LOG_LEVEL: 'info' }
log level is : info
[2022-03-20T16:52:25.919] [INFO] farmProduct - [Farm] [registerProductNames] 具象クラスの登録
[2022-03-20T16:52:25.924] [INFO] farmProduct - [TestFactory] [exec] テストドライバ開始
[2022-03-20T16:52:25.924] [ERROR] farmProduct - [TestFactory] [exec] Error: 生産品[Banana]は登録されていません。
✨ Done in 0.58s.
Bananaを生産品として扱うためには
- src/product/productClasses.tsに具象クラスを定義する。
// バナナ
export class Banana extends Fruit {
private name : string = `バナナ`;
private price : number = 80;
// 名称取得関数
getName() : string {
return this.name;
}
// 価格取得関数
getPrice() : number {
return this.price;
}
}
- src/factory/farm.tsにて登録対象に加える。
- javaのようにClass.forName()を使ってクラス名文字列から実装クラスのインスタンス化をしたいところだが、javaScriptは仕様上できないためせっせとインスタンスをプールしておかなければならない。(魅力半減)
- プールされたインスタンスを使い回す事になるので、インスタンスの関数は排他制御しておく必要がある。(魅力激減)
- 上記のマイナス要因は案件ごとのアーキテクチャとして制御可能な要素ではある。コード量・障害特定・保守性の観点で見ると有用。
import {
// ここにproductClassesに作成した具象クラスをリストしていく
Apple,
Onion,
Banana, // <--追加する
} from '../product/productClasses';
-- 中略 --
Farm.products.push(
// ここにproductClassesに作成した具象クラスをリストしていく
Apple,
Onion,
Banana, // <--追加する
);
Node.jsサーバ案件への適用考察
- router体系
- expressの入り口でFactoryからrouterインタフェースを取得
- 実装の大枠や共通処理は抽象クラスで実装し、個別処理は具象クラスで実装する
- Error体系
- 例外発生時に例外インターフェースをFactoryから取得
- Request体系
- 要求電文をFactoryから取得したインターフェースにマッピング
- ただし電文仕様の定義が必要
- Response体系
- 応答電文作成時にFactoryから取得したインターフェースを用いて構築
- ただし電文仕様の定義が必要
routerへの適用イメージ
テンプレrouter構造
フロントエンドへの適用
- React, Vue, AngularなどTypeScript適用可能なフレームワークへ応用可能と想定できる(未確認)