ホームページ ウェブフロントエンド jsチュートリアル JavaScriptのデザインパターン(ポリモーフィズム)を学ぶ_JavaScriptスキル

JavaScriptのデザインパターン(ポリモーフィズム)を学ぶ_JavaScriptスキル

May 16, 2016 pm 03:29 PM
javascript ポリモーフィズム デザインパターン

ポリモーフィズムの実際の意味は、同じ操作を異なるオブジェクトに適用すると、異なる解釈と異なる実行結果が生成される可能性があるということです。言い換えれば、同じメッセージが異なるオブジェクトに送信されると、これらのオブジェクトはメッセージに基づいて異なるフィードバックを返します。

ポリモーフィズムを文字通り理解するのは簡単ではありません。 を見てみましょう。

飼い主は家にアヒルとニワトリの2匹の動物を飼っていますが、飼い主が「鳴く」と命令すると、アヒルは鳴き、ニワトリも鳴きます。どちらの動物も独自の方法で音を出します。彼らも「すべての動物であり、音を出すことができる」のですが、飼い主の指示に従って、それぞれ異なる音を出します。

実際、これにはポリモーフィズムの考え方が含まれています。以下、コードを通して詳しく紹介していきます。

1. 「ポリモーフィック」JavaScript コード

次のように JavaScript コードを使用して上記のストーリーを実装します。

1

2

3

4

5

6

7

8

9

10

11

12

13

var makeSound = function( animal ){

 if ( animal instanceof Duck ){

 console.log( '嘎嘎嘎' );

 }else if ( animal instanceof Chicken ){

 console.log( '咯咯咯' );

 }

};

 

var Duck = function(){};

var Chicken = function(){};

 

makeSound( new Duck() ); //嘎嘎嘎

makeSound( new Chicken() ); //咯咯咯

ログイン後にコピー

このコードは確かに「ポリモーフィズム」を具体化しています。アヒルとニワトリにそれぞれ「呼び出し」メッセージを送信すると、アヒルとニワトリはこのメッセージに基づいて異なる反応をします。しかし、このような「ポリモーフィズム」では不十分です。犬などの動物を後から追加すると、明らかに犬の鳴き声が「ワンワンワン」になるように、makeSound 関数を変更する必要があります。 。コードの変更は常に危険を伴います。変更する箇所が増えると、プログラム エラーが発生する可能性が高まります。また、動物の種類が増えると、makeSound は巨大な機能になる可能性があります。

ポリモーフィズムの背後にある考え方は、「何を」を「誰がどのように行うか」から分離すること、つまり「変化しないもの」と「変化する可能性のあるもの」を分離することです。この物語では、すべての動物が吠えるのは一定ですが、さまざまな種類の動物の具体的な名前は変わります。定数部分を分離し、可変部分をカプセル化すると、プログラムは拡張可能であり、オープンクローズの原則に準拠しているように見えます。コードを変更する場合と比較すると、同じ機能を実現できるコードを追加するだけです。明らかに、はるかにエレガントで安全です。

2. オブジェクトのポリモーフィズム

以下は書き換えられたコードです。まず、変更されていない部分、つまりすべての動物が音を立てる部分を分離します。

1

2

3

var makeSound = function( animal ){

 animal.sound();

};

ログイン後にコピー
次に、変数部分を個別にカプセル化します。先ほど説明したポリモーフィズムは、実際にはオブジェクトのポリモーフィズムを指します。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

var Duck = function(){}

 

Duck.prototype.sound = function(){

 console.log( '嘎嘎嘎' );

};

 

var Chicken = function(){}

 

Chicken.prototype.sound = function(){

 console.log( '咯咯咯' );

};

 

makeSound( new Duck() ); //嘎嘎嘎

makeSound( new Chicken() ); //咯咯咯

ログイン後にコピー
ここで、アヒルとニワトリの両方に「呼び出し」メッセージを送信します。メッセージを受信した後の反応は異なります。ある日、別の犬が動物の世界に追加された場合、以下に示すように、前の makeSound 関数を変更せずに、この時点でコードを追加するだけで済みます。

1

2

3

4

5

6

7

var Dog = function(){}

 

Dog.prototype.sound = function(){

 console.log( '汪汪汪' );

};

 

makeSound( new Dog() ); //汪汪汪

ログイン後にコピー
3. 型チェックとポリモーフィズム

オブジェクトのポリモーフィズムを説明する前に型チェックは避けては通れないトピックですが、JavaScript は型チェックを必要としない動的型付け言語です。ポリモーフィズムの目的を真に理解するには、静的なことから始めましょう。タイプされた言語。

静的型付き言語は、コンパイル時に型一致チェックを実行します。 Java を例に挙げると、コードのコンパイル時に厳密な型チェックが行われるため、変数に異なる型の値を割り当てることができない場合があります。コードは次のとおりです。

