デザインパターンの美し学習(6):抽象クラス対インタフェースの違いは?通常のクラス抽象クラスとインタフェースをシミュレートする方法は?

オブジェクト指向プログラミングでは、抽象クラスとインタフェースは、二つの文法概念が頻繁に使用されている、達成するための基本的な設計原理をプログラミング、オブジェクト指向、デザインパターンの多くは、デザインの4つの特徴です。例えば、我々はむしろ設計原理の実現よりも、抽象クラスを使用すると、オブジェクト指向のデザインパターンなどを実装するためのプロパティとテンプレートを継承するオブジェクト指向の抽象化機能、特性および多型ベースのインターフェイスを実装するためのインタフェースを使用することができます。

ただし、すべてのオブジェクト指向プログラミング言語は文法のこれら二つの概念をサポートし、例えば、C++このプログラミング言語は、抽象クラスをサポートしていますインタフェースはサポートされていない;などPythonの動的プログラミング言語など、どちらもサポート抽象クラス、またそれをサポートしていますインタフェース。いくつかのプログラミング言語がサポートインターフェイスと抽象クラスに既製の構文を提供していませんが、我々はまだこの二つの文法的な概念をシミュレートするために、いくつかの手段を達成することができます。

これら二つの概念の構文は、多くの場合、多くの場合、インタビューで述べたように、だけでなく、仕事で使用されています。「?インタフェースを使用する場合は、抽象クラス?抽象クラスを使用し、存在意義は何インタフェースする場合は?何プログラムは問題を解決することができますか?抽象クラスとインタフェースの違いは、」たとえば、などなど。

抽象クラスとインタフェースは何ですか?何が違いますの?

異なるプログラミング言語は、インタフェースと抽象クラスの定義方法に多少の違いかもしれませんが、違いは大きくありません。

初めて目に私たちは抽象クラスを定義する方法このプログラミング言語、。Java

次のコードは、抽象クラスの典型的な使用シナリオ(テンプレートデザインモード)です。Logger抽象クラスのログであるFileLoggerMessageQueueLogger継承Loggerのメッセージ・キューにログ・ファイルとログ:二つの異なるログモードを達成するために、それぞれ、。FileLoggerそして、MessageQueueLogger2つのサブクラスは、親クラスの再利用LoggernameenabledminPermittedLevelプロパティとlog()メソッドを、しかし、理由は二つの異なるサブクラスの道のログを書き込むために、彼らは彼らの親クラス書き直したdoLog()方法を。

// 抽象类
public abstract class Logger {
  private String name;
  private boolean enabled;
  private Level minPermittedLevel;

  public Logger(String name, boolean enabled, Level minPermittedLevel) {
    this.name = name;
    this.enabled = enabled;
    this.minPermittedLevel = minPermittedLevel;
  }
  
  public void log(Level level, String message) {
    boolean loggable = enabled && (minPermittedLevel.intValue() <= level.intValue());
    if (!loggable) return;
    doLog(level, message);
  }
  
  protected abstract void doLog(Level level, String message);
}
// 抽象类的子类:输出日志到文件
public class FileLogger extends Logger {
  private Writer fileWriter;

  public FileLogger(String name, boolean enabled,
    Level minPermittedLevel, String filepath) {
    super(name, enabled, minPermittedLevel);
    this.writer = new FileWriter(filepath); 
  }
  
  @Override
  public void doLog(Level level, String mesage) {
    // 格式化level和message,输出到日志文件
    fileWriter.write(...);
  }
}
// 抽象类的子类: 输出日志到消息中间件(比如kafka)
public class MessageQueueLogger extends Logger {
  private MessageQueueClient msgQueueClient;
  
  public MessageQueueLogger(String name, boolean enabled,
    Level minPermittedLevel, MessageQueueClient msgQueueClient) {
    super(name, enabled, minPermittedLevel);
    this.msgQueueClient = msgQueueClient;
  }
  
