この記事について
※ 2024/10/01に内容を修正
デザインパターンについて勉強したアウトプット記事です。
記事の内容でおかしな点や間違っている部分があれば、お手数ですがコメントにてご指摘ください!
参考にしたリンク
Adapterパターンとは
Adapterパターンとはあるクラスが特定のインターフェースを持っておらず、そのクラスを編集不可能な状態のときに特定インターフェースを使えるようにするデザインパターンの一つです
引用元:https://qiita.com/Cova8bitdot/items/f09776957073cf64902b
引用した内容をチーム開発の状況に置き換えてみると
特定のインターフェースを持っていないAクラスがあります。このクラスの作成者は他のチームメンバーが作成したクラスなので直接内容を変更することが難しいです。
↑
この「特定のインターフェースを持っていない」かつ「クラスの内容は変更したくない」という状況を解決出来るデザインパターンです。
下は修正前の内容
元からある処理を別の用途で使い回したい時や、元の処理に追加機能を実装したい・元の処理を利用して新しい機能を実装したいという時に利用するデザインパターンです。
実装したソースコード
ソースコード一覧
複数あるので、まとめて表示します。
// これが今回アダプターで利用する特定のインターフェースです
public interface IReceiveWorkSalary
{
/// <summary>
/// 仕事の給料を受け取るメソッド
/// </summary>
public int ReceiveWorkSalary();
}
// 今回アダプターとして対応したいクラスで、IReceiveWorkSalaryインターフェースは未実装です
// ゲームセンターアルバイトクラス
public class GameCenterPartTimeWork
{
// 1時間辺りの時給
private int hourlyWage = 1200;
/// <summary>
/// 1時間当たりの給料を返す
/// </summary>
/// <param name="workHours">働いた時間</param>
/// <returns>時間合計の給料</returns>
public int PerHourSalary(int workHours)
{
return workHours * hourlyWage;
}
}
// アルバイトクラスをIReceiveWorkSalaryで利用できるようにラップしたアダプタークラス
public class GameCenterWorkAdapter : IReceiveWorkSalary
{
// 特定のインタフェースにしたいクラス
private GameCenterPartTimeWork gameCenterJob;
// 固定で8時間働く
private int workHours = 8;
public GameCenterWorkAdapter()
{
gameCenterJob = new();
}
/// <summary>
/// ゲームセンタークラスの給料取得処理を返す
/// </summary>
public int ReceiveWorkSalary()
{
return gameCenterJob.PerHourSalary(workHours);
}
}
public class Human : MonoBehaviour
{
[SerializeField, Header("エンジニア判定フラグ")]
private bool isEngineer = false;
private void Start()
{
Work();
}
/// <summary>
/// 働くメソッド
/// </summary>
private void Work()
{
IReceiveWorkSalary receiveWorkSalary;
if (isEngineer)
{
receiveWorkSalary = new EngineerWork();
Debug.Log($"エンジニアの月給は{receiveWorkSalary.ReceiveWorkSalary()}円です。");
}
else
{
// ゲームセンターアルバイトをラップしているアダプタークラスを使う
receiveWorkSalary = new GameCenterWorkAdapter();
Debug.Log($"ゲームセンターのアルバイトの日給は{receiveWorkSalary.ReceiveWorkSalary()}円です。");
}
}
}
ここからAdapterクラスについて少し解説します。
下記のアルバイトクラスには特定のインターフェース(IReceiveWorkSalary)を持っていない状態です。このクラス自体には変更加えずに、アダプタークラスで特定のインターフェースに対応します。
// 今回アダプターとして対応したいクラスで、IReceiveWorkSalaryインターフェースは未実装です
// ゲームセンターアルバイトクラス
public class GameCenterPartTimeWork
{
// 1時間辺りの時給
private int hourlyWage = 1200;
/// <summary>
/// 1時間当たりの給料を返す
/// </summary>
/// <param name="workHours">働いた時間</param>
/// <returns>時間合計の給料</returns>
public int PerHourSalary(int workHours)
{
return workHours * hourlyWage;
}
}
先ほどのGameCenterPartTimeWorkの処理をIReceiveWorkSalaryで利用できるように、ラップしたクラスを作成します。このようにアダプタークラスでインターフェースに対応することによって、元のクラスには変更を行わずに対応することができます。
public class GameCenterWorkAdapter : IReceiveWorkSalary
{
// 特定のインタフェースに対応したいクラス
private GameCenterPartTimeWork gameCenterJob;
// 固定で8時間働く
private int workHours = 8;
public GameCenterWorkAdapter()
{
gameCenterJob = new();
}
/// <summary>
/// ゲームセンタークラスの給料取得処理を返す
/// </summary>
public int ReceiveWorkSalary()
{
return gameCenterJob.PerHourSalary(workHours);
}
}
「特定のインターフェースに対応できる」「元のクラスは変更が必要ない」という内容から、後からインターフェースの処理だけを差し替えたり、新しい処理を追加したりできそうですね。
下は修正前の内容
既に完成したGameCenterJobには何も変更せず、BonusGameCenterJobクラスでラップして追加処理を含めたり、要件に合わせて改良しています。Adapterパターンの利用タイミングとしては、既存の物を変更せずに再利用したい時や、元の機能を利用して新しい機能を実装する時に利用する考えなので、ゲームが完成した後のブラッシュアップで追加機能を作るとかに向いてそうですね。
メリット
・対応したいクラス自体は変更しないこと
対応したいクラス自体に変更を行わないので、既存クラスも利用可能かつインターフェースに合わせて自由に改良できる。
・他人のクラスも利用できる
1つ上の内容と被っているけど、クラス自体を変更しないことから他人のクラスも利用できる。
・異なるインターフェースに対応できる
処理が異なる内容でも、アダプターでラップする時に変更を加えたり変換することで異なるインターフェースに対応できる
下は修正前の内容
・既に完成している機能を改良できる
自分が利用したいと考えている要件に合わせて既存機能を改良することができるので、柔軟に変更できて良いなと感じました。
・再開発の防止
「元からある機能を利用する」という考え方から、同じ様な機能実装を避けることができます。新しい機能や追加機能の実装に集中できるという部分はとても良いですね。
デメリット
・アダプターの依存関係が滅茶苦茶になる
特定のインターフェースに、対応するために特定のクラスを利用するので、アダプターのクラス参照が増えたり、特定のクラス自体が変更されるとエラーを巻き起こす原因になる。
・利用する場面があまり想像出来ていない
ゲーム開発でSDK_Aを利用していたところ、別のSDK_Bも追加で使うなんていう場面があったりします。
スマホゲームならSNS連携などがまさにこの事例ですね。
TwitterAPI, Facebook, LINE, Instagram... 特にビジネス都合でアプリ側に連携を対応してほしい
引用元:https://qiita.com/Cova8bitdot/items/f09776957073cf64902b
最近作っているゲームの、ChatGPTとかのAPIにメッセージを送ったりする処理の内容とか、androidのカメラ起動(スマホのカメラ)とかに便利かも...?ただ、使用用途がゲーム開発の終盤辺りくらいに多分使うかも?という印象しかない。
下は修正前の内容
・そもそも既存機能が無いと話しが始まらない
本当にタイトルのままなんですが、既存機能が無ければこのデザインパターンは力を発揮しないのが欠点です。
・Adapterの依存関係が滅茶苦茶になりそう
「元からある機能を利用する」ので、インターフェースや依存関係に気をつけないと色々な機能に依存する危険なクラスができそうだと感じました。
どんな時に使うべきか
・異なるインターフェースに特定のクラスを対応したい時
・元クラスは変更したくないけど、処理を追加したいという時
・ゲーム完成後のブラッシュアップや機能の差し替えを行う時に便利そう(実体験が無いから想像ではありますが...)