次に、アヒルとニワトリを擬似的に作成する上記の例を Java コードに変更してみましょう:

1

2

3

4

String str;

 

str = abc; //没有问题

str = 2; //报错

ログイン後にコピー

私たちはアヒルを鳴かせることに成功しましたが、今からニワトリを鳴らしたいとしても、それは不可能であることがわかります。なぜなら、(1) の AnimalSound クラスの makeSound メソッドは、Duck タイプのパラメータのみを受け入れるように規定されているためです。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

public class Duck { //鸭子类

 public void makeSound(){

 System.out.println( 嘎嘎嘎 );

 }

}

 

public class Chicken { //鸡类

 public void makeSound(){

 System.out.println( 咯咯咯 );

 }

}

 

 

public class AnimalSound {

 public void makeSound( Duck duck ){ //(1)

 duck.makeSound();

 }

 

}

 

public class Test {

 public static void main( String args[] ){

 AnimalSound animalSound = new AnimalSound();

 Duck duck = new Duck();

 animalSound.makeSound( duck ); //输出:嘎嘎嘎

 }

}

ログイン後にコピー

某些时候,在享受静态语言类型检查带来的安全性的同时,我们亦会感觉被束缚住了手脚。

为了解决这一问题,静态类型的面向对象语言通常被设计为可以向上转型:当给一个类变量赋值时,这个变量的类型既可以使用这个类本身,也可以使用这个类的超类。这就像我们在描述天上的一只麻雀或者一只喜鹊时,通常说“一只麻雀在飞”或者“一只喜鹊在飞”。但如果想忽略它们的具体类型,那么也可以说”一只鸟在飞“。

同理,当Duck对象和Chicken对象的类型都被隐藏在超类型Animal身后,Duck对象和Chicken对象就能被交换使用,这是让对象表现出多态性的必经之路,而多态性的表现正是实现众多设计模式的目标。

4. 使用继承得到多态效果

使用继承来得到多态效果,是让对象表现出多态性的最常用手段。继承通常包括实现继承和接口继承。本节我们讨论实现继承,接口继承的例子请参见第21章。

我们先创建一个Animal抽象类,再分别让Duck和Chicken都继承自Animal抽象类,下述代码中(1)处和(2)处的赋值语句显然是成立的,因为鸭子和鸡也是动物:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public abstract class Animal {

 abstract void makeSound(); //抽象方法

}

 

public class Chicken extends Animal{

 public void makeSound(){

 System.out.println( 咯咯咯 );

 }

}

 

public class Duck extends Animal{

 public void makeSound(){

 System.out.println( 嘎嘎嘎 );

 }

}

 

Animal duck = new Duck(); //(1)

Animal chicken = new Chicken(); //(2)

ログイン後にコピー

现在剩下的就是让AnimalSound类的makeSound方法接受Animal类型的参数,而不是具体的Duck类型或者Chicken类型:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public class AnimalSound{

 public void makeSound( Animal animal ){ //接受Animal类型的参数

 animal.makeSound();

 }

}

 

public class Test {

 public static void main( String args[] ){

 AnimalSound animalSound= new AnimalSound ();

 Animal duck = new Duck();

 Animal chicken = new Chicken();

 animalSound.makeSound( duck ); //输出嘎嘎嘎

 animalSound.makeSound( chicken ); //输出咯咯咯

 }

}

ログイン後にコピー

5. JavaScript的多态

从前面的讲解我们得知,多态的思想实际上是把“做什么”和“谁去做”分离开来,要实现这一点,归根结底先要消除类型之间的耦合关系。如果类型之间的耦合关系没有被消除,那么我们在makeSound方法中指定了发出叫声的对象是某个类型,它就不可能再被替换为另外一个类型。在Java中,可以通过向上转型来实现多态。

而JavaScript的变量类型在运行期是可变的。一个JavaScript对象,既可以表示Duck类型的对象,又可以表示Chicken类型的对象,这意味着JavaScript对象的多态性是与生俱来的。

这种与生俱来的多态性并不难解释。JavaScript作为一门动态类型语言,它在编译时没有类型检查的过程,既没有检查创建的对象类型,又没有检查传递的参数类型。在2节的代码示例中,我们既可以往makeSound函数里传递duck对象当作参数,也可以传递chicken对象当作参数。

由此可见,某一种动物能否发出叫声,只取决于它有没有makeSound方法,而不取决于它是否是某种类型的对象,这里不存在任何程度上的“类型耦合”。这正是我们从上一节的鸭子类型中领悟的道理。在JavaScript中,并不需要诸如向上转型之类的技术来取得多态的效果。

6. 多态在面向对象程序设计中的作用

