ソフトウェア開発は、相互に動作するコンポーネント間の非互換性によって複雑になることがよくあります。たとえば、新しいライブラリを以前のバージョンの Java で記述された古いプラットフォームと統合する必要がある場合、オブジェクト (より正確にはインターフェイス) の非互換性が発生する可能性があります。この場合どうすればよいでしょうか? コードを書き換えますか? しかし、これは不可能です。システムの分析に多くの時間がかかるか、作業の内部ロジックが壊れてしまいます。この問題を解決するために、互換性のないインターフェイスを持つオブジェクトが連携できるようにするアダプター パターンを考案しました。使い方を見てみましょう!
問題の詳細
まず、古いシステムの動作をシミュレーションしてみましょう。それが仕事や学校に遅刻する理由を生み出したとします。これを行うために、Excuse
メソッドと をgenerateExcuse()
含むインターフェースを用意します。 likeExcuse()
dislikeExcuse()
public interface Excuse {
String generateExcuse();
void likeExcuse(String excuse);
void dislikeExcuse(String excuse);
}
このインターフェースは次のクラスによって実装されますWorkExcuse
。
public class WorkExcuse implements Excuse {
private String[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
"искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все How в тумане.",
"предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
private String[] sorryOptions = {"Это, конечно, не повторится, мне очень жаль.", "Прошу меня извинить за непрофессиональное поведение.", "Нет оправдания моему поступку. Я недостоин этой должности."};
@Override
public String generateExcuse() { // Случайно выбираем отговорку из массива
String result = "Я сегодня опоздал, потому что " + reasonOptions[(int) Math.round(Math.random() + 1)] + "\n" +
sorryOptions[(int) Math.round(Math.random() + 1)];
return result;
}
@Override
public void likeExcuse(String excuse) {
// Дублируем элемент в массиве, чтобы шанс его выпадения был выше
}
@Override
public void dislikeExcuse(String excuse) {
// Удаляем элемент из массива
}
}
例をテストしてみましょう:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
結論:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Прошу меня извинить за непрофессиональное поведение.
ここで、サービスを立ち上げて統計を収集し、サービス ユーザーの大半が大学生であることに気付いたと想像してみましょう。このグループのニーズに合わせて改善するために、あなたは別の開発者に彼女専用の言い訳生成システムを注文しました。開発チームは調査を実施し、評価をまとめ、人工知能を接続し、交通渋滞や天候などとの統合を追加しました。これで、学生向けの言い訳を生成するためのライブラリができましたが、それを操作するためのインターフェイスが異なりますStudentExcuse
。
public interface StudentExcuse {
String generateExcuse();
void dislikeExcuse(String excuse);
}
このインターフェイスにはgenerateExcuse
、言い訳を生成する と、dislikeExcuse
言い訳が今後表示されないようにブロックする の 2 つのメソッドがあります。サードパーティのライブラリは編集のために閉じられています。そのソース コードを変更することはできません。その結果、システムには、インターフェイスを実装する 2 つのクラスと、インターフェイスを実装するExcuse
クラスを含むライブラリが存在します。 SuperStudentExcuse
StudentExcuse
public class SuperStudentExcuse implements StudentExcuse {
@Override
public String generateExcuse() {
// Логика нового функционала
return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки or сбои в расписании общественного транспорта.";
}
@Override
public void dislikeExcuse(String excuse) {
// Добавляет причину в черный список
}
}
コードは変更できません。現在のスキームは次のようになります。 このバージョンのシステムは Excuse インターフェイスでのみ動作します。コードを書き直すことはできません。大規模なアプリケーションでは、このような変更には長い時間がかかったり、アプリケーションのロジックが壊れたりする可能性があります。メイン インターフェイスを導入して階層を増やすことを提案できます。 これを行うには、インターフェイスの名前を変更する必要がありますExcuse
。しかし、本格的なアプリケーションでは階層の追加は望ましくありません。共通のルート要素を導入するとアーキテクチャが破壊されます。損失を最小限に抑えながら新旧の機能を使用できるようにする中間クラスを実装する必要があります。つまり、アダプターが必要です。
アダプター パターンの仕組み
アダプターは、あるオブジェクトのメソッドを別のオブジェクトが理解できるように呼び出す中間オブジェクトです。この例のアダプターを実装して、それを と呼びましょうMiddleware
。アダプターは、いずれかのオブジェクトと互換性のあるインターフェースを実装する必要があります。なるがままにExcuse
。これにより、Middleware
最初のオブジェクトのメソッドを呼び出すことができます。 Middleware
呼び出しを受信し、互換性のある形式で 2 番目のオブジェクトに渡します。Middleware
メソッドとメソッドを使用したメソッドの実装は次のようになりgenerateExcuse
ますdislikeExcuse
。
public class Middleware implements Excuse { // 1. Middleware становится совместимым с an objectом WorkExcuse через интерфейс Excuse
private StudentExcuse superStudentExcuse;
public Middleware(StudentExcuse excuse) { // 2. Получаем ссылку на адаптируемый an object
this.superStudentExcuse = excuse;
}
@Override
public String generateExcuse() {
return superStudentExcuse.generateExcuse(); // 3. Адаптер реализовывает метод интерфейса
}
@Override
public void dislikeExcuse(String excuse) {
// Метод предварительно помещает отговорку в черный список БД,
// Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
}
// Метод likeExcuse появятся позже
}
テスト (クライアント コード内):
public class Test {
public static void main(String[] args) {
Excuse excuse = new WorkExcuse(); // Создаются an objectы классов,
StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
System.out.println("Обычная причина для работника:");
System.out.println(excuse.generateExcuse());
System.out.println("\n");
Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в an object-адаптер
System.out.println("Использование нового функционала с помощью адаптера:");
System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
}
}
結論:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности.
Использование нового функционала с помощью адаптера
現在の気象状況、交通渋滞、公共交通機関のダイヤの乱れに合わせた、信じられない言い訳です。このメソッドは、generateExcuse
追加の変換を行わずに、呼び出しを別のオブジェクトに転送するだけです。この方法でdislikeExcuse
は、まずデータベースのブラックリストに言い訳を登録する必要がありました。追加の中間データ処理が、アダプター パターンが愛される理由です。likeExcuse
しかし、インターフェースにはあるがExcuse
インターフェースにはないメソッドはどうなるでしょうかStudentExcuse
? この操作は新機能ではサポートされていません。UnsupportedOperationException
このケースでは、要求された操作がサポートされていない場合に例外がスローされるという例外が考えられました。これを使ってみましょう。新しいクラスの実装は次のようになりますMiddleware
。
public class Middleware implements Excuse {
private StudentExcuse superStudentExcuse;
public Middleware(StudentExcuse excuse) {
this.superStudentExcuse = excuse;
}
@Override
public String generateExcuse() {
return superStudentExcuse.generateExcuse();
}
@Override
public void likeExcuse(String excuse) {
throw new UnsupportedOperationException("Метод likeExcuse не поддерживается в новом функционале");
}
@Override
public void dislikeExcuse(String excuse) {
// Метод обращается за дополнительной информацией к БД,
// Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
}
}
一見すると、このソリューションは成功しているように見えませんが、機能をシミュレートすると、より複雑な状況が発生する可能性があります。クライアントが注意深く、アダプターが十分に文書化されている場合、この解決策は受け入れられます。
アダプターを使用する場合
-
サードパーティのクラスを使用する必要があるが、そのインターフェイスがメイン アプリケーションと互換性がない場合。上の例は、ターゲット オブジェクトが理解できる形式で呼び出しをラップする shim オブジェクトがどのように作成されるかを示しています。
-
複数の既存のサブクラスに共通の機能が必要な場合。追加のサブクラス (サブクラスの作成によりコードの重複が発生します) を追加する代わりに、アダプターを使用することをお勧めします。
長所と短所
利点:アダプターは、あるオブジェクトから別のオブジェクトへの処理リクエストの詳細をクライアントから隠します。クライアント コードは、データのフォーマットやターゲット メソッドへの呼び出しの処理については考慮しません。複雑すぎるし、プログラマーは怠け者です :) 欠点:プロジェクトのコード ベースは追加のクラスによって複雑になり、互換性のない点が多数ある場合、その数は制御不能なサイズに増加する可能性があります。ファサードとデコレータを混同しないでください
表面的に調べると、Adapter は Façade および Decorator パターンと混同される可能性があります。アダプターとファサードの違いは、ファサードが新しいインターフェイスを導入し、サブシステム全体をラップすることです。さて、デコレータはアダプタとは異なり、インターフェイスではなくオブジェクト自体を変更します。段階的な実装アルゴリズム
-
まず、このパターンで解決できる問題があることを確認してください。
-
別のクラスが使用されるクライアント インターフェイスを定義します。
-
前の手順で定義したインターフェイスに基づいてアダプター クラスを実装します。
-
アダプター クラスで、オブジェクトへの参照を格納するフィールドを作成します。この参照はコンストラクターに渡されます。
-
すべてのクライアント インターフェイス メソッドをアダプターに実装します。このメソッドでは次のことが可能です。
-
通話を変更せずに転送します。
-
データの変更、対象メソッドの呼び出し回数の増減、データの構成のさらなる拡張など。
-
特定のメソッドに互換性がない場合の最後の手段として、UnsupportedOperationException をスローします。これは厳密に文書化する必要があります。
-
-
アプリケーションがクライアント インターフェイス経由でのみアダプターを使用する場合 (上記の例のように)、これにより、将来アダプターを問題なく拡張できるようになります。
GO TO FULL VERSION