Javaにおけるタイプオブジェクトパターン:動的なクラス定義による柔軟性の向上
別名
- タイプ記述子
- 型安全な列挙
タイプオブジェクトデザインパターンの目的
柔軟で拡張可能な関連タイプのセットの作成を可能にします。
タイプオブジェクトパターンの詳細な説明と現実世界の例
現実世界の例
タイプオブジェクトパターンの現実世界の類似例としては、ロールプレイングゲーム(RPG)のキャラクターカスタマイズシステムが挙げられます。このようなゲームでは、プレイヤーは戦士、魔法使い、弓使いなど、それぞれ固有の能力と属性を持つ様々なキャラクタークラスから選択できます。タイプオブジェクトパターンにより、ゲームはこれらのキャラクタークラスとその動作を動的に定義できます。各クラスの詳細をハードコーディングする代わりに、新しいキャラクタータイプを追加したり、既存のタイプを変更したりしても、基盤となるゲームロジックを変更する必要がない柔軟なシステムを使用します。この拡張性により、開発者はアップデートや拡張を通じて新しいキャラクタークラスを導入し、ゲームを新鮮で魅力的なものにすることができます。
簡単に言うと
Javaのタイプオブジェクトパターンが、柔軟で拡張可能な関連クラスの動的な作成と管理を可能にする方法を探り、既存のコードベースを変更せずにモジュール性を求めるJava開発者にとって理想的なパターンです。
gameprogrammingpatterns.comによると
タイプオブジェクトクラスと型付きオブジェクトクラスを定義します。各タイプオブジェクトインスタンスは、異なる論理タイプを表します。各型付きオブジェクトは、そのタイプを記述するタイプオブジェクトへの参照を格納します。
Javaにおけるタイプオブジェクトパターンのプログラミング例
タイプオブジェクトパターンは、オブジェクトの「タイプ」を表すフィールドを持つクラスを作成することで、柔軟で再利用可能なオブジェクトの作成を可能にするデザインパターンです。このデザインパターンは、予期されるJavaの型が事前に定義されていない場合、または修正や追加が必要な場合に非常に役立ち、頻繁な再コンパイルを行うことなく効率的なJava開発を保証します。
提供されたコードでは、タイプオブジェクトパターンがミニキャンディークラッシュゲームに実装されています。このゲームには多くの異なるキャンディーがあり、ゲームのアップグレードに伴って時間の経過とともに変化する可能性があります。
この実装の主要なコンポーネントを分解してみましょう。
- **Candyクラス**:このクラスは、このパターンにおける「タイプ」オブジェクトを表します。各
Candy
には、name
、parent
、points
、type
があります。type
は、CRUSHABLE_CANDY
またはREWARD_FRUIT
のいずれかである列挙型です。
class Candy {
String name;
Candy parent;
String parentName;
int points;
Type type;
Candy(String name, String parentName, Type type, int points) {
// constructor implementation
}
int getPoints() {
// implementation
}
Type getType() {
// implementation
}
void setPoints(int a) {
// implementation
}
}
- **JsonParserクラス**:このクラスは、キャンディーに関する詳細を含むJSONファイルを解析する役割を担っています。JSONファイル内の各キャンディーに対して
Candy
オブジェクトを作成し、Hashtable
に格納します。
public class JsonParser {
Hashtable<String, Candy> candies;
JsonParser() {
this.candies = new Hashtable<>();
}
void parse() throws JsonParseException {
// implementation
}
void setParentAndPoints() {
// implementation
}
}
- **Cellクラス**:このクラスは、ゲームマトリックス内のセルを表します。各セルには、クラッシュできるキャンディーが含まれています。また、クラッシュの方法、マトリックスの再構成方法、ポイントの獲得方法に関する情報も含まれています。
class Cell {
Candy candy;
int positionX;
int positionY;
Cell() {
// implementation
}
Cell(Candy candy, int positionX, int positionY) {
// implementation
}
void crush(CellPool pool, Cell[][] cellMatrix) {
// implementation
}
// other methods...
}
- **CandyGameクラス**:このクラスには、ゲームの継続に関するルールが含まれています。
class CandyGame {
Cell[][] cells;
CellPool pool;
int totalPoints;
CandyGame(int num, CellPool pool) {
// implementation
}
boolean continueRound() {
// implementation
}
// other methods...
}
- **CellPoolクラス**:このクラスは、繰り返し新しいセルを作成するのではなく、クラッシュしたキャンディーセルを再利用するプールです。
class CellPool {
int pointer;
List<Cell> pool;
Candy[] randomCode;
CellPool(int num) {
// implementation
}
void addNewCell(Cell c) {
// implementation
}
Candy[] assignRandomCandytypes() {
// implementation
}
Cell getNewCell() {
// implementation
}
}
- **Appクラス**:このクラスには、ゲームを開始するメインメソッドが含まれています。
@Slf4j
public class App {
public static void main(String[] args) {
var givenTime = 50; //50ms
var toWin = 500; //points
var pointsWon = 0;
var numOfRows = 3;
var start = System.currentTimeMillis();
var end = System.currentTimeMillis();
var round = 0;
while (pointsWon < toWin && end - start < givenTime) {
round++;
var pool = new CellPool(numOfRows * numOfRows + 5);
var cg = new CandyGame(numOfRows, pool);
if (round > 1) {
LOGGER.info("Refreshing..");
} else {
LOGGER.info("Starting game..");
}
cg.printGameStatus();
end = System.currentTimeMillis();
cg.round((int) (end - start), givenTime);
pointsWon += cg.totalPoints;
end = System.currentTimeMillis();
}
LOGGER.info("Game Over");
if (pointsWon >= toWin) {
LOGGER.info("" + pointsWon);
LOGGER.info("You win!!");
} else {
LOGGER.info("" + pointsWon);
LOGGER.info("Sorry, you lose!");
}
}
}
App
クラスで何が起こるかを分解してみましょう。
main
メソッドはアプリケーションのエントリポイントです。いくつかの変数を初期化することから始まります。
givenTime
は50ミリ秒に設定されます。これはゲームの時間制限です。toWin
は500ポイントに設定されます。これはゲームに勝利するための目標スコアです。pointsWon
は0に初期化されます。この変数は、これまでに獲得した合計ポイントを追跡します。numOfRows
は3に設定されます。これはゲームグリッドの行数です。start
とend
はどちらも、ミリ秒単位の現在のシステム時間に設定されます。これらの変数は、経過時間を追跡するために使用されます。round
は0に初期化されます。この変数は、現在のラウンド数を追跡します。
ゲームは、プレイヤーが十分なポイントを獲得する(
pointsWon >= toWin
)か、時間制限に達する(end - start < givenTime
)まで続くループに入ります。各ラウンドの開始時に、新しい
CellPool
とCandyGame
が作成されます。CellPool
は、ゲームグリッド内のセルの数(numOfRows * numOfRows + 5
)に基づいてサイズが初期化されます。CandyGame
は、行数とCellPool
で初期化されます。最初のラウンドでない場合は、「Refreshing..」というメッセージがログに出力されます。最初のラウンドの場合は、「Starting game..」というメッセージがログに出力されます。
cg.printGameStatus()
を呼び出すことで、現在のゲーム状況が出力されます。end
時間は、現在のシステム時間に更新されます。cg.round((int) (end - start), givenTime)
を呼び出すことで、ゲームラウンドがプレイされます。経過時間と時間制限が引数として渡されます。ラウンドで獲得したポイントは、合計ポイントに追加されます。
end
時間は再び現在のシステム時間に更新されます。ループの後、「Game Over」というメッセージがログに出力されます。
獲得した合計ポイントが目標スコア以上の場合、勝利メッセージがログに出力されます。そうでない場合は、敗北メッセージがログに出力されます。
これは、プレイヤーが所定の時間制限内にできるだけ多くのポイントを獲得しようとする、キャンディークラッシュに似たゲームの簡略版です。ゲームはラウンドで行われ、プレイヤーのスコアと経過時間はゲーム全体を通して追跡されます。
コンソール出力
14:36:14.453 [main] INFO com.iluwatar.typeobject.App -- Starting game..
14:36:14.455 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- cherry |
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- mango |
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum |
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum |
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle |
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle |
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean |
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean |
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- mango |
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.459 [main] INFO com.iluwatar.typeobject.CandyGame -- +20 points!
...
...
...
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- cherry |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- mango |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +20 points!
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- cherry |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- mango |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum |
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame --
14:36:14.465 [main] INFO com.iluwatar.typeobject.App -- Game Over
14:36:14.465 [main] INFO com.iluwatar.typeobject.App -- 660
14:36:14.465 [main] INFO com.iluwatar.typeobject.App -- You win!!
この実装では、タイプオブジェクトパターンにより、Candy
オブジェクトを柔軟に作成できます。各キャンディーのタイプは、JSONファイルを解析することで実行時に決定されるため、コードを再コンパイルすることなく、キャンディータイプの追加、変更、削除を容易に行うことができます。
Javaでタイプオブジェクトパターンを使用するケース
このパターンは、次のような場合に使用できます。
- 既存のコードを変更せずに、拡張可能な関連クラスのセットを作成する必要がある場合。
- タイプとその動作を実行時に、または柔軟な方法で定義する必要があるシナリオに最適です。
- タイプの数が多く、時間の経過とともに変化する可能性がある状況に適しています。
- 異なるオブジェクトの「タイプ」の違いは、動作ではなくデータです。
タイプオブジェクトパターンのJavaチュートリアル
Javaにおけるタイプオブジェクトパターンの現実世界の応用事例
- Javaコレクションフレームワーク:List、Set、Mapなどの様々なコレクションタイプを活用します。
- グラフィックライブラリ:特定のプロパティと動作を持つ様々な形状を定義します。
- ゲーム開発:固有の属性と動作を持つ、異なるタイプのキャラクターやアイテムを作成します。
タイプオブジェクトパターンのメリットとトレードオフ
メリット
- コードの柔軟性と拡張性を向上させます。
- 既存のコードを変更することなく、新しいタイプの追加を簡素化します。
- 関連する動作とプロパティを整理することで、コードの可読性を向上させます。
トレードオフ
- 適切に管理されない場合、複雑さが増す可能性があります。
- 動的な型チェックと処理により、パフォーマンスのオーバーヘッドが発生する可能性があります。
関連するJavaデザインパターン
- ファクトリーメソッド:多くの場合、Type Objectと組み合わせて、型のインスタンスを作成するために使用されます。
- ストラテジー:アルゴリズムまたは動作のファミリーを定義するという点で類似していますが、交換可能な動作により焦点を当てています。
- プロトタイプ:既存のインスタンスをコピーして新しいインスタンスを作成するために使用でき、動的で柔軟な型作成をサポートします。