有许多人认为,多态是面向对象编程语言中最重要的技术。但我们目前还很难看出这一点,毕竟大部分人都不关心鸡是怎么叫的,也不想知道鸭是怎么叫的。让鸡和鸭在同一个消息之下发出不同的叫声,这跟程序员有什么关系呢?

Martin Fowler在《重构:改善既有代码的设计》里写到:

多态的最根本好处在于,你不必再向对象询问“你是什么类型”而后根据得到的答案调用对象的某个行为——你只管调用该行为就是了,其他的一切多态机制都会为你安排妥当。

换句话说,多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。

Martin Fowler的话可以用下面这个例子很好地诠释:

在电影的拍摄现场,当导演喊出“action”时,主角开始背台词,照明师负责打灯光,后面的群众演员假装中枪倒地,道具师往镜头里撒上雪花。在得到同一个消息时,每个对象都知道自己应该做什么。如果不利用对象的多态性,而是用面向过程的方式来编写这一段代码,那么相当于在电影开始拍摄之后,导演每次都要走到每个人的面前,确认它们的职业分工(类型),然后告诉他们要做什么。如果映射到程序中,那么程序中将充斥着条件分支语句。

利用对象的多态性,导演在发布消息时,就不必考虑各个对象接到消息后应该做什么。对象应该做什么并不是临时决定的,而是已经事先约定和排练完毕的。每个对象应该做什么,已经成为了该对象的一个方法,被安装在对象的内部,每个对象负责它们自己的行为。所以这些对象可以根据同一个消息,有条不紊地分别进行各自的工作。

将行为分布在各个对象中,并让这些对象各自负责自己的行为,这正是面向对象设计的优点。

再看一个现实开发中遇到的例子,这个例子的思想和动物叫声的故事非常相似。

假设我们要编写一个地图应用,现在有两家可选的地图API提供商供我们接入自己的应用。目前我们选择的是谷歌地图,谷歌地图的API中提供了show方法,负责在页面上展示整个地图。示例代码如下:

1

2

3

4

5

6

7

8

9

10

11

var googleMap = {

 show: function(){

 console.log( '开始渲染google地图' );

 }

};

 

var renderMap = function(){

 googleMap.show();

};

 

renderMap(); // 输出: 开始渲染google地图

ログイン後にコピー

后来因为某些原因,要把谷歌地图换成百度地图,为了让renderMap函数保持一定的弹性,我们用一些条件分支来让renderMap函数同时支持谷歌地图和百度地图:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

var googleMap = {

 show: function(){

 console.log( '开始渲染google地图' );

 }

};

 

var baiduMap = {

 show: function(){

 console.log( '开始渲染baidu地图' );

 }

};

 

var renderMap = function( type ){

 if ( type === 'google' ){

 googleMap.show();

 }else if ( type === 'baidu' ){

 baiduMap.show();

 }

};

 

renderMap( 'google' ); // 输出: 开始渲染google地图

renderMap( 'baidu' ); // 输出: 开始渲染baidu地图

ログイン後にコピー

可以看到,虽然renderMap函数目前保持了一定的弹性,但这种弹性是很脆弱的,一旦需要替换成搜搜地图,那无疑必须得改动renderMap函数,继续往里面堆砌条件分支语句。

我们还是先把程序中相同的部分抽象出来,那就是显示某个地图:

1

2

3

4

5

6

7

8

var renderMap = function( map ){

 if ( map.show instanceof Function ){

 map.show();

 }

};

 

renderMap( googleMap ); // 输出: 开始渲染google地图

renderMap( baiduMap ); // 输出: 开始渲染baidu地图

ログイン後にコピー

现在来找找这段代码中的多态性。当我们向谷歌地图对象和百度地图对象分别发出“展示地图”的消息时,会分别调用它们的show方法,就会产生各自不同的执行结果。对象的多态性提示我们,“做什么”和“怎么去做”是可以分开的,即使以后增加了搜搜地图,renderMap函数仍然不需要做任何改变,如下所示:

1

2

3

4

5

6

7

var sosoMap = {

 show: function(){

 console.log( '开始渲染soso地图' );

 }

};

 

renderMap( sosoMap ); // 输出: 开始渲染soso地图

ログイン後にコピー

在这个例子中,我们假设每个地图API提供展示地图的方法名都是show,在实际开发中也许不会如此顺利,这时候可以借助适配器模式来解决问题。

以上就是本文的全部内容,很全面,以生动的举例来帮助大家学习多态,希望大家能够真正的有所收获。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

C++ 仮想関数テーブルとポリモーフィック実装、メモリの無駄を避ける方法 C++ 仮想関数テーブルとポリモーフィック実装、メモリの無駄を避ける方法 May 31, 2024 pm 07:03 PM

