抽象化の意味、つまりより一般的なユースケースに適用できるようにコードをリファクタリングすることは理解できたと思いました。しかし、私は最近、いくつかのタイプの抽象化が実際には間接的である可能性があることを知り、私の理解に疑問を投げかけています。
2つの概念の違いを示す(説明ではなくJavaScriptのコードの)特定の例をいくつか見たいと思います。
私はJSをよく知っているだけなので、Javascriptを指定しています(Pythonは、私がおそらく従うことができるほど十分に似ていますが)。
厳密に言えば、間接指定とは、名前、アドレス、インデックスなどを介して間接的に何かを参照する機能です(直接的なものと間接的なものはコンテキストに依存する場合があります)。いくつかの形式の間接参照が言語に組み込まれています。たとえば、JavaScript関数にオブジェクトを渡してそのメソッドを呼び出す場合、実際にはメソッドを直接呼び出しているわけではありません。そのオブジェクトは何でもかまいません。そのため、いくつかの実装候補があります(メソッドは継承することもできます。そのため、オブジェクト上にまったく存在しない可能性があります)。あなたは実際に名前(間接参照)を指定し、言語はオブジェクトを調べて関数を見つけるメカニズムを提供します( this を参照)。
「手動」で間接指定を使用することもできます。たとえば、配列のインデックスや辞書のキーなどを使用します。
「一部の種類の抽象化は実際には間接的である可能性があります」という記述は、精査下では必ずしも意味がありません。抽象化にはさまざまな種類がありますが、非常に一般的に言えば、抽象化は2つの相互作用するコンポーネントの間に配置し、それらのコンポーネントの両方をその抽象化に依存させることで、独立して変化させることができます。 (通常、これらのコンポーネントの1つを他のコンポーネントのクライアントとして表示するため、導入したオブジェクトは他のコンポーネントの抽象化であると言いますが、実際には、抽象化はコンポーネント間の相互作用の本質的な機能をキャプチャします)。 OOPでは、これはほとんどの場合インターフェースです(「これらの動作が必要」-「これらの動作を提供する」関係をキャプチャーします)が、そうである必要はありません。 JavaScriptでは、それはドキュメントにのみ存在する暗黙的なものになる可能性があります(たとえば、一部のメソッドへのパラメーターは、いくつかのプロパティと関数のセットを実装するオブジェクトである必要があります。これはインターフェースの要件であり、その要件はここでも抽象化です。コードでは明示的に表現されていませんが)。抽象化自体が間接化の手段を提供できるという事実は、それを「抽象化ではない」とはしません。
理論的には、用語の違いは簡単に定義できます。
2つのもの(AとB)の間に3番目のもの(C)がある場合、それらの間には抽象化が存在します。 AとBは、Cによって互いに抽象化されています。
間接(または逆参照)は、モノAが別のモノBから直接参照されず、モノCを介して間接的に参照される場合に発生します。
それらの間の類似点に注意してください?そのような緩い言葉で表現されるとき、それらは同義です。したがって、違いを定義するのは簡単です。何もありません。しかし、人々はそれらに異なる意味を持たせたいので、そこに問題が発生します。
間接指定は、CPU命令セットの黎明期(または少なくとも80年代前半のZ80 CPU以降、最初に出会った場所)から、絶対ではなくレジスターを使用してアドレスを指定することを指す用語です。自身をアドレス指定します。この用語は、配列へのインデックスなどのオフセットにも一般的に使用されます。この場合、配列の先頭のアドレスとインデックスに要素の絶対アドレスを指定します。
しかし、これらはどちらも抽象化です。直接アクセスするのではなく、アドレスを抽象化しています。しかし、これらを抽象化として参照する人々を見つけるのは珍しいことです。
抽象化は、OOPでインターフェイスと抽象型を参照するために一般的に使用される用語です。ここでは、呼び出し側と呼び出し先の間に(抽象型の形式で)抽象化レイヤーを配置しています。間接的に呼び出し先を参照していることを意味します。これには間接参照があります。繰り返しますが、これを間接参照として参照する人はまれです(継承の場合、その抽象化はしばしば間接参照と呼ばれます)。
つまり、要約すると、抽象化と間接化は基本的に同じものです。人々はそれらが異なっているかのように用語を使用したいのですが、それらは実際には異なっていないため、違いの多くの矛盾する定義に出くわすでしょう。
したがって、選択肢は次のとおりです。それらを同じものとして表示するか、それらを区別してそれを維持しようとする用語の定義を選択します。どちらの方法でも、他の人々があなたに用語の異なる意味を選んだかもしれないので、用語を議論しようとすると混乱が生じる可能性があることに注意してください。
最後に、JavaScriptについて触れます。 JavaScriptコミュニティー内で、これらの用語の厳密な定義についてコンセンサスが存在する場合、JavaScriptを使用するときはそれらに固執します。私はそのようなコンセンサスがあるとは信じていませんが、それは私の側の無知によるものかもしれません。あなたがこの質問をするという事実は、コンセンサスが欠如していることに重きを置いています。
JavaScriptには、従来の抽象化の組み込みサポートはありません。少なくとも、インターフェースや抽象クラスのような型はありません。開発者は一般的に抽象化を気にしません。抽象化を無視しても、小さなコードを記述している間は問題は発生しませんが、大規模なアプリケーションを扱う場合は、比較的困難な場合があります。一方、抽象化は、分野横断的な懸念に対処する方法を提供し、緊密に結合されたコードを回避できるようにします。
抽象化には、インターフェースと抽象クラスを使用できますが、JavaScriptにはそのような機能はありません。あるいは、プロトタイプを使用してインターフェースを定義することもできます。 JavaScriptでインターフェースを定義する方法を見てみましょう。
//ItemDemo interface
var ItemDemo = {
addItem : function(item){},
removeItem : function(id){},
getItem: function(id){}
};
次に、ItemDemoの2つの実装を定義します。1つ目はajaxによるデータの保存と取得、2つ目はcookieからのデータの保存と取得です。
var ItemDemoAjax = function(url){
this.url = url;
};
var ItemDemoCookie = function(){};
//Extend the ItemDemofor Ajax
ItemDemoAjax.prototype = Object.create(ItemDemo);
//Extend the ItemDemofor Cookie
ItemDemoCookie.prototype = Object.create(ItemDemo);
ItemDemoAjax.prototype.addItem = function(item){
//actual add item code
};
ItemDemoCookie.prototype.addItem = function(item){
//actual add item code
}
itemRepoの実装を定義したので、ItemRepoを利用する新しいクラスを作成しましょう。
var ItemController = function(itemDemo){
this.itemDemo= itemDemo;
};
ItemController.prototype.add = function(item){
itemDemo.addItem(item);
};
ItemControllerとItemDemoのいずれかを利用しましょう。
var itemDemo = new ItemDemoAjax("url");
var itemController = new ItemController(itemDemo);
itemController.add({item:"myItem"});
コントローラクラスを実装したので、このクラスのメソッドをログに記録したり、プロファイルしたりできます。
var Logger = {
log : function(log){}
}
var ConsoleLogger = function() {};
ConsoleLogger.prototype = Object.create(Logger);
ConsoleLogger.prototype.log = function(log) {
//actual logging code
}
// Define new Controller
var LoggingItemController = function(itemDemo , logger){
this.itemDemo = itemDemo ;
this.logger = logger;
}
LoggingItemController.prototype = Object.create(ItemController);
LoggingItemController.prototype.add = function(item) {
this.logger.log("start");
this.itemDemo .addItem(item);
this.logger.log("stop");
};
var consoleLogger = new ConsoleLogger();
var itemDemo = new ItemDemoCookie();
var loggingItemController =
new LoggingItemController(itemDemo , consoleLogger);
loggingItemController.add("item");
インターフェイス定義を使用することで、クラシックパターンを簡単に適用できます
var ItemDemoFactory = {
getItemDemo : function(type) {}
};
var ConcreteItemDemoFactory = function() {};
ConcreteItemDemoFactory.prototype = Object.create(ItemDemoFactory);
ConcreteItemDemoFactory.prototype.getItemDemo= function(type) {
if (type === "ajax") {
return new ItemDemoAjax("url");
}
return new ItemDemoCookie();
};
JavaScriptでインターフェースを使用すると、保守性が向上するだけでなく、テスト性も向上します。
var ItemDemoMock = function(mockItem){
this.mockItem = mockItem;
};
//Extend the ItemDemo for Ajax
ItemDemoMock .prototype = Object.create(ItemDemo );
ItemDemoMock .prototype.getItem = function(id){
return this.mockItem;
}
function testGetItem(){
var mockItem = "mock";
var mockItemDemo = new ItemDemo Mock(mockItem);
var itemController = new ItemController(mockItemDemo );
//An equal method is needed.
equal(itemController.getItem("id"), mockItem);
}