Adapter Pattern の使用をチームに示したいと思います。私は多くの本や記事をオンラインで読みました。誰もがコンセプトを理解するのに役立つ例(形状、メモリカード、電子アダプタなど)を引用していますが、実際のケーススタディはありません。
アダプタパターンのケーススタディを教えてください。
追伸stackoverflowで既存の質問を検索しようとしましたが、答えが見つからなかったため、新しい質問として投稿しました。これに対する回答が既にあることがわかっている場合は、リダイレクトしてください。
アダプターの多くの例は、些細なまたは非現実的です( 長方形vs.レガシー長方形、ラチェットvs.ソケット 、 SquarePeg vs RoundPeg 、 ダックvs.トルコ )。さらに悪いことに、多くの場合、異なるアダプタのmultiple Adaptersが表示されません( アダプタパターンの例としてJavaのArrays.asListを引用した人 )。 1つのクラスのみのインターフェイスを別のインターフェイスと連動させることは、GoFアダプターパターンの弱い例のようです。このパターンは継承とポリモーフィズムを使用しているため、さまざまなアダプター用のアダプターの複数の実装を示す良い例が期待されます。
私が見つけた最良の例は MLとパターンの適用:オブジェクト指向分析と設計と反復開発(第3版)の紹介 の26章にあります。以下の画像は、本のFTPサイトで提供されているインストラクター資料からのものです。
最初の例では、機能的に類似している(税計算機、会計モジュール、クレジット認証サービスなど)が、APIが異なる複数の実装(アダプター)をアプリケーションが使用する方法を示します。ドメインレイヤーコードをハードコーディングして、税の計算、販売後の処理、クレジットカードリクエストの承認など、さまざまな方法を処理するのを避けたいと思います。これらはすべて異なる外部モジュールであり、変更することはできませんコード。アダプターを使用すると、アダプターでハードコーディングを行うことができますが、ドメインレイヤーコードは常に同じインターフェイス(IWhateverAdapterインターフェイス)を使用します。
上記の図には実際のアダプターは表示されていません。ただし、次の図は、IAccountingAdapterインターフェースでpostSale(...)
の多態的な呼び出しがどのように行われるかを示しており、SAPシステムへのSOAP)による販売の転記になります。
フランス人を普通の人に変える方法...
public interface IPerson
{
string Name { get; set; }
}
public interface IFrenchPerson
{
string Nom { get; set; }
}
public class Person : IPerson
{
public string Name { get; set; }
}
public class FrenchPerson : IFrenchPerson
{
public string Nom { get; set; }
}
public class PersonService
{
public void PrintName(IPerson person)
{
Debug.Write(person.Name);
}
}
public class FrenchPersonAdapter : IPerson
{
private readonly IFrenchPerson frenchPerson;
public FrenchPersonAdapter(IFrenchPerson frenchPerson)
{
this.frenchPerson = frenchPerson;
}
public string Name
{
get { return frenchPerson.Nom; }
set { frenchPerson.Nom = value; }
}
}
例
var service = new PersonService();
var person = new Person();
var frenchPerson = new FrenchPerson();
service.PrintName(person);
service.PrintName(new FrenchPersonAdapter(frenchPerson));
インターフェイスを別のインターフェイスに変換します。
アダプタパターンの実際の例
電源を接続するために、世界中にさまざまなインターフェイスがあります。アダプタを使用すると、賢明なように簡単に接続できます。
analog data
からdigit data
への変換をシミュレートする例を次に示します。
浮動小数点データをバイナリデータに変換するアダプターを提供しますが、実際にはおそらく役に立たず、アダプターパターンの概念を説明するのに役立ちます。
AnalogSignal.Java
package eric.designpattern.adapter;
public interface AnalogSignal {
float[] getAnalog();
void setAnalog(float[] analogData);
void printAnalog();
}
DigitSignal.Java
package eric.designpattern.adapter;
public interface DigitSignal {
byte[] getDigit();
void setDigit(byte[] digitData);
void printDigit();
}
FloatAnalogSignal.Java
package eric.designpattern.adapter;
import Java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FloatAnalogSignal implements AnalogSignal {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private float[] data;
public FloatAnalogSignal(float[] data) {
this.data = data;
}
@Override
public float[] getAnalog() {
return data;
}
@Override
public void setAnalog(float[] analogData) {
this.data = analogData;
}
@Override
public void printAnalog() {
logger.info("{}", Arrays.toString(getAnalog()));
}
}
BinDigitSignal.Java
package eric.designpattern.adapter;
import Java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BinDigitSignal implements DigitSignal {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private byte[] data;
public BinDigitSignal(byte[] data) {
this.data = data;
}
@Override
public byte[] getDigit() {
return data;
}
@Override
public void setDigit(byte[] digitData) {
this.data = digitData;
}
@Override
public void printDigit() {
logger.info("{}", Arrays.toString(getDigit()));
}
}
AnalogToDigitAdapter.Java
package eric.designpattern.adapter;
import Java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Adapter - convert analog data to digit data.
* </p>
*
* @author eric
* @date Mar 8, 2016 1:07:00 PM
*/
public class AnalogToDigitAdapter implements DigitSignal {
public static final float DEFAULT_THRESHOLD_FLOAT_TO_BIN = 1.0f; // default threshold,
private Logger logger = LoggerFactory.getLogger(this.getClass());
private AnalogSignal analogSignal;
private byte[] digitData;
private float threshold;
private boolean cached;
public AnalogToDigitAdapter(AnalogSignal analogSignal) {
this(analogSignal, DEFAULT_THRESHOLD_FLOAT_TO_BIN);
}
public AnalogToDigitAdapter(AnalogSignal analogSignal, float threshold) {
this.analogSignal = analogSignal;
this.threshold = threshold;
this.cached = false;
}
@Override
public synchronized byte[] getDigit() {
if (!cached) {
float[] analogData = analogSignal.getAnalog();
int len = analogData.length;
digitData = new byte[len];
for (int i = 0; i < len; i++) {
digitData[i] = floatToByte(analogData[i]);
}
}
return digitData;
}
// not supported, should set the inner analog data instead,
@Override
public void setDigit(byte[] digitData) {
throw new UnsupportedOperationException();
}
public synchronized void setAnalogData(float[] analogData) {
invalidCache();
this.analogSignal.setAnalog(analogData);
}
public synchronized void invalidCache() {
cached = false;
digitData = null;
}
@Override
public void printDigit() {
logger.info("{}", Arrays.toString(getDigit()));
}
// float -> byte convert,
private byte floatToByte(float f) {
return (byte) (f >= threshold ? 1 : 0);
}
}
AdapterTest.Java
package eric.designpattern.adapter.test;
import Java.util.Arrays;
import junit.framework.TestCase;
import org.junit.Test;
import eric.designpattern.adapter.AnalogSignal;
import eric.designpattern.adapter.AnalogToDigitAdapter;
import eric.designpattern.adapter.BinDigitSignal;
import eric.designpattern.adapter.DigitSignal;
import eric.designpattern.adapter.FloatAnalogSignal;
public class AdapterTest extends TestCase {
private float[] analogData = { 0.2f, 1.4f, 3.12f, 0.9f };
private byte[] binData = { 0, 1, 1, 0 };
private float[] analogData2 = { 1.2f, 1.4f, 0.12f, 0.9f };
@Test
public void testAdapter() {
AnalogSignal analogSignal = new FloatAnalogSignal(analogData);
analogSignal.printAnalog();
DigitSignal digitSignal = new BinDigitSignal(binData);
digitSignal.printDigit();
// adapter
AnalogToDigitAdapter adAdapter = new AnalogToDigitAdapter(analogSignal);
adAdapter.printDigit();
assertTrue(Arrays.equals(digitSignal.getDigit(), adAdapter.getDigit()));
adAdapter.setAnalogData(analogData2);
adAdapter.printDigit();
assertFalse(Arrays.equals(digitSignal.getDigit(), adAdapter.getDigit()));
}
}
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
単体テストを実行するだけです。
アダプタパターンは、2つの互換性のないインターフェイス間のブリッジとして機能します。このパターンには、2つの独立したインターフェースまたは互換性のないインターフェース間の通信を担当するアダプターと呼ばれる単一のクラスが含まれます。
実際の例としては、言語翻訳者やモバイル充電器などがあります。このYouTubeビデオの詳細:
1つの実際の例はQt-Dbusです。
Qt-dbusには、提供されたxmlファイルからアダプターとインターフェースコードを生成するユーティリティがあります。手順は次のとおりです。
1. Create the xml file - this xml file should have the interfaces
that can be viewed by the qdbus-view in the system either on
the system or session bus.
2.With the utility - qdbusxml2cpp , you generate the interface adaptor code.
This interface adaptor does the demarshalling of the data that is
received from the client. After demarshalling, it invokes the
user defined - custom methods ( we can say as adaptee).
3. At the client side, we generate the interface from the xml file.
This interface is invoked by the client. The interface does the
marshalling of the data and invokes the adaptor interface. As told
in the point number 2, the adaptor interface does the demarshalling
and calls the adaptee - user defined methods.
Qt-Dbusの完全な例をこちらでご覧いただけます-
http://www.tune2wizard.com/linux-qt-signals-and-slots-qt-d-bus/
インジェクション攻撃に対する防御として使用されるアダプタパターンのPHP実装をここで見つけることができます:
http://www.php5dp.com/category/design-patterns/adapter-composition/
アダプタパターンの興味深い側面の1つは、多重継承に依存するクラスアダプタと構成に依存するオブジェクトアダプタの2つのフレーバがあることです。上記の例は、構成に依存しています。
似たような振る舞いを持つ異なるインターフェース(通常、似た振る舞いをするがメソッドが異なるクラスを意味する)を扱う必要がある場合、アダプター設計パターンを使用できます。たとえば、Samsung TVに接続するクラスと、Sony TVに接続するクラスがあります。メニューを開く、再生を開始する、ネットワークに接続するなどの一般的な動作を共有しますが、各ライブラリには異なる実装(異なるメソッド名とシグネチャ)があります。これらの異なるベンダー固有の実装は、UML図ではAdapteeと呼ばれます。
そのため、コードでは(UML図ではClientと呼ばれます)、各ベンダーのメソッド呼び出しをハードコードする代わりに(またはAdaptee)、汎用インターフェイス(UMLダイアグラムではTargetと呼ばれる)を作成してこれらをラップすることができます同様の動作と1つのタイプのオブジェクトのみで動作します。
Adaptersは、メソッド呼び出しを委任するTargetインターフェイスを実装しますAdapteesは、コンストラクタを介してAdaptersに渡されます。
Javaコードでこれを実現するために、複数のスマートTVインターフェイスを処理するアダプターを使用して、上記とまったく同じ例を使用して非常に単純なプロジェクトを作成しました。説明のため、実際の実装がどのようになるかを確認してください。
コードをダウンロードして、MavenプロジェクトとしてEclipse(またはお気に入りのIDE)にインポートするだけです。 org.example.Main.Javaを実行してコードを実行できます。ここで重要なことは、パターンを設計するためにクラスとインターフェースがどのように組み立てられるかを理解することです。また、パッケージcom.thirdparty.libsに偽のAdapteesを作成しました。それが役に立てば幸い!
アダプター設計パターンの実際の例は多数あります
Mobile/Chargerアダプター、配水管レデューサー、言語トランスレーターなどの詳細については、 Javaのアダプター設計パターン をご覧ください。
実際の例は、アプリケーション内のドキュメントのレポートです。ここにある単純なコード。
プログラミング構造に非常に役立つと思うアダプター。
class WordAdaptee implements IReport{
public void report(String s) {
System.out.println(s +" Word");
}
}
class ExcellAdaptee implements IReport{
public void report(String s) {
System.out.println(s +" Excel");
}
}
class ReportAdapter implements IReport{
WordAdaptee wordAdaptee=new WordAdaptee();
@Override
public void report(String s) {
wordAdaptee.report(s);
}
}
interface IReport {
public void report(String s);
}
public class Main {
public static void main(String[] args) {
//create the interface that client wants
IReport iReport=new ReportAdapter();
//we want to write a report both from Excel and world
iReport.report("Trial report1 with one adaptee"); //we can directly write the report if one adaptee is avaliable
//assume there are N adaptees so it is like in our example
IReport[] iReport2={new ExcellAdaptee(),new WordAdaptee()};
//here we can use Polymorphism here
for (int i = 0; i < iReport2.length; i++) {
iReport2[i].report("Trial report 2");
}
}
}
結果は次のようになります。
Trial report1 with one adaptee Word
Trial report 2 Excel
Trial report 2 Word
Judith Bishop著の「C#3.0 Design Patterns」本によると、Appleはアダプタパターンを使用してMac OSをIntel製品で動作するように適合させました(第4章4抜粋で説明 2 )
変更できないが、使用する必要があるインターフェイスがある場合は、アダプターを使用します。あなたはオフィスの新しい人であり、白髪をあなたのルールに合わせることができないので、それを見てください-あなたは彼らに順応しなければなりません。これは、ユーザーインターフェイスが与えられたときに作業した実際のプロジェクトの実際の例です。
ファイル内のすべての行をListデータ構造に読み取り、グリッドに表示するアプリケーションがあります(基になるデータストアインターフェイスIDataStoreを呼び出しましょう)。ユーザーは、「最初のページ」、「前のページ」、「次のページ」、「最後のページ」ボタンをクリックして、これらのデータをナビゲートできます。すべてが正常に動作します。
ここで、アプリケーションは、メモリに読み込むには大きすぎる実稼働ログで使用する必要がありますが、ユーザーはそれをナビゲートする必要があります! 1つの解決策は、最初のページ、次のページ、前のページ、および最後のページを保存するキャッシュを実装することです。ユーザーが[次のページ]をクリックすると、キャッシュからページが返され、キャッシュが更新されます。最後のページをクリックすると、キャッシュから最後のページが返されます。バックグラウンドでは、すべての魔法を行うファイルストリームがあります。そうすることで、ファイル全体ではなく、メモリ内に4ページしかありません。
アダプタを使用して、ユーザーに気付かれずにこの新しいキャッシュ機能をアプリケーションに追加できます。現在のIDataStoreを拡張し、CacheDataStoreと呼びます。ロードするファイルが大きい場合、CacheDataStoreを使用します。 First、Next、Previous、Lastページのリクエストを行うと、情報はキャッシュにルーティングされます。
明日、上司はデータベーステーブルからファイルの読み取りを開始したいと考えています。 Cacheの場合と同様に、IDataStoreをSQLDataStoreに拡張し、バックグラウンドで接続を設定するだけです。 [次のページ]をクリックすると、必要なSQLクエリを生成して、データベースから次の数百行をフェッチします。
基本的に、アプリケーションの元のインターフェイスは変更されていません。従来のインターフェイスを維持しながら、最新のクールな機能を単純に機能させて動作させました。
@Justice oの例では、アダプターパターンについて明確に説明していません。彼の答えを拡張します-コンシューマコードが使用する既存のインターフェイスIDataStoreがあり、それを変更することはできません。ここで、実装したいことを行うXYZライブラリのクールな新しいクラスを使用するように求められますが、そのクラスを変更してIDataStoreを拡張することはできません。新しいクラス-ADAPTERを作成し、コンシューマコードが期待するインターフェイス、つまりIDataStoreを実装し、機能が必要なライブラリのクラスを使用することにより、ADAPTEEをADAPTERのメンバーとして使用して、目的を達成できます。