Interface(インターフェース)

2010.11.16kickbaseActionScript3.0


今回はInterface(インターフェース)について解説します。インターフェースとは、「一連のやり取りを保障する指示書」のようなものです。インターフェースの中には必要なメソッドを記載していくのですが、メソッドの中身(実装)は記載しません。「こういう機能は必ずつけてね」という指示書のようなものなのです。

例として、ヤカンをとりあげてみましょう。ヤカンは「お湯を沸かす」ことができなければいけません。しかし、「お湯を沸かす」ことさえ保障されていれば、電気で沸かしても、火にかけてもいいわけです。

つまり、ヤカンインターフェースを実装しているクラスは、どんな方法を使っているかは分かりませんが、「お湯を沸かす」ことはできると言い切れるということになります。

インターフェースを使うと設計意図をより明確にし、実装漏れを減らし、使用段階での理解度を高める役割を果たしてくれます。「大人数で実装するようなプロジェクト」では非常に役に立ちますし、「長期的な案件」でも設計指針がぶれないため、有用でしょう。

そしてもう一つの使用法として「インターフェースをデータ型として使う」というものがあります。本例ではこの手法を説明します。
※デザインパターンのひとつ「Strategy(ストラテジー)」を例として取り上げています。少し難しい内容もありますが、ぜひトライしてください。

実装としてはおなじみの「ステージ上に円があり、ステージをクリックしたときと円をクリックしたときでトレース結果が違う」という簡単なものとします。

まずは通常の方法を示します。

01 : 通常の方法

Main

package 
{
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	
	/**
	 * ...
	 * @author kickbase
	 */
	[SWF(width = "640", height = "480", frameRate = "30", backgroundColor = "#ffffff")]
	 
	public class Main extends Sprite 
	{
		private var ball:Ball = new Ball(100);
		
		public function Main():void 
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			//ステージ設定
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			
			//円をステージ中央に配置
			ball.x = stage.stageWidth / 2;
			ball.y = stage.stageHeight / 2;
			addChild(ball);
			
			//ステージに対してマウスクリックイベントをリスン
			stage.addEventListener(MouseEvent.CLICK, onClick);
		}
		
		private function onClick(e:MouseEvent):void 
		{
			if (e.target == e.currentTarget)
			{
				trace("ステージをクリックしましたね");
			}
			else 
			{
				trace("円をクリックしましたね");
			}
		}
	}
}

Ball

package
{
	import flash.display.Sprite;

	public class Ball extends Sprite
	{
		private var _radius:Number;
		private var _color:uint;
		
		/*--------------------------------------------------------------------------
		 * コンストラクタ
		 *---------------------------------------------------------------------*//**
		 * <p>円を描くクラス(引数を省略すると、半径20、ランダムカラーの円が描画される)</p> 
		 */
		public function Ball(radius:Number = 20, color:uint = 0x0)
		{
			color ||= Math.random() * 0xFFFFFF;
			this._radius = radius;
			this._color = color;
			draw();
		}

		private function draw():void
		{
			with (graphics)
			{
				beginFill(_color);
				drawCircle(0, 0, _radius);
				endFill();
			}
		}
	}
}

02 : インターフェースをデータ型として使う

見ての通りいきなり複雑になりましたね。「これのどこが便利なんだ?」と思う方もいるかと思います。順を追って解説しましょう。
今回のソースでポイントとなるのは2点です。

1.ドキュメントクラスのonClickメソッド内において、executeメソッドを実行しているのは、IStrategy型のオブジェクトだと言う点
2.交換可能なオブジェクト(今回ではStageClickedとBallClicked)は、IStrategyインターフェースを実装し、executeメソッドを必ず保持している点

これらについて見ていきます。

ドキュメントクラスにおいてclickObjをIStrategy型として宣言したのは、IStrategyインターフェースを実装したクラスでさえあればclickObjに何を代入しても、確実にexecuteメソッドがあることが保障されるからです。これによって、「clickObj.execute();」は何の疑いもなく実行することができます。そしてその実行内容は、StageClickedとBallClickedに委譲することができるのです。

このように、インターフェースに対してプログラミングを行うと、実行する側(クライアント)と実装(アルゴリズム)を分離することが可能になります。
StageClickedの仲間がいくら増えても、clickObjにそれらを代入するだけで、いくらでも実装を入れ替えることができるのです。

インターフェースに対してプログラミングを行う最大のメリットは、実装を分離できると言う点です。
アルゴリズムを分離できるということは、再利用も入れ替えも、修正もしやすい柔軟なプログラムになるのです。

Main

package 
{
	import flash.display.*;
	import flash.events.*;
	import strategy.*;
	
	/**
	 * ...
	 * @author kickbase
	 */
	[SWF(width = "640", height = "480", frameRate = "30", backgroundColor = "#ffffff")]
	 
	public class Main extends Sprite 
	{
		private var ball:Ball = new Ball(100);
		
		private var clickObj:IStrategy;
		private var stageClicked:StageClicked = new StageClicked();
		private var ballClicked:BallClicked = new BallClicked();
		
		public function Main():void 
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			
			ball.x = stage.stageWidth / 2;
			ball.y = stage.stageHeight / 2;
			addChild(ball);
			
			stage.addEventListener(MouseEvent.CLICK, onClick);
		}
		
		private function onClick(e:MouseEvent):void 
		{
			if (e.target == e.currentTarget)
			{
				//IStrategyを実装しているクラスなら何でも代入可能
				clickObj = stageClicked;
			}
			else 
			{
				//IStrategyを実装しているクラスなら何でも代入可能
				clickObj = ballClicked;
			}
			
			//clickObjはIStrategy型なので、必ずexecuteメソッドを持っていることを保障されている
			clickObj.execute();
		}
	}
}

IStrategy

package strategy 
{
	/**
	 * ...
	 * @author kickbase
	 */
	public interface IStrategy 
	{
		/*
		 * インターフェースではメソッドにアクセス制限キーワードは宣言しません
		 * 必ずpublic属性になります
		*/
		function execute():void;
	}
}

StageClicked

package strategy
{
	/**
	 * ...
	 * @author kickbase
	 */
	public class StageClicked implements IStrategy
	{
		//インターフェースで定義されている、executeメソッドだけは必ず実装する必要があります。
		public function execute():void
		{
			trace("ステージをクリックしましたね");
		}
	}
}

BallClicked

package strategy 
{
	/**
	 * ...
	 * @author kickbase
	 */
	public class BallClicked implements IStrategy
	{
		//インターフェースで定義されている、executeメソッドだけは必ず実装する必要があります。
		public function execute():void
		{
			trace("円をクリックしましたね");
		}
	}
}

サンプルのダウンロード


ページトップへ