  @Override
  protected void doLog(Level level, String mesage) {
    // 格式化level和message,输出到消息中间件
    msgQueueClient.send(...);
  }
}复制代码

上記の例で、この外観では、抽象クラスはどんな機能します。

  • 抽象クラスはインスタンス化することが許可されていない、唯一継承することができます。言い換えれば、あなたがすることはできませんnew抽象クラスのうちのオブジェクト(Logger logger = new Logger(…);コンパイルエラーを報告します)。
  • 抽象クラスは、属性とメソッドが含まれていてもよいです。この方法は、(例えば、コード含有実装されてもよいLoggerにおいてlog()法)、または(例えば、コード実装を含んでいてもよいLoggerにおけるdoLog()方法)。この方法は、抽象メソッドが呼び出された実装するコードが含まれていません。
  • サブクラスが抽象クラスを継承して、すべての抽象メソッド抽象クラスが実装する必要があります。サンプルコードに対応し、全ての後継ということであるLogger抽象クラスのサブクラスでオーバーライドする必要がありますdoLog()方法を。

その後で、見て、我々はインターフェイスをどう定義するか、このプログラミング言語。Java

// 接口
public interface Filter {
  void doFilter(RpcRequest req) throws RpcException;
}
// 接口实现类:鉴权过滤器
public class AuthencationFilter implements Filter {
  @Override
  public void doFilter(RpcRequest req) throws RpcException {
    //...鉴权逻辑..
  }
}
// 接口实现类:限流过滤器
public class RateLimitFilter implements Filter {
  @Override
  public void doFilter(RpcRequest req) throws RpcException {
    //...限流逻辑...
  }
}
// 过滤器使用demo
public class Application {
  // filters.add(new AuthencationFilter());
  // filters.add(new RateLimitFilter());
  private List<Filter> filters = new ArrayList<>();
  
  public void handleRpcRequest(RpcRequest req) {
    try {
      for (Filter filter : fitlers) {
        filter.doFilter(req);
      }
    } catch(RpcException e) {
      // ...处理过滤结果...
    }
    // ...省略其他处理逻辑...
  }
}复制代码

上記のコードは、典型的な使用シナリオ・インターフェースです。することにより、キーワードの定義インタフェース。そして、2種類のインタフェースの実装クラスに実現された要求の認証と制限フィルタリング。JavainterfaceFilterAuthencationFilterRateLimitFilterRPC

コードは非常に簡単です。次いで、界面の特性を有するコードの組み合わせを見てください。

  • インタフェースは、(、メンバ変数である)の属性を含めることはできません。
  • インターフェイスは、メソッドだけを宣言することができ、この方法は、コードを含めることはできません。
  • クラスがインターフェイスを実装すると、すべてのメソッドを実装しなければならないインタフェースで宣言しました。

そのような抽象クラスのような比較的大きな差がどちらも比較構文的特性は、プロパティ、メソッドを達成するために定義することができ、及び界面特性を定義することができない、コードを含む方法が得等することができません。文法的な機能に加えて、設計の観点から、2つの比較的大きな違いがあります。

抽象クラスは、このクラスは、サブクラスだけによって継承、オブジェクトとしてインスタンス化することはできません、実際にクラスであるが、特殊なクラスです。私たちは、継承関係があることを知っているis-aことは、クラス、抽象クラスに属し、また、いずれかを表しているため、関係is-aの関係を。抽象クラスis-aの関係は、インターフェースショーhas-a関係は、特定の関数を表します。インタフェースについて、その合意のためのより鮮やかな名前は(ありcontract)。

抽象クラスとインタフェースは、任意のプログラミングの問題を解決することができますか?

なぜ我々は抽象クラスが必要なのでしょうか?これは、任意のプログラミングの問題を解決することができますか?

抽象クラスは、生まれたコードの再利用です。サブクラスの複数の同一のコードを書き換え、抽象クラス、サブクラス避けで定義された属性およびメソッドを継承します。

