no_mkd
正式开始する前に、いくつかの問題を考えてみましょう:
- 既存の新しいプロジェクトで、古いプロジェクトの大量のレガシーコードを利用できる場合、最初から新しいプロジェクトを完成させるつもりですか、それとも古いプロジェクトのモジュール機能とインターフェースを理解するつもりですか?
- レガシーコードを理解した後、いくつかの重要な機能モジュールのインターフェースが異なる(それらは複数の古いプロジェクトから来ている可能性があるため)ため、直接再利用できないことがわかった場合、レガシーコードの使用を諦めるつもりですか?
- 諦めるつもりがないなら(そうすべきです。レガシーコードの正しさは実践によって検証されています)、残りの n - 1 個のインターフェースを書き換えるしかありませんか?あるいはすべての n 個のインターフェースを書き換えるしかありませんか?
- そうしない場合、他に簡単な方法はありますか?
一.アダプターパターンとは?
まず、アダプターが何であるかを知る必要があります。はい、ノートパソコンの電源アダプターを聞いたことがありますか?これは 220V の交流電気をノートパソコンが必要とする 15V の直流電気に変換できます。素晴らしいことです。小さな電源アダプターが、家庭用電気とノートパソコンが必要とする電気のタイプの不一致という問題を解決しました。何か気づきましたか?そうです。私たちは家庭用電気を変えたわけでも(15V 直流電気に変換)、ノートパソコンを変えたわけでも(220V 交流電気に変換)ありませんが、確かにこの問題を解決しました
アダプターパターン——異なるインターフェースの変換を実現するためのデザインパターン
二.例を挙げる
2 つのカプセル化された機能モジュールがあると仮定しますが、それらが必要とするパラメータが異なります(パラメータの実質は同じ種類のオブジェクトですが)
例えば、A モジュール(テキストチェックモジュール)は以下の通りです:
package AdapterPattern;/**
-
@author ayqy
-
テキストチェックモジュール(MSOffice Word の「スペルと文法チェック」に類似) */ public class TextCheckModule { FormatText text;
public TextCheckModule(FormatText text){ this.text = text; }
/*
- 多くの具体的な Check 操作を省略。。 */ }
A モジュールの入り口には FormatText タイプのパラメータが必要です。その定義は以下の通りです:
package AdapterPattern;/**
-
@author ayqy
-
フォーマット済みテキストの定義 */ public interface FormatText { String text = null;
/**
- @return テキストの論理行数 */ public abstract int getLineNumber();
/**
- @param index 行番号
- @return index 行目の内容 */ public abstract String getLine(int index);
/*
- 他の有用なメソッドを省略 */ }
B モジュール(テキスト表示モジュール)もあります。これは以下の通りです:
package AdapterPattern;/**
-
@author ayqy
-
テキスト表示モジュール */ public class TextPrintModule { DefaultText text;
public TextPrintModule(DefaultText text){ this.text = text; }
/*
- 多くの表示関連操作を省略
- */ }
B モジュールの入り口に必要な DefaultText:
package AdapterPattern;/**
-
@author ayqy
-
デフォルトテキストの定義 */ public interface DefaultText { String text = null;
/**
- @return テキストの論理行数 */ public abstract int getLineCount();
/**
- @param index 行番号
- @return index 行目の内容 */ public abstract String getLineContent(int index);
/*
- 他の有用なメソッドを省略 */ }
新しいプロジェクトでは、ワードプロセッシングプログラム(MSOffice Word のような)の実装が必要です。テキストチェック機能を実装するために A モジュールを呼び出す必要があり、テキスト表示機能を実装するために B モジュールを呼び出す必要があります。しかし問題は、2 つのモジュールのインターフェースが一致しないため、既存の A と B を直接再利用できないことです。。
この時、私たちは 2 つの選択肢しかないようです:
- FormatText(または DefaultText)を修正して DefaultText(または FormatText)を満たし、A(または B)の内部実装も修正する必要があります
- 3 番目のインターフェース MyText を定義し、A と B を修正して、インターフェースを統一するために MyText をパラメータとして使用させます
もちろん、私たちは最初の選択肢を好むかもしれません。必要な修正が比較的少ないからです。しかしそれでも、作業量は依然として大きいです。A のカプセル化を開き、その内部実装を理解し、メソッド呼び出しの詳細を修正する必要があります。実際には、より良い選択肢があります——Adapter を定義して、FormatText から DefaultText への変換(またはその逆)を担当させます:
package AdapterPattern;/**
-
@author ayqy
-
デフォルトテキストアダプターの定義 */ public class DefaultTextAdapter implements DefaultText{ FormatText formatText = null;//ソースオブジェクト
/**
- @param text 変換が必要なソーステキストオブジェクト */ public DefaultTextAdapter(FormatText formatText){ this.formatText = formatText; }
@Override public int getLineCount() { int lineNumber;
lineNumber = formatText.getLineNumber(); /* * ここで追加の変換処理を追加 * */ return lineNumber;}
@Override public String getLineContent(int index) { String line;
line = formatText.getLine(index); /* * ここで追加の変換処理を追加 * */ return line;} }
私たちのやり方は非常に簡単です:
- Adapter を定義してターゲットインターフェースを実装します
- ソースインターフェースオブジェクトを取得して保持します
- ターゲットインターフェースの各メソッドを実装します(メソッド本体でソースインターフェースオブジェクトのメソッドを呼び出し、変換を実現するための追加処理を追加します)
アダプターが完成しました。どのように使用すればよいでしょうか?Test クラスを実装してテストしてみましょう:
package AdapterPattern;/**
-
@author ayqy
-
インターフェースアダプターのテスト */ public class Test implements FormatText{
public static void main(String[] args) { //ソースインターフェースオブジェクトの作成 FormatText text = new Test(); //テキストチェックモジュールオブジェクトの作成 TextCheckModule tcm = new TextCheckModule(text); /tcm を呼び出してテキストチェックを実装/
//アダプターオブジェクトの作成。ソースインターフェースオブジェクトからターゲットインターフェースオブジェクトへの変換を実行 DefaultTextAdapter textAdapter = new DefaultTextAdapter(text); //Adapter を使用してテキスト表示モジュールオブジェクトの作成 TextPrintModule tpm = new TextPrintModule(textAdapter); /*tcm を呼び出してテキスト表示を実装*/}
/下の怠けた部分を無視してください。。/ @Override public int getLineNumber() { // TODO Auto-generated method stub return 0; }
@Override public String getLine(int index) { // TODO Auto-generated method stub return null; }
}
(P.S. 私の怠け行為を許してください。FormatText がインターフェースであるのはなぜですか。。)
もちろん、Test には実行結果はありませんが、コンパイルを通過できれば、変換に問題がないことを十分に示せます。。
実際、私たちは非常に重要な問題を無視しました。例では、ソースインターフェースとターゲットインターフェースのメソッドが対応しています。言い換えれば:ソースインターフェースで定義されたメソッドは、ターゲットインターフェースに類似したメソッドが対応しています。もちろん、このような状況は非常に稀です。通常、メソッドが対応しない問題が存在します(ソースインターフェースにターゲットインターフェースで定義されていないメソッドが存在する、またはその逆の状況)
この時、私たちは 2 つの選択肢があります:
- 例外をスローしますが、注釈またはドキュメントで詳細な説明を行うべきです。次のように:
throw new UnsupportedOperationException();//ソースインターフェースはこの操作をサポートしていません
- 空の実装を完成します。例えば、return false、0、null など
どちらを選択するかは、具体的な状況によって異なります。それぞれに利点があり、一概には言えません
三.もう一つのアダプター実装方法
例では、「ソースインターフェースオブジェクトを保持し、ターゲットインターフェースを実装する」方法を使用してアダプターを実装しました。実際には、もう一つの方法が存在します——多重継承(または複数のインターフェースを実装する)
Adapter クラスが A インターフェースと B インターフェースの両方を実装している場合、間違いなく、Adapter オブジェクトは A タイプでもあり B タイプでもあります(多重継承の原理は類似しています。。)
Java は多重継承をサポートしていませんが、多重継承をサポートする言語環境では、このような実装方法を考えるべきです。その後、具体的な状況に応じて多重継承を使用して Adapter を実装するかどうかを決定します
四.まとめ
私たちが同時に 2 つ穴のプラグと 3 つ穴のソケットを持っている時、いつもプラグの芯を八字形に捻る習慣があります。なぜアダプターを買わないのでしょうか?
- プラグを破壊する必要もなければ、ソケットを破壊する必要もありません(コードの変更は確かに破壊的な場合があります。変更を避けることで破壊も避けました)
- より重要なのは:購入したアダプターを友人に貸すことができます(再利用可能)
コメントはまだありません