仮想基本クラスは、追加の vtable を作成せずに複数の基本クラスからの継承を許可することで、vtable のメモリ オーバーヘッドを最適化します。最適化されたコードでは、形状基本クラスに仮想関数テーブルがなくなり、円クラスと長方形クラスが同じ仮想関数テーブルを共有するため、メモリ消費量が削減されます。

Java フレームワークにおけるデザイン パターンとアーキテクチャ パターンの違い Java フレームワークにおけるデザイン パターンとアーキテクチャ パターンの違い Jun 02, 2024 pm 12:59 PM

Java フレームワークにおけるデザイン パターンとアーキテクチャ パターンの違いは、デザイン パターンがソフトウェア設計における一般的な問題に対する抽象的な解決策を定義し、ファクトリ パターンなどのクラスとオブジェクト間の相互作用に焦点を当てていることです。アーキテクチャ パターンは、階層化アーキテクチャなどのシステム コンポーネントの編成と相互作用に焦点を当てて、システム構造とモジュールの間の関係を定義します。

Java デザイン パターンにおけるデコレータ パターンの分析 Java デザイン パターンにおけるデコレータ パターンの分析 May 09, 2024 pm 03:12 PM

デコレータ パターンは、元のクラスを変更せずにオブジェクトの機能を動的に追加できる構造設計パターンです。抽象コンポーネント、具象コンポーネント、抽象デコレータ、具象デコレータの連携によって実装され、ニーズの変化に合わせてクラス機能を柔軟に拡張できます。この例では、ミルクとモカのデコレーターが総額 2.29 ドルで Espresso に追加されており、オブジェクトの動作を動的に変更するデコレーター パターンの力を示しています。

PHP設計パターンの実践事例分析 PHP設計パターンの実践事例分析 May 08, 2024 am 08:09 AM

1. ファクトリ パターン: オブジェクト作成とビジネス ロジックを分離し、ファクトリ クラスを通じて指定された型のオブジェクトを作成します。 2. オブザーバー パターン: サブジェクト オブジェクトが状態の変化をオブザーバー オブジェクトに通知できるようにし、疎結合とオブザーバー パターンを実現します。

Java 設計パターンにおけるアダプター パターンの素晴らしい使用法 Java 設計パターンにおけるアダプター パターンの素晴らしい使用法 May 09, 2024 pm 12:54 PM

アダプター パターンは、互換性のないオブジェクトが連携できるようにする構造設計パターンであり、オブジェクトがスムーズに対話できるように、あるインターフェイスを別のインターフェイスに変換します。オブジェクト アダプタは、適応されたオブジェクトを含むアダプタ オブジェクトを作成し、ターゲット インターフェイスを実装することにより、アダプタ パターンを実装します。実際のケースでは、クライアント (MediaPlayer など) はアダプター モードを通じて高度な形式のメディア (VLC など) を再生できますが、クライアント自体は通常のメディア形式 (MP3 など) のみをサポートします。

デザインパターンがコードメンテナンスの課題にどのように対処するか デザインパターンがコードメンテナンスの課題にどのように対処するか May 09, 2024 pm 12:45 PM

デザイン パターンは、再利用可能で拡張可能なソリューションを提供することで、コード メンテナンスの課題を解決します。 オブザーバー パターン: オブジェクトがイベントをサブスクライブし、イベントが発生したときに通知を受信できるようにします。ファクトリ パターン: 具象クラスに依存せずにオブジェクトを作成するための集中的な方法を提供します。シングルトン パターン: クラスには、グローバルにアクセス可能なオブジェクトの作成に使用されるインスタンスが 1 つだけ存在することが保証されます。

PHP デザイン パターン: テスト駆動開発の実践 PHP デザイン パターン: テスト駆動開発の実践 Jun 03, 2024 pm 02:14 PM

TDD は、高品質の PHP コードを作成するために使用されます。その手順には、テスト ケースを作成し、期待される機能を記述し、テスト ケースを失敗させることが含まれます。過度な最適化や詳細な設計を行わずに、テスト ケースのみが通過するようにコードを記述します。テスト ケースが合格したら、コードを最適化およびリファクタリングして、可読性、保守性、およびスケーラビリティを向上させます。

Guice フレームワークでのデザイン パターンの適用 Guice フレームワークでのデザイン パターンの適用 Jun 02, 2024 pm 10:49 PM

Guice フレームワークは、次のような多くの設計パターンを適用します。 シングルトン パターン: @Singleton アノテーションによってクラスのインスタンスが 1 つだけであることを保証します。ファクトリ メソッド パターン: @Provides アノテーションを使用してファクトリ メソッドを作成し、依存関係の注入中にオブジェクト インスタンスを取得します。戦略モード: アルゴリズムをさまざまな戦略クラスにカプセル化し、@Named アノテーションを通じて特定の戦略を指定します。

See all articles