連続自体は、コードの再利用、継承の目的を達成することができますし、親クラスは抽象クラスでなければならないことを必要としないためしかし、その後、私たちは抽象クラスを使用していない、それはまだ継承と再利用を実現することができます。このような観点から、私たちは、抽象クラスを必要としない、その構文のように見えますうん。コードの再利用の問題を解決するために加えて、抽象クラス、存在の他のどのような意味はそれですか?

またはテイクの前に印刷ログの例。私たちは、上記のコードの最初の次の変換を行います。コード変換した後、Loggerもはや、削除された普通の親、抽象クラスではありませんLoggerlog()doLog()この方法は、追加isLoggable()の方法を。FileLoggerそして、MessageQueueLoggerまだ継承しLogger、コードの再利用の目的を達成するために、親クラスを。具体的なコードは次のよう:

// 父类:非抽象类,就是普通的类. 删除了log(),doLog(),新增了isLoggable().
public class Logger {
  private String name;
  private boolean enabled;
  private Level minPermittedLevel;

  public Logger(String name, boolean enabled, Level minPermittedLevel) {
    //...构造函数不变,代码省略...
  }

  protected boolean isLoggable() {
    boolean loggable = enabled && (minPermittedLevel.intValue() <= level.intValue());
    return loggable;
  }
}
// 子类:输出日志到文件
public class FileLogger extends Logger {
  private Writer fileWriter;

  public FileLogger(String name, boolean enabled,
    Level minPermittedLevel, String filepath) {
    //...构造函数不变,代码省略...
  }
  
  public void log(Level level, String mesage) {
    if (!isLoggable()) return;
    // 格式化level和message,输出到日志文件
    fileWriter.write(...);
  }
}
// 子类: 输出日志到消息中间件(比如kafka)
public class MessageQueueLogger extends Logger {
  private MessageQueueClient msgQueueClient;
  
  public MessageQueueLogger(String name, boolean enabled,
    Level minPermittedLevel, MessageQueueClient msgQueueClient) {
    //...构造函数不变,代码省略...
  }
  
  public void log(Level level, String mesage) {
    if (!isLoggable()) return;
    // 格式化level和message,输出到消息中间件
    msgQueueClient.send(...);
  }
}复制代码

このデザインのアイデアが、コードの再利用の目的を達成するために、しかし、多状態の特性を使用することはできません。次のような書き込みコード、ので、コンパイル時エラーが発生Logger定義されていないlog()方法を。

Logger logger = new FileLogger("access-log", true, Level.WARN, "/users/muchen/access.log");
logger.log(Level.ERROR, "This is a test log message.");复制代码

あなたはこの問題は非常にシンプルなああです解決するために、と言うかもしれません。ではLogger、親クラス、空の定義log()サブクラスは親クラスのオーバーライドするための方法log()、方法を、独自のロギングのロジックを実装し、それを行うことができませんの?

public class Logger {
  // ...省略部分代码...
  public void log(Level level, String mesage) { // do nothing... }
}
public class FileLogger extends Logger {
  // ...省略部分代码...
  @Override
  public void log(Level level, String mesage) {
    if (!isLoggable()) return;
    // 格式化level和message,输出到日志文件
    fileWriter.write(...);
  }
}
public class MessageQueueLogger extends Logger {
  // ...省略部分代码...
  @Override
  public void log(Level level, String mesage) {
    if (!isLoggable()) return;
    // 格式化level和message,输出到消息中间件
    msgQueueClient.send(...);
  }
}复制代码

このデザインコンセプトを使用することができ、しかし、それは明らかにエレガントな抽象的なアイデアで前に実現しませんでした。なぜあなたは言うのですか?主な理由は次の通りです。

