C言語でのデザインパターン実装は難しいとされていますが、チェーンオブリスポンシビリティパターンを使えば、柔軟かつ拡張性のあるコードを書くことができます。本記事では、その具体的な実装方法と応用例を紹介します。
チェーンオブリスポンシビリティパターンとは
チェーンオブリスポンシビリティパターンは、複数のオブジェクトが連続して処理を行うデザインパターンです。このパターンでは、リクエストがチェーン状に繋がった複数のハンドラに順次渡され、各ハンドラがリクエストを処理するか、次のハンドラに渡すかを決定します。これにより、処理の責任を特定のオブジェクトに集中させず、柔軟で拡張可能なシステムを構築することができます。
C言語での基本的な実装方法
C言語でチェーンオブリスポンシビリティパターンを実装するためには、以下の手順を踏みます。
ハンドラ構造体の定義
各ハンドラを表す構造体を定義します。この構造体には、処理関数のポインタと次のハンドラへのポインタを含めます。
typedef struct Handler {
void (*handle)(struct Handler* self, int request);
struct Handler* next;
} Handler;
処理関数の実装
各ハンドラの具体的な処理を行う関数を実装します。例として、具体的な処理を行うハンドラを3つ作成します。
void handleRequest1(Handler* self, int request) {
if (request == 1) {
printf("Request 1 handled by Handler 1\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
void handleRequest2(Handler* self, int request) {
if (request == 2) {
printf("Request 2 handled by Handler 2\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
void handleRequest3(Handler* self, int request) {
if (request == 3) {
printf("Request 3 handled by Handler 3\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
ハンドラの連結
各ハンドラを連結してチェーンを作成します。
Handler handler1 = {handleRequest1, NULL};
Handler handler2 = {handleRequest2, NULL};
Handler handler3 = {handleRequest3, NULL};
handler1.next = &handler2;
handler2.next = &handler3;
ハンドラの定義と実装
ハンドラは、リクエストを処理する責任を持つオブジェクトです。各ハンドラは、次のハンドラへの参照を保持し、リクエストを処理できない場合は次のハンドラに処理を委譲します。ここでは、具体的なハンドラの定義と実装方法について詳しく解説します。
ハンドラ構造体の定義
ハンドラを表す構造体には、処理関数のポインタと次のハンドラへのポインタを持たせます。この構造体により、チェーンを形成することができます。
typedef struct Handler {
void (*handle)(struct Handler* self, int request);
struct Handler* next;
} Handler;
ハンドラの処理関数の実装
各ハンドラが具体的なリクエストを処理する関数を実装します。この関数は、リクエストを処理するか、次のハンドラにリクエストを渡すかを決定します。
void handleRequest1(Handler* self, int request) {
if (request == 1) {
printf("Request 1 handled by Handler 1\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
void handleRequest2(Handler* self, int request) {
if (request == 2) {
printf("Request 2 handled by Handler 2\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
void handleRequest3(Handler* self, int request) {
if (request == 3) {
printf("Request 3 handled by Handler 3\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
ハンドラの初期化と連結
各ハンドラを初期化し、次のハンドラと連結します。この手順により、チェーンが形成され、リクエストが順次ハンドラに渡されます。
Handler handler1 = {handleRequest1, NULL};
Handler handler2 = {handleRequest2, NULL};
Handler handler3 = {handleRequest3, NULL};
handler1.next = &handler2;
handler2.next = &handler3;
ハンドラの連結方法
複数のハンドラを連結してチェーンを形成する方法を解説します。ハンドラを連結することで、リクエストが順次各ハンドラに渡されるようになります。
ハンドラの連結
各ハンドラは、自身の処理が完了した後、次のハンドラにリクエストを渡す必要があります。これを実現するために、各ハンドラの次のハンドラへのポインタを設定します。
Handler handler1 = {handleRequest1, NULL};
Handler handler2 = {handleRequest2, NULL};
Handler handler3 = {handleRequest3, NULL};
handler1.next = &handler2;
handler2.next = &handler3;
連結の仕組み
連結されたハンドラは、リクエストを受け取ると自分の処理関数を実行し、処理できなければ次のハンドラにリクエストを渡します。これにより、リクエストがチェーン状に渡され、適切なハンドラで処理されるまで続きます。
void handleRequest1(Handler* self, int request) {
if (request == 1) {
printf("Request 1 handled by Handler 1\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
void handleRequest2(Handler* self, int request) {
if (request == 2) {
printf("Request 2 handled by Handler 2\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
void handleRequest3(Handler* self, int request) {
if (request == 3) {
printf("Request 3 handled by Handler 3\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
チェーンの実行
チェーンが正しく連結されたことを確認したら、リクエストを最初のハンドラに渡してチェーンを実行します。
int main() {
Handler handler1 = {handleRequest1, NULL};
Handler handler2 = {handleRequest2, NULL};
Handler handler3 = {handleRequest3, NULL};
handler1.next = &handler2;
handler2.next = &handler3;
int request = 2;
handler1.handle(&handler1, request);
return 0;
}
このコードを実行すると、リクエストが順次ハンドラに渡され、適切なハンドラで処理されます。
実行例とその解説
ここでは、チェーンオブリスポンシビリティパターンを実装したC言語のプログラムを実際に動かして、その動作を解説します。
コード全体の例
以下に、ハンドラを定義し、連結し、実行する完全なプログラムを示します。
#include <stdio.h>
typedef struct Handler {
void (*handle)(struct Handler* self, int request);
struct Handler* next;
} Handler;
void handleRequest1(Handler* self, int request) {
if (request == 1) {
printf("Request 1 handled by Handler 1\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
void handleRequest2(Handler* self, int request) {
if (request == 2) {
printf("Request 2 handled by Handler 2\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
void handleRequest3(Handler* self, int request) {
if (request == 3) {
printf("Request 3 handled by Handler 3\n");
} else if (self->next != NULL) {
self->next->handle(self->next, request);
}
}
int main() {
Handler handler1 = {handleRequest1, NULL};
Handler handler2 = {handleRequest2, NULL};
Handler handler3 = {handleRequest3, NULL};
handler1.next = &handler2;
handler2.next = &handler3;
int request = 2;
handler1.handle(&handler1, request);
return 0;
}
実行結果
このプログラムを実行すると、次のような出力が得られます。
Request 2 handled by Handler 2
解説
- ハンドラの定義:
各ハンドラは、handleRequest1
、handleRequest2
、handleRequest3
という関数で処理を行います。これらの関数は、リクエストが特定の値に一致する場合に処理を行い、一致しない場合は次のハンドラにリクエストを渡します。 - ハンドラの連結:
handler1
、handler2
、handler3
の各ハンドラが定義され、handler1
のnext
ポインタがhandler2
を指し、handler2
のnext
ポインタがhandler3
を指すように設定されています。 - リクエストの処理:
メイン関数で、handler1
のhandle
関数を呼び出し、リクエストを処理します。リクエストが2
であるため、handler1
からhandler2
に渡され、handler2
で処理されます。
このようにして、チェーンオブリスポンシビリティパターンを用いることで、リクエストが順次ハンドラに渡され、適切なハンドラで処理される柔軟なシステムを構築することができます。
応用例:エラーハンドリング
チェーンオブリスポンシビリティパターンは、エラーハンドリングにも効果的です。ここでは、エラーハンドリングの例として、異なる種類のエラーを処理するハンドラを作成し、エラーが発生したときに適切なハンドラで処理する方法を紹介します。
エラーハンドラの実装
異なる種類のエラーを処理するハンドラを実装します。それぞれのハンドラは特定のエラータイプを処理し、処理できない場合は次のハンドラにエラーを渡します。
#include <stdio.h>
typedef struct Handler {
void (*handle)(struct Handler* self, int error);
struct Handler* next;
} Handler;
void handleErrorNotFound(Handler* self, int error) {
if (error == 404) {
printf("Error 404: Not Found handled by NotFoundHandler\n");
} else if (self->next != NULL) {
self->next->handle(self->next, error);
}
}
void handleErrorUnauthorized(Handler* self, int error) {
if (error == 401) {
printf("Error 401: Unauthorized handled by UnauthorizedHandler\n");
} else if (self->next != NULL) {
self->next->handle(self->next, error);
}
}
void handleErrorInternal(Handler* self, int error) {
if (error == 500) {
printf("Error 500: Internal Server Error handled by InternalErrorHandler\n");
} else if (self->next != NULL) {
self->next->handle(self->next, error);
}
}
エラーハンドラの連結
エラーハンドラを連結し、チェーンを形成します。
int main() {
Handler notFoundHandler = {handleErrorNotFound, NULL};
Handler unauthorizedHandler = {handleErrorUnauthorized, NULL};
Handler internalErrorHandler = {handleErrorInternal, NULL};
notFoundHandler.next = &unauthorizedHandler;
unauthorizedHandler.next = &internalErrorHandler;
int error = 401;
notFoundHandler.handle(¬FoundHandler, error);
return 0;
}
実行結果と解説
このプログラムを実行すると、次のような出力が得られます。
Error 401: Unauthorized handled by UnauthorizedHandler
この結果は、エラーコードが401であるため、最初のハンドラnotFoundHandler
では処理されず、次のハンドラunauthorizedHandler
で処理されたことを示しています。
エラーハンドリングの利点
- 柔軟性: エラーハンドラを追加することで、新しい種類のエラーに簡単に対応できます。
- 拡張性: ハンドラのチェーンを変更することで、処理の順序を簡単に変更できます。
- 責任分離: 各ハンドラが特定のエラータイプを処理するため、コードが明確で保守しやすくなります。
応用例:イベント処理
チェーンオブリスポンシビリティパターンは、イベント処理にも効果的です。複数のイベントハンドラを連結することで、特定のイベントを適切なハンドラで処理することができます。ここでは、イベント処理の例として、異なる種類のイベントを処理するハンドラを作成し、イベントが発生したときに適切なハンドラで処理する方法を紹介します。
イベントハンドラの実装
異なる種類のイベントを処理するハンドラを実装します。それぞれのハンドラは特定のイベントタイプを処理し、処理できない場合は次のハンドラにイベントを渡します。
#include <stdio.h>
typedef struct Handler {
void (*handle)(struct Handler* self, int event);
struct Handler* next;
} Handler;
void handleMouseEvent(Handler* self, int event) {
if (event == 1) {
printf("Mouse Event handled by MouseEventHandler\n");
} else if (self->next != NULL) {
self->next->handle(self->next, event);
}
}
void handleKeyboardEvent(Handler* self, int event) {
if (event == 2) {
printf("Keyboard Event handled by KeyboardEventHandler\n");
} else if (self->next != NULL) {
self->next->handle(self->next, event);
}
}
void handleWindowEvent(Handler* self, int event) {
if (event == 3) {
printf("Window Event handled by WindowEventHandler\n");
} else if (self->next != NULL) {
self->next->handle(self->next, event);
}
}
イベントハンドラの連結
イベントハンドラを連結し、チェーンを形成します。
int main() {
Handler mouseEventHandler = {handleMouseEvent, NULL};
Handler keyboardEventHandler = {handleKeyboardEvent, NULL};
Handler windowEventHandler = {handleWindowEvent, NULL};
mouseEventHandler.next = &keyboardEventHandler;
keyboardEventHandler.next = &windowEventHandler;
int event = 2;
mouseEventHandler.handle(&mouseEventHandler, event);
return 0;
}
実行結果と解説
このプログラムを実行すると、次のような出力が得られます。
Keyboard Event handled by KeyboardEventHandler
この結果は、イベントコードが2であるため、最初のハンドラmouseEventHandler
では処理されず、次のハンドラkeyboardEventHandler
で処理されたことを示しています。
イベント処理の利点
- 柔軟性: イベントハンドラを追加することで、新しい種類のイベントに簡単に対応できます。
- 拡張性: ハンドラのチェーンを変更することで、処理の順序を簡単に変更できます。
- 責任分離: 各ハンドラが特定のイベントタイプを処理するため、コードが明確で保守しやすくなります。
演習問題
チェーンオブリスポンシビリティパターンの理解を深めるために、以下の演習問題に取り組んでください。これらの問題を通じて、実際に手を動かしながら学ぶことで、パターンの活用方法を体得できます。
演習問題1: カスタムエラーハンドラの追加
新しいエラーハンドラを追加して、特定のエラーコード (例えば、403 Forbidden) を処理するようにプログラムを拡張してください。
void handleErrorForbidden(Handler* self, int error) {
if (error == 403) {
printf("Error 403: Forbidden handled by ForbiddenHandler\n");
} else if (self->next != NULL) {
self->next->handle(self->next, error);
}
}
// main関数内でのハンドラ連結に追加
Handler forbiddenHandler = {handleErrorForbidden, NULL};
internalErrorHandler.next = &forbiddenHandler;
演習問題2: 複数イベント処理の実装
以下のイベントタイプを処理するハンドラを追加し、イベントが発生したときに適切なハンドラで処理するようにプログラムを拡張してください。
- 4: Network Event
- 5: Disk Event
void handleNetworkEvent(Handler* self, int event) {
if (event == 4) {
printf("Network Event handled by NetworkEventHandler\n");
} else if (self->next != NULL) {
self->next->handle(self->next, event);
}
}
void handleDiskEvent(Handler* self, int event) {
if (event == 5) {
printf("Disk Event handled by DiskEventHandler\n");
} else if (self->next != NULL) {
self->next->handle(self->next, event);
}
}
// main関数内でのハンドラ連結に追加
Handler networkEventHandler = {handleNetworkEvent, NULL};
Handler diskEventHandler = {handleDiskEvent, NULL};
windowEventHandler.next = &networkEventHandler;
networkEventHandler.next = &diskEventHandler;
演習問題3: 汎用ハンドラの実装
どのリクエストやエラーも処理できる汎用ハンドラを作成し、チェーンの最後に追加してください。このハンドラは、全てのリクエストやエラーに対してデフォルトの処理を行います。
void handleDefault(Handler* self, int code) {
printf("Default handler: Unrecognized code %d\n", code);
}
// main関数内でのハンドラ連結に追加
Handler defaultHandler = {handleDefault, NULL};
diskEventHandler.next = &defaultHandler;
まとめ
チェーンオブリスポンシビリティパターンは、C言語で柔軟かつ拡張性のあるコードを実装するための強力なデザインパターンです。本記事では、その基本的な実装方法から応用例までを紹介しました。このパターンを活用することで、エラーハンドリングやイベント処理など、複雑な処理をシンプルに管理できるようになります。是非、自分のプロジェクトにも応用してみてください。
コメント