2つの基盤となるAPIを1つの新しいAPIにバンドルするためのインターフェイスを書いています。 APIは、アーカイブされた請求書に関するデータを提供します。
3つのAPI(2つの古いAPIと私の新しいAPI)はすべて異なるデータ構造を持っています。
例えば: <INVOICE_NO>
(古いXML)、"number"
(古いJSON)、"formatted_number"
(新しいJSON)はすべて同じフィールドを意味します。
各APIのクラスを作成し、SpringのRestTemplate
に応答の解析/フォーマットを処理させました。
ここで、最初のAPIからClassA
オブジェクトを受け取り、2番目のAPIからClassB
オブジェクトを受け取った後、それらをClassC
オブジェクトに変換し、その形式で返す必要があります。
私の最初のアプローチは、ClassC
の2つのコンストラクターを作成することでした。これらのコンストラクターは、ClassA
型のオブジェクトまたはClassB
型のオブジェクトを引数として受け取ります。
ただし、2つのデータ転送オブジェクトを結合しているため、これが正しいことかどうかはわかりません。
InvoiceConverter
クラスを作成したほうがよいでしょうか、それとも何か他のものを作成した方がよいでしょうか?
これは Adapter Pattern を使用する完全に良い例のようです
基本的には、1つのメソッドを持つ1つのインターフェースを作成します。
public interface Adapter
{
ClassC ConvertObject();
}
次に、次のように、このインターフェイスを実装する2つのクラスを作成します。
class AdapterClass : Adapter
{
private ClassA adaptee;
public AdapterClassA(ClassA pAdaptee)
{
adaptee = pAdaptee;
}
public ClassC ConvertObject()
{
//Code that converts ClassA to ClassC
}
}
class AdapterClassB : Adapter
{
private ClassB adaptee;
public AdapterClassB(ClassB pAdaptee)
{
adaptee = pAdaptee;
}
public ClassC ConvertObject()
{
//Code that converts ClassB to ClassC
}
}
このようにして、型変換のロジックを異なるクラスに分離し、同じインターフェースをユーザークラスに残します。
ここで、ClassC以外の誰もがアダプタクラスのコンストラクタを呼び出さない場合に、不必要にインターフェイスを作成することで問題が発生する可能性があります。誰がコンストラクタを呼び出すかという理由だけでは、インターフェースはありません。たとえば、このようなものがあれば、単体テストを作成する方が簡単です。
次に、クラスを完全に分離し、コンストラクターの呼び出しをいくつかのファクトリークラスに移動することができます。ClassCはアダプターインターフェースを参照するだけで、ClassAまたはClassBにまったく依存しません。もちろん、これは KISSの原則 の違反でいちゃつくですが、私はそれを判決の呼びかけと呼びます。
つまり、ClassCをClassAおよびClassBから切り離したい場合は、アダプタークラスをバインドするものが必要です。抽象親クラス、通常の親クラス、インターフェースのいずれでもかまいません。親エンティティが情報を持たないことを考えると、インターフェースを使用するのが論理的です。
しかし、2つのデータ転送オブジェクトを結合しているので、それがいいことかどうかはわかりません。
はい、これはコードのにおいのようです。
InvoiceConverterクラスを作成するのが良いでしょうか?
多分そう。私があなたを正しく理解していれば、あなたは次のようなデータフローになります
[class A or B object] -> [Converter] -> [C object] -> [code using C]
そしてConverter
は、API AおよびBに直接依存するコード内の唯一の場所ですが、CオブジェクトおよびCオブジェクトを使用するコードは、AまたはBに依存しなくなります(おそらくここでの目標は、システムのさまざまな部分を分離することです)。
ただし、CオブジェクトをAおよびBに直接依存させて、個別のコンバータークラスを回避しようとすると、Cオブジェクトを使用するすべてのコードはAおよびBに依存します。これはおそらく回避したいものです。 Cを使用して(コンパイル済み言語を使用して)別のライブラリーにコードを配置する場合の意味を考えてください。 2番目のケースでは、このライブラリはAおよびBのAPIにリンクする必要がありますが、1番目のケースでは、ライブラリはAまたはBに対するリンクを必要としません。
あなたは正しい、あなたはコンバーターを必要としています。そして確かに、すべての変換に対して特定のインターフェースを作成したくはありません。それがジェネリックの目的です。 Javaの構文はわかりませんが、C#のように実行できることはかなりわかります。
最初に、あるタイプから別のタイプへの変換を処理するインターフェースを作成します。
public interface IObjectConverter<TSource, TResult>{
TResult Convert(TSource source);
}
ただし、変換を行うたびにこれを参照したくないので、次のような別のインターフェースを作成します。
public interface IConverter(){
TResult Convert<TSource,TResult>(TSource source);
IObjectConverterFactory Factory;
}
ここには別のインターフェイス、IObjectConverterFactoryがあることに注意してください。 IConverter.Convertの実装でこれを使用して、IObjectConverterオブジェクトを取得します。ファクトリーはこのようなものです:
public interface IConverterFactory
{
IObjectConverter<TSource, TResult> Create<TSource, TResult>();
}
したがって、3つのインターフェースがあり、多くのように見えるかもしれませんが、ここでの考え方は、使用を容易にすることです(カップリングを増やすことではありません)。 IConverterを参照し、Convertメソッドにソースタイプと結果タイプを通知するだけで済みます。
宣言するのは以下のみです。
IConverter _converter;
次のように使用します。
var classC1 = _converter.Convert<ClassA,ClassC>(classA);
var classC2 = _converter.Convert<ClassB,ClassC>(classB);
そこからは、コンバーターの特定の実装について心配するだけで済みます。