  • ではLogger、空で定義された方法は、コードの読みやすさに影響を与えます。我々は精通していない場合はLogger、コードのコメントを強制する方法について、基本的な設計思想ではない、私たちが読んでLoggerタイムコード、どのように空を定義することが可能であるlog()方法と混同し、参照してくださいする必要がありLoggerFileLoggerMessageQueueLoggerそれを理解するためには、継承関係を意図デザイン。
  • 継承新しいサブクラス作成する場合はLogger、親クラスを、我々は再実装するのを忘れてlog()た方法。抽象クラスの前にデザインのアイデアに基づいて、コンパイラは、サブクラスのオーバーライドを強制されますlog()それ以外の場合は、コンパイルエラーを報告します、方法。私は新しい定義したいので、あなたは、言うかもしれないLoggerサブクラスが再実装する方法を忘れてしまうlog()ことを行う方法を?私たちの例では、比較的簡単であるLogger方法で、あまりない、と数行のコード。ただし、Logger数百行があり、そこにあるnあなたがない限り、より多くの方法よりもLogger設計に精通している、または再実装することを忘れlog()方法、それは不可能ではありません。
  • Logger言い換えれば、私たちがすることができ、インスタンス化することができて、空の呼び出し方法を。また、クラスの誤用のリスクを増大させます。もちろん、この問題は、プライベートコンストラクタメソッドを設定することで解決することができます。しかし、明らかにそれはエレガントな抽象クラスを介して来ませんでした。newLoggerlog()

なぜインターフェイス?これは、任意のプログラミングの問題を解決することができますか?

より抽象的なコードの再利用のためのクラス、およびインタフェースは、より多くのデカップリングに焦点を当てています。インターフェイスは、あなたがおよそアナロジーを考えることができ、契約または契約のセットの同等の、行為の抽象的であるAPIインタフェース。呼び出し側は、特定の実装、呼び出し側に透明固有の実装コードを知っている必要はありません、抽象インタフェースに焦点を当てる必要があります。インターフェイス規則と相分離がコード、スケーラビリティを向上させるためのコードとの間の結合を低減することができます。

実際には、インターフェイスは、抽象よりもアプリケーションのより広いクラスで、より重要なポイントです。例えば、しばしば呼ばれる「インターフェースベースのプログラミングではなく達成」ほとんど毎日使用され、大幅にコードの柔軟性、設計のスケーラビリティを向上させることができる。一つであります

2つの抽象クラスとインタフェース文法の概念をシミュレートする方法は?

前の例で引用では、の使用Javaインタフェース構文は実装Filterフィルタ。しかし、あなたが精通している場合、C++このプログラミング言語、あなたは、言うかもしれないC++唯一の抽象クラスを、そして何のインタフェースが実現するコードの観点から、それが達成できないされていないこと、ありませんFilter、それのデザインのアイデアを?

最初は、インターフェイスの定義を思い出し:インターフェイスはメンバ変数、唯一のメソッド宣言ではなく、達成するための方法はありません、すべてのインタフェースの実装クラスは、インタフェースのメソッドを実装する必要があります。長いこれらの点のように、設計の観点から、我々は、インタフェース、それを呼び出すことができます。実際には、これらの特性構文のインタフェースを満たすようにすることは難しいことではありません。以下、この中にC++コード(以下、このコードは、実際にポリシーモデルにおけるコードの一部である)は、アナログに抽象クラスインターフェースを使用します。

class Strategy { // 用抽象类模拟接口
  public:
    ~Strategy();
    virtual void algorithm()=0;
  protected:
    Strategy();
};复制代码

抽象クラスは、Strategy任意の属性を定義しないと、すべてのメソッドは次のように宣言されているvirtual(と同等のタイプので、すべてのコードで実装する方法と、すべての継承された抽象クラスのサブクラスを持つことができないというこれらのメソッドを実装する必要があり、キーワード) 。ビューの文法的特徴から、これは抽象クラスインターフェースに相当します。Javaabstract

ではPythonRubyこれらの動的言語インタフェースの概念をしただけでなく、全く同様の存在しないabstractvirtual上記を達成するためにどのようにして、抽象クラスを定義するには、このキーワードはFilterLoggerそれのデザインのアイデア?実際には、抽象クラスのアナログインターフェースに加えて、また、通常のクラスのインターフェースをシミュレートするために使用することができます。特定のJavaコードの実装は、以下に示します。

public class MockInteface {
  protected MockInteface() {}
  public void funcA() {
    throw new MethodUnSupportedException();
  }
}复制代码

クラスのメソッドこのインターフェイスの定義を満たしていないことを認識を含める必要があります。しかし、私たちは、クラスメソッドがスローさせることができMethodUnSupportedException、それ以外の場合は、実行時にスローされます、インターフェイスは含まれておらず、達成するためのイニシアチブを取るために、親クラスを継承すべての親クラスをサブクラスを強制することができますシミュレートするために、例外を異常。だから、このクラスは、それを避けるために何をインスタンス化されますか?実際には非常に簡単です、我々だけに、クラスのコンストラクタを宣言する必要がprotectedアクセスを。

あなたは抽象クラスやインターフェイスをどのように決めるのですか?

実際には、判断の基準は非常に簡単です。私たちは1人の表現したい場合はis-a、我々は1つの表現したい場合は、関係があり、コードの再利用の問題を解決するために、我々は、抽象クラスを使用してhas-a関係を、抽象コードの再利用ではなく、問題を解決するために、その後、我々はできますインタフェースを使用しています。

継承階層クラスの観点から、抽象クラスは、ボトムアップ設計アイデア、第一コードの重複のサブクラス、および次いで(抽象クラ​​ス、である)上位の親クラスに抽象化されます。逆にインターフェースが、それはトップダウン設計のアイデアです。私たちは、プログラミングされている一般的インターフェイスの最初の設計され、実装を検討して行きます。

RECAP

抽象クラスとインタフェースの1構文特性

抽象クラスはインスタンス化することが許可されていない、唯一継承することができます。これは、属性とメソッドを含めることができます。コードを含む方法を実施することができる、またはコードを含んでいてもよいです。この方法は、抽象メソッドが呼び出された実装するコードが含まれていません。サブクラスが抽象クラスを継承して、すべての抽象メソッド抽象クラスが実装する必要があります。インタフェースは、唯一の宣言的なアプローチは、メソッドのコードを含めることはできませんすることができ、属性を含めることはできません。クラスがインターフェイスを実装すると、すべてのメソッドを実装しなければならないインタフェースで宣言しました。

2.抽象クラスとインタフェースの重要性の有無

抽象クラスは、メンバ変数とメソッドの抽象でありis-a、コードの再利用の問題を解決するために、関係。唯一のインタフェースが抽象メソッドであり、has-aデカップリングの問題を解決するために、関係は、行動特性を有する基を表し、絶縁型インターフェイスおよび特定の実装では、拡散符号改善しました。

3.抽象クラスとインタフェースのアプリケーションシナリオとの差

場合は、抽象クラスを使用するには?インターフェースを使用する場合は?実際には、判断の基準は非常に簡単です。あなたが1表現したい場合はis-a、問題を解決するために関係し、コードの再利用を、私たちは、抽象クラスを使用します。あなたが1つの表現したい場合はhas-a関係を、むしろコードの再利用よりも、抽象的な問題を解決するために、我々はインターフェイスを使用しています。

考えます

  • インターフェイスと抽象クラスは頻繁にインタビューで尋ねた二つの概念です。あなたはインタフェースと抽象クラスについての面接の話を聞かせている場合、どのようにそれに答えるでしょうか?

参考:抽象クラス対インタフェースの違いは?通常のクラス抽象クラスとインタフェースをシミュレートする方法は?

ブログ記事複数のプラットフォームからこの記事OpenWriteリリース!

詳細については、私のブログをクリックしてくださいムー陳

おすすめ

転載: juejin.im/post/5de11f51f265da05ea763933