前のレッスンでは、インターフェイスと抽象クラス、およびさまざまなプログラミング言語がこれら2つの文法概念をどのようにサポートおよび実装するかについて説明しました。今日、私たちは「インターフェース」に関連する知識ポイント、つまり実装ではなくインターフェースに基づくプログラミングについて話し続けます。この原則は非常に重要であり、コードの品質を向上させるための非常に効果的な方法であり、通常の開発で特によく使用されます。
この原則がどのように適用されるかを完全に理解し、真に習得するために、本日は、画像ストレージの実際の事例と併せて説明します。さらに、この原則は、各実装クラスに対応するインターフェイスを定義するなど、簡単に過剰に適用される可能性があります。このような問題について、本日の説明では、トレードオフの方法と、この原則を適切に適用する方法についても説明します。
言うまでもありませんが、今日の学習を正式に始めましょう!
原則として「インターフェース」という言葉をどのように解釈しますか?
「実装ではなくインターフェースに基づく」という原則の英語による説明は、「実装ではなくインターフェースへのプログラム」です。この原則を理解するとき、最初は特定のプログラミング言語に縛られてはならず、プログラミング言語の「インターフェース」文法(Javaのインターフェース文法など)に限定されてはなりません。この原則は、1994年にGoFの「DesignPatterns」の本に最初に登場しました。これは、多くのプログラミング言語(Java言語など)の前に生まれました。これは、比較的抽象的で一般的な設計アイデアです。
実際、この原則を理解するための鍵は、「インターフェース」という言葉を理解することです。前回のレッスンで説明した「インターフェース」の定義を覚えていますか?本質的に、「インターフェース」は「プロトコル」または「契約」のセットであり、関数プロバイダーによってユーザーに提供される「関数リスト」です。「インターフェース」は、アプリケーションのシナリオによって解釈が異なります。たとえば、サーバーとクライアント間の「インターフェース」、クラスライブラリによって提供される「インターフェース」、または通信プロトコルのセットでさえ「インターフェース」と呼ぶことができます。 。現在の「インターフェース」の理解は比較的高レベルで抽象的なものであり、実際のコード記述からは少し遠いです。特定のコーディングで実装されている場合、「実装プログラミングではなくインターフェイスに基づく」という原則の「インターフェイス」は、プログラミング言語のインターフェイスまたは抽象クラスとして理解できます。
前述したように、この原則はコード品質の向上に非常に効果的です。この原則を適用すると、インターフェイスと実装を分離し、不安定な実装をカプセル化し、安定したインターフェイスを公開できるためです。アップストリームシステムは、実装プログラミングではなくインターフェイス指向であり、不安定な実装の詳細に依存しないため、実装が変更された場合、カップリングを減らしてスケーラビリティを向上させるために、アップストリームシステムのコードを基本的に変更する必要はありません。
実際、「実装プログラミングではなくインターフェースに基づく」という原則を表現する別の方法は、「実装プログラミングではなく抽象化に基づく」ことです。後者の表現方法は、実際にはこの原則の本来の意図をよりよく反映しています。ソフトウェア開発における最大の課題の1つは、要件の絶え間ない変更です。これは、コード設計の品質をテストするための標準でもあります。**より抽象的で、トップレベルであり、デザインの特定の実装から外れているほど、コードはより柔軟になり、将来の要件の変更に対応できるようになります。優れたコード設計は、現在のニーズに対応できるだけでなく、元のコード設計を破壊することなく、将来の要件の変更に柔軟に対応できます。**抽象化は、コードのスケーラビリティ、柔軟性、および保守性を向上させるための最も効果的な手段の1つです。
この原則を実際の戦闘にどのように適用しますか?
この原則については、具体的な実際の事例と併せてさらに説明します。
私たちのシステムには、画像の処理と保存を含む多くのビジネスロジックがあると仮定します。写真は処理され、AlibabaCloudにアップロードされます。コードを再利用するために、画像ストレージに関連するコードロジックをカプセル化し、システム全体で使用できる統合されたAliyunImageStoreクラスを提供します。具体的なコードの実装は次のとおりです。
public class AliyunImageStore {
//...省略属性、构造函数等...
public void createBucketIfNotExisting(String bucketName) {
// ...创建bucket代码逻辑...
// ...失败会抛出异常..
}
public String generateAccessToken() {
// ...根据accesskey/secrectkey等生成access token
}
public String uploadToAliyun(Image image, String bucketName, String accessToken) {
//...上传图片到阿里云...
//...返回图片存储在阿里云上的地址(url)...
}
public Image downloadFromAliyun(String url, String accessToken) {
//...从阿里云下载图片...
}
}
// AliyunImageStore类的使用举例
public class ImageProcessingJob {
private static final String BUCKET_NAME = "ai_images_bucket";
//...省略其他无关代码...
public void process() {
Image image = ...; //处理图片,并封装为Image对象
AliyunImageStore imageStore = new AliyunImageStore(/*省略参数*/);
imageStore.createBucketIfNotExisting(BUCKET_NAME);
String accessToken = imageStore.generateAccessToken();
imagestore.uploadToAliyun(image, BUCKET_NAME, accessToken);
}
}
アップロードプロセス全体は、バケットの作成(ストレージディレクトリとして簡単に理解できます)、アクセストークンアクセス資格情報の生成、およびアクセストークンを含む画像の指定されたバケットへのアップロードの3つのステップで構成されます。コードの実装は非常にシンプルで、クラス内のいくつかのメソッドは非常に明確に定義されており、使用方法も非常に明確です。一見大きな問題はなく、AlibabaCloudに写真を保存するというビジネスニーズを完全に満たすことができます。
ただし、ソフトウェア開発における唯一の不変は変更です。しばらくして、独自のプライベートクラウドを構築しました。AlibabaCloudに写真を保存するのではなく、自己構築したプライベートクラウドに写真を保存します。このような需要の変化に対応するために、コードをどのように変更しますか?
プライベートクラウドに画像を格納するPrivateImageStoreクラスを再設計して実装し、それを使用してプロジェクト内のすべてのAliyunImageStoreクラスオブジェクトを置き換える必要があります。このような変更は複雑に聞こえません。単なる置き換えであり、コード全体はあまり変更されていません。しかし、「細部は悪魔だ」とよく言われます。この文は、特にソフトウェア開発に当てはまります。実際、デザインの実装では、問題が発生しやすい多くの「悪魔の詳細」が隠されています。それらを見てみましょう。
コードの変更を最小限に抑えながらAliyunImageStoreクラスを置き換えるには、新しいPrivateImageStoreクラスに対してどのようなメソッドを設計および実装する必要がありますか?これには、AliyunImageStoreクラスで定義されているすべてのパブリックメソッドをPrivateImageStoreクラスで1つずつ定義して再実装する必要があります。いくつか問題がありますので、以下の2点をまとめました。
まず、AliyunImageStoreクラスの一部の関数名は、uploadToAliyun()やdownloadFromAliyun()などの実装の詳細を公開します。この機能を開発する同僚がインターフェース認識と抽象的な思考を持っていない場合、実装の詳細を公開するこの命名方法は驚くべきことではありません。結局のところ、最初は写真をAlibabaCloudに保存することだけを検討しました。そして、「aliyun」という単語を含むこのメソッドをPrivateImageStoreクラスにコピーしましたが、これは明らかに不適切です。新しいクラスでuploadToAliyun()メソッドとdownloadFromAliyun()メソッドの名前を変更すると、プロジェクトでこれら2つのメソッドを使用するすべてのコードを変更する必要があり、コードの変更量が非常に多くなる可能性があります。
次に、Alibaba Cloudに写真を保存するプロセスは、プライベートクラウドに写真を保存するプロセスとまったく同じではない場合があります。たとえば、Alibaba Cloudの画像をアップロードおよびダウンロードするプロセスでは、アクセストークンを作成する必要がありますが、プライベートクラウドはアクセストークンを必要としません。AliyunImageStoreで定義されているgenerateAccessToken()メソッドをPrivateImageStoreにコピーできない一方で、AliyunImageStoreを使用して画像をアップロードおよびダウンロードする場合、コードはgenerateAccessToken()メソッドを使用します。プライベートクラウドのアップロードおよびダウンロードに変更する場合は、プロセス、これらのコードを調整する必要があります。
これらの2つの問題を解決する方法は?この問題を解決するための基本的な方法は、コードを書くときに「実装プログラミングではなくインターフェースに基づく」という原則に従うことです。具体的には、次の3つの点を実行する必要があります。
-
関数の名前から、実装の詳細を明らかにすることはできません。たとえば、前述のuploadToAliyun()は要件を満たしていないため、aliyunという単語を削除して、upload()などのより抽象的な命名方法に置き換える必要があります。
-
特定の実装の詳細をカプセル化します。たとえば、Alibaba Cloudに関連する特別なアップロード(またはダウンロード)プロセスは、発信者に公開しないでください。アップロード(またはダウンロード)プロセスをカプセル化し、すべてのアップロード(またはダウンロード)の詳細を呼び出し元にパッケージ化する方法を提供します。
-
実装クラスは、抽象インターフェースを定義します。特定の実装クラスはすべて、統一されたインターフェイス定義に依存し、一貫したアップロード機能プロトコルに従います。ユーザーは、プログラミングする特定の実装クラスではなく、インターフェースに依存しています。
このアイデアに従い、コードをリファクタリングします。リファクタリングされたコードは次のとおりです。
public interface ImageStore {
String upload(Image image, String bucketName);
Image download(String url);
}
public class AliyunImageStore implements ImageStore {
//...省略属性、构造函数等...
public String upload(Image image, String bucketName) {
createBucketIfNotExisting(bucketName);
String accessToken = generateAccessToken();
//...上传图片到阿里云...
//...返回图片在阿里云上的地址(url)...
}
public Image download(String url) {
String accessToken = generateAccessToken();
//...从阿里云下载图片...
}
private void createBucketIfNotExisting(String bucketName) {
// ...创建bucket...
// ...失败会抛出异常..
}
private String generateAccessToken() {
// ...根据accesskey/secrectkey等生成access token
}
}
// 上传下载流程改变:私有云不需要支持access token
public class PrivateImageStore implements ImageStore {
public String upload(Image image, String bucketName) {
createBucketIfNotExisting(bucketName);
//...上传图片到私有云...
//...返回图片的url...
}
public Image download(String url) {
//...从私有云下载图片...
}
private void createBucketIfNotExisting(String bucketName) {
// ...创建bucket...
// ...失败会抛出异常..
}
}
// ImageStore的使用举例
public class ImageProcessingJob {
private static final String BUCKET_NAME = "ai_images_bucket";
//...省略其他无关代码...
public void process() {
Image image = ...;//处理图片,并封装为Image对象
ImageStore imageStore = new PrivateImageStore(...);
imagestore.upload(image, BUCKET_NAME);
}
}
さらに、多くの人々は、インターフェースを定義するときにクラスを実装することによって、インターフェースの定義を逆にすることを望んでいます。最初に実装クラスを記述してから、実装クラスに含まれるメソッドを確認し、それをインターフェイス定義にコピーします。この考え方に従うと、インターフェイス定義が十分に抽象化されておらず、特定の実装に依存している可能性があります。このようなインターフェース設計は無意味です。ただし、この考え方がスムーズだと感じた場合は問題ありません。実装クラスのメソッドをインターフェイス定義に選択的に移動するだけで、特定の実装に関連するメソッドをインターフェイスに移動しないでください。 、AliyunImageStoreのgenerateAccessToken()メソッドなど。
要約すると、ソフトウェア開発を行うときは、抽象的な意識、カプセル化の意識、およびインターフェイスの意識が必要です。インターフェイスを定義するときは、実装の詳細を公開しないでください。インターフェイスの定義は、何をするかを示すだけであり、どのように行うかは示していません。さらに、インターフェースを設計するときは、そのようなインターフェース設計が十分に普遍的であるかどうか、およびインターフェース定義を変更せずに特定のインターフェース実装を置き換えるときにそれを実現できるかどうかについてもっと考える必要があります。
クラスごとにインターフェースを定義する必要がありますか?
今説明を読んだ後、あなたはこの質問をするかもしれません:この原則を満たすために、私は各実装クラスに対応するインターフェースを定義する必要がありますか?開発するとき、コードはインターフェイスのみに依存し、実装プログラミングにはまったく依存しない必要がありますか?
行うすべてのことにおいて「程度」に注意を払う必要があります。この原則を使いすぎると、クラスごとにインターフェイスを定義する必要があります。インターフェイスは空中を飛び交うため、不必要な開発負担も発生します。特定のクラスのインターフェイスを定義してインターフェイスベースのプログラミングを実装する必要がある場合、インターフェイスを定義して実装クラスプログラミングを直接使用する必要がない場合、トレードオフを行うための基本的な基礎は、設計原則の誕生の本来の意図に戻ることです。この原則がどのような問題を解決するように設計されているかを理解している限り、以前はあいまいだった多くの問題が突然明らかになることがわかります。
前述したように、この原則の本来の目的は、インターフェイスと実装を分離し、不安定な実装をカプセル化し、安定したインターフェイスを公開することでした。アップストリームシステムは、実装プログラミングではなくインターフェイス指向であり、不安定な実装の詳細に依存しないため、実装が変更された場合、コード間の結合を減らし、コードの拡張を改善するために、アップストリームシステムのコードを基本的に変更する必要はありません。セックス。
元の設計の観点から、ビジネスシナリオで関数の実装が1つだけであり、将来的に他の実装に置き換えることができない場合は、そのインターフェイスを設計する必要はなく、インターフェイスに基づいてプログラミングする必要もありません。 、実装クラスを直接使用するだけです。
さらに、システムが不安定になるほど、コードのスケーラビリティと保守性に取り組む必要があります。逆に、システムが非常に安定していて、基本的に開発後に保守する必要がない場合は、そのスケーラビリティのために不必要な開発時間を費やす必要はありません。
キーレビュー
今日のコンテンツは以上です。習得する必要のある主要なコンテンツを要約して確認しましょう。
-
「実装ではなくインターフェースに基づくプログラミング」、この原則を表現する別の方法は、「実装ではなく抽象化に基づくプログラミング」です。後者の表現方法は、実際にはこの原則の本来の意図をよりよく反映しています。ソフトウェア開発を行うときは、抽象意識、カプセル化意識、インターフェース意識が必要です。抽象的であるほど、トップレベルが高くなり、特定の実装設計から逸脱するほど、コードの柔軟性、スケーラビリティ、および保守性を向上させることができます。
-
インターフェイスを定義する場合、一方では、名前付けは十分に一般的である必要があり、特定の実装に関連する単語を含めることはできません。一方、特定の実装に関連するメソッドは、インターフェイスで定義しないでください。
-
「実装プログラミングではなくインターフェースに基づく」という原則は、非常に詳細なプログラミング開発を導くだけでなく、より高レベルのアーキテクチャ設計とシステム設計を導くこともできます。たとえば、サーバーとクライアント間の「インターフェイス」設計、クラスライブラリの「インターフェイス」設計。