web-dev-qa-db-ja.com

APIを介した抽象化の作成

私は、共通のものがあるさまざまなAPIを抽象化する方法を理解しようとしています。たとえば、Android、Windows Phone、IOSを使用しているモバイルプラットフォームを見てみましょう。 APIまたはプログラムまたはドメイン固有の言語を作成したいとします。これにより、1つのプログラムを記述して、指定されたすべてのプラットフォームで作業することができます。もちろん、これは単なる例であり、結局のところ、すべてのプラットフォームには共通点が多く、抽象化についてあまり考えずにプログラムやライブラリを作成できます。たとえば、すべてのプラットフォームにCコンパイラがあります。より良い例はERPシステム、3つの異なるERPシステム、同じプラグインを作成したい場合、APIの上に抽象化を作成するにはどうすればよいでしょうか。一度書いて、それをコンパイルし(ビルドするか、なんでも呼べるように)、すべてのシステムで使用できるようにします。簡単にするために、これらのERPシステムはすべて同じプログラミング言語で記述していますが、APIには関数名などの点で共通点はほとんどありません。同様の機能しかありません。メタプログラミングの使用については、プログラムに自分が欲しいものを伝えるようにしています、およびAPIごとに異なる一連のルールを使用すると、APIごとにプログラムが出力されます。私が考えたもう1つのことは、すべてのAPIのコード(データ)を保持するクラスのすべての一般的な機能をカプセル化することです。指定されたAPIの機能を要求したときに出力します。例を挙げます。すべての印刷用のクラスを持つ3つのAPIがあるとします。 sは次のようになります。

  1. プリンター-PrintToLog(Data)
  2. 出力者-OutputToLog(Data、lenght)
  3. IO-PrntLogData(データ、logPtr)

簡単にするために、これらのクラスはすべて静的にします。私はこのようなクラスを作成できました(疑似コード)

Class Printer{
    api //holds for which API the code is requested
    constructor(API){
        this.api = API
    }
    func logData(Data){
        switch API{
            case API_1 //let's say the API are stored in ENUM or something similar
                return "PrintToLog(" + Data + ")"
            case API_2
                return "OutputToLog(" + Data + "," + len(Data) + ")" // len returns the lenght of data
            case API_3
                return "PrntLogData(" + Data + ", /default/log/location)"
        }
    }
}


しかし、私はそれらの方法がどれほど実用的で、信頼できるかわかりません。また、抽象化を作成するためのこれらのタイプに関する多くの情報を見つけることができませんでした。そのような抽象化のためのパターン、方法論、または原則のセットはありますか?

2
nameLess

これはソフトウェア開発における一般的な問題です。使用できる2つの手法があります。

  • すべての基本的な実装で共有される一般的なメソッドのみが含まれるようにインターフェースを設計できます。

    利点は、コードを変更する必要なく、ある実装から別の実装への移行が非常に簡単になることです。たとえば、共通のインターフェースは、GoogleとMicrosoftの両方のAPIでサポートされているメソッドを含みながら、GoogleマップまたはBingマップとの対話を可能にする場合があります。どちらのAPIも非常に似ているため、共通のインターフェースを作成することは比較的簡単ですが、それでも非常に優れた機能を維持できます。

  • 一部の実装でのみサポートされ、他の実装ではサポートされていない、または十分にサポートされていないメソッドを提供できます。これには、特定のアクションがサポートされているかどうかを呼び出し元が判断できる必要があります(使用中の実装が何であるかを必ずしも知る必要はありません)。

    利点は、一般的なサブセットよりもはるかに多くの機能を提供でき、一部の実装の長所を使用できることです。たとえば、一部の機能はiOSでより適切に実行され、その他の機能はAndroidでより適切に実行されます。一般的な機能をほとんど利用しない場合、それらの機能を利用することはできません。

実装

switchステートメントはあまり好きではありません。一般的な解決策は 継承を使用する です。この場合、実装ごとに 1つのアダプター を使用することを意味し、APIと基盤となるシステムまたはAPIとの間の通信を保証します。

依存性注入を使用すると、実行時に使用するアダプターを選択できるようになります。各アダプターは、他のアダプターから独立して変更できます。

例:

interface IGeolocation
{
    Coordinates addressToCoords(string address);
    string coordsToAddress(Coordinates position);
}

class GoogleMapsGeolocationAdapter : IGeolocation
{
    ... // Concrete implementation of addressToCoords and coordsToAddress.
        // Each method calls the Google's API.
}

class BingMapsGeolocationAdapter : IGeolocation
{
    ... // Concrete implementation of addressToCoords and coordsToAddress.
        // Each method calls the Microsoft's API.
}

住所から緯度と経度を取得する必要のあるクラスは、Dependency Injectionを介して渡された具体的な実装とは関係なく、コンストラクタにIGeolocationを渡してaddressToCoordsを呼び出す必要があるだけです。 。これはアプリケーション自体に属し、構成に基づいて、どのクラスを初期化する必要があるかを決定します:GoogleMapsGeolocationAdapterまたはBingMapsGeolocationAdapter

IOS対Androidの場合、実装の選択はプラットフォームに基づいて行われるか、アプリケーションのコンパイル中に行われます。

3

すでに述べたことを補足するものとして、私は function によるアプローチを使用します( functional プログラミングの意味ではなく、 "私が書いていることの目的は何ですか?」、「何をすべきか」)

つまり、3つのAPIに共通するものから始めようとするボトムアップアプローチ(帰納的)だけではありません。まあ、私はまず、可能な、難しい、または不可能なことを理解するために、まず低位の実装に慣れる必要があると思います。

But abstraction abstract を作成したいので、「具体的な現実、特定のオブジェクト、または実際のインスタンスから離れて考えた」 ")、あなたはそもそもそこになかった考えをそこに置くことを試みています。したがって、実際のインスタンスとは別に、エンティティに何らかの考えを当てる必要がありますそれ自体。経験則として、「抽象化のソースはインスタンスではなく、あなたにある

具体的には、Printer(またはそのようなもの)のクラスを作成します。

  1. 理想的には、そのプリンターをどのように動作させ、ニーズに正確に一致させたいですか?どのように説明しますか?ユースケース、どのメソッドとプロパティをエクスポートするかを考えます。

  2. 次に、のみ、手元にあるERP実装のそれぞれを使用してオブジェクトを実装する方法を計算します(通常、サブクラス化または切り替えによって)簡単なものもあれば、難しいものもあります。

  3. 解決できないことがあると感じた場合、または共通モデルを改善する機会が見られた場合は、後戻りしてください。

  4. 完了するまで交互に上下に作業します。

(特定のニーズに応じて)理想的なプリンターがどうあるべきかについて独自の見方がある場合は、実装によって偏ることはありません。必要のないもの(「念のため」)を実装することに夢中になることはありません。あなたのコードは短くて要点があります。そして、プロセスが適切に行われた場合、少なくとも独自のユースケースでは、元のAPIの10倍の使いやすさを備えた、すっきりとしたクリーンなAPIを提供することになります。

そして、開発者が各プラットフォームに提供するのに必要な時間を節約することとは別に、それは2番目のメリットです。

逆に、単に「最も一般的な分母」または「穴のある一般的な実装」を実装した場合、結果は別のlayerになり、それを削除するのではなく、さらに複雑さ(およびドキュメントの要件)を追加します。

2
fralau