= AOP (Aspect-Oriented Programming) アスペクト指向プログラミングに 関する十の神話 増原英彦 ([email protected]) 東京大学 大学院 総合文化研究科 第37回情報科学若手の会招待講演 2004年9月 1 概要 • AOP入門 – モジュール化の意義 – モジュール化のための既存の技術 – 既存の技術の限界 ―― 横断的関心事 – AOP と AspectJの基本機能 • ポイントカット・アドバイス機構 • 型間宣言機構 • 神話 2 お母さんのためのAOP入門 料理のレシピに見る 横断的関心事 3 プログラムとは レシピのようなものだ • 両者とも「どのような順序で どのような操作を行うか」を記述 ビーフカレーの作り方 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参 1本・玉葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カ レールー1箱 1. 牛肉を一口大に切り、サラダ油を熱した鍋で炒める 2. 1に一口大に切ったじゃがいも、人参を加えて炒める 3. 玉葱をみじん切りにして、バターを熱したフライパンで炒める 4. 1に2を合わせ、ベイリーフ、水を加えて30分煮込む 5. アクをとり、カレールーを加えてさらに20分煮込む 4 レシピはモジュール化されてる! 「決定版 はじめての料理」 モジュール化 = 一連の手順に 名前を付け、別に定義すること • 使用側では手順の詳細を 気にしなくてよい void void みじん切り(材料) みじん切り(材料) {{ 1. 1. 材料の皮をむく 材料の皮をむく 2. 2. for for aa in in X,Y,Z X,Y,Z 材料をa軸方向に4~ 材料をa軸方向に4~ 5mm間隔に包丁で切る 5mm間隔に包丁で切る • 定義側を独立して変更できる • 同じ手順が何度も出現する 場合に定義を共有できる ― 変更も1回 void void 皮をむく(材料) 皮をむく(材料) {{ switch(材料) switch(材料) {{ case case 玉葱: 玉葱: 1. 1に一口大に切ったじゃがいも、人参を 茶色の薄皮を手でむく 茶色の薄皮を手でむく 2. 玉葱をみじん切りにして、バターを熱した case case 人参: 人参: ピーラーを使って… }} }} 3. 1に2を合わせ、ベイリーフ、水を加えて30 ピーラーを使って… 4. アクをとり、カレールーを加えてさらに20分 5 }} 「みじん切り」の効用 「決定版 はじめての料理」 モジュール化 = 一連の手順に 名前を付け、別に定義すること • 使用側では手順の詳細を 気にしなくてよい • 定義側を独立して変更できる • 同じ手順が何度も出現する 場合に定義を共有できる ― 変更も1回 void void みじん切り(材料) みじん切り(材料) {{ 1. 1. 材料の皮をむく 材料の皮をむく 2. 2. for for aa in in X,Y,Z X,Y,Z 材料をa軸方向に4~ 材料をa軸方向に4~ 5mm間隔に包丁で切る 5mm間隔に包丁で切る }} 7. 1に一口大に切ったじゃがいも、 8. にんにくをみじん切りにして、バタ にんにくをみじん切りにして、バ 9. 1に2を合わせ、ベイリーフ、水を 1. 1に一口大に切ったじゃがいも、人参を 10.アクをとり、カレールーを加えて 2. 玉葱をみじん切りにして、バターを熱した 3. 1に2を合わせ、ベイリーフ、水を加えて30 4. アクをとり、カレールーを加えてさらに20分 6 「みじん切り」の効用 「決定版 はじめての料理」 モジュール化 = 一連の手順に 名前を付け、別に定義すること • 使用側では手順の詳細を 気にしなくてよい void void みじん切り(材料) みじん切り(材料) {{ 1. 1. 材料の皮をむく 材料の皮をむく 2. 2. for for aa in in X,Y,Z X,Y,Z 材料をa軸方向に4~ 材料をa軸方向に4~ 5mm間隔に包丁で切る 5mm間隔に包丁で切る • 定義側を独立して変更できる • 同じ手順が何度も出現する 場合に定義を共有できる ― 変更も1回 void void みじん切り(材料) みじん切り(材料) {{ 1. 1. 材料の皮をむく 材料の皮をむく 1. 1に一口大に切ったじゃがいも、人参を 2. 2. 材料をぶつ切りにして 材料をぶつ切りにして ミキサーで混ぜる }} 2. 玉葱をみじん切りにして、バターを熱した ミキサーで混ぜる 3. 1に2を合わせ、ベイリーフ、水を加えて30 4. アクをとり、カレールーを加えてさらに20分 7 }} 横断的関心事とは 「本格派」のようなもの • 関心事 = ひとまとまりと考えられる 手順・材料群・コツ・etc. (モジュール = 関心事を ひとまとめに記述したもの) • 横断的関心事 = モジュールに分けて書いたときに、 複数のモジュールにまたがる関心事 – その関心事は散在してしまう – 他の関心事はもつれてしまう 8 横断的関心事とは 「本格派」のようなもの普通の 本格派ビーフカレーの作り方 レシピ±α 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1本・玉 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1本・玉 葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレールー1箱・カ 葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレールー1箱・カ レー粉大さじ3・トマトジュース200cc・ヨーグルト1カップ・ガラムマサ レー粉大さじ3・トマトジュース200cc・ヨーグルト1カップ・ガラムマサ ラ適量・ブイヨン3個 ラ適量・ブイヨン3個 void void 玉葱を炒める() 玉葱を炒める() {{ 1. 1. 牛肉を一口大に切り、カレー粉小さじ1と一緒にサラダ油を熱した鍋 牛肉を一口大に切り、カレー粉小さじ1と一緒にサラダ油を熱した鍋 で炒める 1. で炒める 1. 弱火のフライパンでバターを 弱火のフライパンでバターを 2. 溶かす 2. 1に一口大に切ったじゃがいも、人参を加えて炒める 1に一口大に切ったじゃがいも、人参を加えて炒める 溶かす 2. 2. while while (20分過つまで (20分過つまで 3. 3. 玉葱をみじん切りにして、バターを熱したフライパンで炒める 玉葱をみじん切りにして、バターを熱したフライパンで炒める 飴茶色になるまで) 飴茶色になるまで) 4. 4. 1に2を合わせ、ベイリーフ、水、ブイヨン、トマトジュースを加えて30 1に2を合わせ、ベイリーフ、水、ブイヨン、トマトジュースを加えて30 玉葱をかき混ぜる 玉葱をかき混ぜる 分煮込む 分煮込む 焦げそうになったときは 焦げそうになったときは 5. 5. アクをとり、カレールー残りのカレー粉を加えてさらに20分煮込む アクをとり、カレールー残りのカレー粉を加えてさらに20分煮込む フライパンを火から離し フライパンを火から離し 6. 仕上げにヨーグルト1カップを加え混ぜ、ガラムマサラをかける 6. 仕上げにヨーグルト1カップを加え混ぜ、ガラムマサラをかける }} 火力を調節する 火力を調節する 9 横断的関心事とは 「本格派」のようなもの 本格派ビーフカレーの作り方 • 散在 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1本・玉 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1本・玉 葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレールー1箱・カ 葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレールー1箱・カ • もつれ レー粉大さじ3・トマトジュース200cc・ヨーグルト1カップ・ガラムマサ レー粉大さじ3・トマトジュース200cc・ヨーグルト1カップ・ガラムマサ ラ適量・ブイヨン3個 ラ適量・ブイヨン3個 void void 玉葱を炒める() 玉葱を炒める() {{ 1. 1. 牛肉を一口大に切り、カレー粉小さじ1と一緒にサラダ油を熱した鍋 牛肉を一口大に切り、カレー粉小さじ1と一緒にサラダ油を熱した鍋 で炒める 1. で炒める 1. 弱火のフライパンでバターを 弱火のフライパンでバターを 2. 溶かす 2. 1に一口大に切ったじゃがいも、人参を加えて炒める 1に一口大に切ったじゃがいも、人参を加えて炒める 溶かす 2. 2. while while (20分過つまで (20分過つまで 3. 3. 玉葱をみじん切りにして、バターを熱したフライパンで炒める 玉葱をみじん切りにして、バターを熱したフライパンで炒める 飴茶色になるまで) 飴茶色になるまで) 4. 4. 1に2を合わせ、ベイリーフ、水、ブイヨン、トマトジュースを加えて30 1に2を合わせ、ベイリーフ、水、ブイヨン、トマトジュースを加えて30 玉葱をかき混ぜる 玉葱をかき混ぜる 分煮込む 分煮込む 焦げそうになったときは 焦げそうになったときは 5. 5. アクをとり、カレールー残りのカレー粉を加えてさらに20分煮込む アクをとり、カレールー残りのカレー粉を加えてさらに20分煮込む フライパンを火から離し フライパンを火から離し 6. 仕上げにヨーグルト1カップを加え混ぜ、ガラムマサラをかける 6. 仕上げにヨーグルト1カップを加え混ぜ、ガラムマサラをかける 火力を調節する }} 火力を調節する 10 AOPとは「本格派」を分けて 書く技術 メリット 本格派の場合 本格派の場合 •• カレールーのかわりにカレー粉 カレールーのかわりにカレー粉 • 「普通のレシピ」と共存 大さじ3を使う 大さじ3を使う •• 牛肉はカレー粉大さじ1を加えて 牛肉はカレー粉大さじ1を加えて • 「本格派」を別レシピに 炒める 炒める 時間のない場合 時間のない場合 •• 玉葱を炒めるときは、飴茶色に 玉葱を炒めるときは、飴茶色に 再利用 マトンカレーの作り方 •• 玉葱も一口大に切り、他の野菜 玉葱も一口大に切り、他の野菜 なるまでじっくりと。焦げそうに なるまでじっくりと。焦げそうに と一緒に炒める と一緒に炒める 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1 なったときはフライパンを火から ビーフカレーの作り方 なったときはフライパンを火から 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1 本・玉葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレー 本・玉葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレー •• 炒め終わったら圧力鍋で5分間 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1 炒め終わったら圧力鍋で5分間 材料(4人分):牛肉300g・サラダ油大さじ2・じゃがいも4個・人参1 本・玉葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレー 離し火力を調節する 本・玉葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレー ルー1箱 離し火力を調節する ルー1箱 本・玉葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレー 本・玉葱1個・ベイリーフ1枚・水5カップ・バター大さじ2・カレー ルー1箱 ルー1箱 煮る 煮る 1. 1. 牛肉を一口大に切り、サラダ油を熱した鍋で炒める 牛肉を一口大に切り、サラダ油を熱した鍋で炒める ルー1箱 •• 具を煮込む際に、ブイヨン3個と ルー1箱 具を煮込む際に、ブイヨン3個と 野菜カレーの作り方 1. 1. 2. 2. 3. 3. 4. 4. 5. 5. 1. 1. 牛肉を一口大に切り、サラダ油を熱した鍋で炒める 牛肉を一口大に切り、サラダ油を熱した鍋で炒める 2. 2. 1に一口大に切ったじゃがいも、人参を加えて炒める 1に一口大に切ったじゃがいも、人参を加えて炒める 牛肉を一口大に切り、サラダ油を熱した鍋で炒める 牛肉を一口大に切り、サラダ油を熱した鍋で炒める トマトジュース200ccを加える 2. トマトジュース200ccを加える 2. 1に一口大に切ったじゃがいも、人参を加えて炒める 1に一口大に切ったじゃがいも、人参を加えて炒める 3. 3. 玉葱をみじん切りにして、バターを熱したフライパンで炒める 玉葱をみじん切りにして、バターを熱したフライパンで炒める 1に一口大に切ったじゃがいも、人参を加えて炒める 1に一口大に切ったじゃがいも、人参を加えて炒める 3. 3. 玉葱をみじん切りにして、バターを熱したフライパンで炒める 玉葱をみじん切りにして、バターを熱したフライパンで炒める 4. 4. 1に2を合わせ、ベイリーフ、水を加えて30分煮込む 1に2を合わせ、ベイリーフ、水を加えて30分煮込む •• 仕上げにヨーグルト1カップを加 仕上げにヨーグルト1カップを加 玉葱をみじん切りにして、バターを熱したフライパンで炒める 玉葱をみじん切りにして、バターを熱したフライパンで炒める 4. 1に2を合わせ、ベイリーフ、水を加えて30分煮込む 4. 1に2を合わせ、ベイリーフ、水を加えて30分煮込む 5. 5. アクをとり、カレールーを加えてさらに20分煮込む アクをとり、カレールーを加えてさらに20分煮込む 1に2を合わせ、ベイリーフ、水を加えて30分煮込む 1に2を合わせ、ベイリーフ、水を加えて30分煮込む え、ガラムマサラをかける え、ガラムマサラをかける 5. アクをとり、カレールーを加えてさらに20分煮込む 5. アクをとり、カレールーを加えてさらに20分煮込む アクをとり、カレールーを加えてさらに20分煮込む アクをとり、カレールーを加えてさらに20分煮込む 11 以上、 お母さんのためのAOP入門 ―本題― プログラミング言語では? 12 プログラミング言語における モジュール化とは何か? 「1つ」の操作や値を1まとめにして インターフェースを決めること • 1まとめ → 使う側は細部を 忘れることができる • インターフェース (型・仕様・etc.) ← 「使う側」に対する約束 → 「中身」を「使う側」と 独立して変更できる 13 モジュール化のための技術 対象 操作 値 操作と値 モジュール 技術 サブルーチン・関数・手続き 構造体・レコード 抽象データ型・クラス・structure コンポーネント・functor 14 モジュール化のための技術 対象 操作 値 操作と値 モジュール 技術 サブルーチン・関数・手続き 構造体・レコード 抽象データ型・クラス・structure コンポーネント・functor 要因にきれいに分解された 解をもたないような問題は, しょせん本当の意味で満足には 解決できない問題なのだ (Dijkstra,1972) 15 モジュール化の例 (OOP) • 図形エディタ [Kiczales, et al. 2001] – 特徴: Illustratorのようなもの • 「点」「線」「円」などの部品単位で描画・操作できる • 対話的な描画―移動すると再描画 • モジュール化の方針 – 図形要素をクラスに – 「移動」「描画」等の操作をメソッドに 16 モジュール化の例 (OOP) • 図形エディタ class FigElm {{ class FigElm void void moveby(int moveby(int dx, dx, int int dy); dy); void void display(Display display(Display d); d); }} class Display {{ class Display static static List<FigElm> List<FigElm> figures figures == …; …; static static void void redraw redraw() () {display.draw();} {display.draw();} void void draw draw() () {{ for for (fig (fig :: figures) figures) fig.draw(d); fig.draw(d); }} }} 図形の形による • 違いを隠す class class class Point Point extends extends FigElm FigElm {{ Line extends extends FigElm FigElm {{ class Line • 共通する性質を int Point int x, x, y; y; Point p1, p1, p2; p2; 「線」の内部表現と int x; Point () int getX getX() () {{ return return x; }} Point getP1 getP1 () {{ return return p1; p1; }} まとめる int Point int getY() getY() {{ … … }} Point getP2() getP2() {{ … … }} 使い方を分離 void void (int dx, dx, int int dy) dy) void moveby(int (int dx, dx, int int dy) dy) {{ void moveby(int {{ x+=dx; Display.redraw(); }} x+=dx; y+=dy; y+=dy; Display.redraw(); void void setX setX(int (int x) x) {{ this.x this.x == x; x; Display.redraw Display.redraw(); (); }} void void setY(int setY(int y) y) {{ … … }} void void draw(Display draw(Display d) d) {{ … … }} }} p1.moveby(dx,dy); p1.moveby(dx,dy); p2.moveby(dx,dy); p2.moveby(dx,dy); Display.redraw Display.redraw(); (); }} void void setP1 setP1(Point (Point p1) p1) {{ this.p1 this.p1 == p1; p1; Display.redraw Display.redraw(); (); }} void void setP2(Point setP2(Point p2) p2) {{ … … }} void void draw(Display draw(Display d) d) {{ … … }} }} 17 複雑なデータを1つの 値として扱える Q: 横断的関心事は何? • 図形エディタ class FigElm {{ class FigElm void void moveby(int moveby(int dx, dx, int int dy); dy); void void display(Display display(Display d); d); }} class class Point Point extends extends FigElm FigElm {{ int int x, x, y; y; int int getX getX() () {{ return return x; x; }} int int getY() getY() {{ … … }} void (int dx, dx, int int dy) dy) void moveby(int {{ x+=dx; Display.redraw(); }} x+=dx; y+=dy; y+=dy; Display.redraw(); void void setX setX(int (int x) x) {{ this.x this.x == x; x; Display.redraw Display.redraw(); (); }} void void setY(int setY(int y) y) {{ … … }} void void draw(Display draw(Display d) d) {{ … … }} }} class Display {{ class Display static static List<FigElm> List<FigElm> figures figures == …; …; static static void void redraw redraw() () {display.draw();} {display.draw();} void void draw draw() () {{ for for (fig (fig :: figures) figures) fig.draw(d); fig.draw(d); }} }} class Line extends extends FigElm FigElm {{ class Line Point Point p1, p1, p2; p2; Point Point getP1 getP1() () {{ return return p1; p1; }} Point Point getP2() getP2() {{ … … }} void (int dx, dx, int int dy) dy) {{ void moveby(int p1.moveby(dx,dy); p1.moveby(dx,dy); p2.moveby(dx,dy); p2.moveby(dx,dy); Display.redraw Display.redraw(); (); }} void void setP1 setP1(Point (Point p1) p1) {{ this.p1 this.p1 == p1; p1; Display.redraw Display.redraw(); (); }} void void setP2(Point setP2(Point p2) p2) {{ … … }} void void draw(Display draw(Display d) d) {{ … … }} }} 18 A:図形が変化する度に再描画 class FigElm {{ class FigElm void void moveby(int moveby(int dx, dx, int int dy); dy); void void display(Display display(Display d); d); }} 縺れ class class Point Point extends extends FigElm FigElm {{ • x,x,抽象図形として int y; int y; int () int getX getX () {{ return return x; x; }} 利用するときに邪魔 int int getY() getY() {{ … … }} void (int dx, dx, int int dy) dy) void moveby(int {{ x+=dx; Display.redraw(); }} x+=dx; y+=dy; y+=dy; Display.redraw(); void void setX setX(int (int x) x) {{ this.x this.x == x; x; Display.redraw Display.redraw(); (); }} void void setY(int setY(int y) y) {{ … … }} }} class class Display Display {{ static static List<FigElm> List<FigElm> figures figures == …; …; static static void void redraw redraw() () {{ … … }} }} 散在 • 再描画の class class Line Line extends extends FigElm FigElm {{ 方針変更時に Point Point p1, p1, p2; p2; Point () Point getP1 getP1 () {{ return return p1; p1; }} 変更箇所が沢山 Point Point getP2() getP2() {{ … … }} void (int dx, dx, int int dy) dy) {{ void moveby(int p1.moveby(dx,dy); p1.moveby(dx,dy); p2.moveby(dx,dy); p2.moveby(dx,dy); Display.redraw Display.redraw(); (); }} void void setP1 setP1(Point (Point p1) p1) {{ this.p1 this.p1 == p1; p1; Display.redraw Display.redraw(); (); }} void void setP2(Point setP2(Point p2) p2) {{ … … }} }} 19 問題: 横断的関心事 • 1まとめに扱いたいものなのに モジュールをまたがるもの 既存のモジュール化技術では 避けられない: ※ 「関心事」と呼ぶ – 「線を移動する」機能 ― Line.movebyメソッド – 「点」というデータ ― Pointクラス – 「変化があると再描画する」機能 ― 横断的 20 アスペクト指向プログラミング (AOP) • 横断的関心事をモジュール化する技術 [Kiczales+97] 複数のモジュールにまたがる 操作や構造の記述手段 • 具体的な処理系: AspectJ [Kiczales+01], 他多数 • 典型的応用: ログ・デバグ・最適化・ セキュリティ等のモジュール化 21 AspectJ入門: 適用結果 • 図形エディタ class class FigElm FigElm {{ void void moveby(int moveby(int dx, dx, int int dy);} dy);} class class Display Display {{ static static List<FigElm> List<FigElm> figures figures == …; …; static static void void redraw() redraw() {display.draw();} {display.draw();} void void draw() draw() {{ for for (fig (fig :: figures) figures) fig.draw(d); fig.draw(d); }} }} 描画方針と独立 class class Point Point extends extends FigElm FigElm {{ int int x, x, y; y; int int getX() getX() {{ return return x; x; }} int int getY() getY() {{ … … }} void void moveby(int moveby(int dx, dx, int int dy) dy) {{ x+=dx; x+=dx; y+=dy; y+=dy; }} void void setX(int setX(int x) x) {{ this.x this.x == x; x; }} void void setY(int setY(int y) y) {{ … … }} }} 図形が変化する 度に再描画する “アスペクト” class class Line Line extends extends FigElm FigElm {{ aspect aspect DisplayUpdating DisplayUpdating {{ Point Point p1, p1, p2; p2; pointcut pointcut move() :: Point p1; Point getP1() getP1() {{ return return p1; }} move() call(int FigElm.moveby(int,int)) FigElm.moveby(int,int)) || || Point Point getP2() getP2() {{ … … }} call(int call(void Point.setX(int)) || || void int void moveby(int moveby(int dx, dx,call(void int dy) dy) {{ Point.setX(int)) Point.setY(int)) call(void Point.setY(int)) || || p1.moveby(dx,dy); p2.moveby(dx,dy);} p1.moveby(dx,dy); call(void p2.moveby(dx,dy);} || call(void || void {{ this.p1 =Line.setP1(Point)) void setP1(Point setP1(Point p1) p1)call(void this.p1 Line.setP1(Point)) = p1; p1; }} call(void Line.setP2(Point)); void {{ … void setP2(Point setP2(Point p2) p2)call(void … }} }} Line.setP2(Point)); after() after() :: move() move() {{ Display.redraw(); Display.redraw(); }} void void FigElm.draw(Display FigElm.draw(Display d); d); void void Point.draw(Display Point.draw(Display d) d) {{ … … }} … … }} 22 AspectJの主要概念 • アスペクト (cf. クラス) 横断的関心事をまとめる単位 • アドバイス (cf. メソッド) 動作 追加的な操作 • ポイントカット (cf. シグネチャ) 適用時点を決める • 型間定義 追加的宣言 構造 23 AspectJの主要概念 • • • • アスペクト アドバイス ポイントカット 型間定義 •aspect 前後に追加する動作 DisplayUpdating { pointcut move() : • 置き換える動作 call(int FigElm.moveby(int,int)) || call(int FigElm.moveby(int,int)) || cf. CLOS の before / after / call(void Point.setX(int)) || aroundメソッド call(void Point.setY(int)) || moveしたら,その後で Display.redraw()を呼べ call(void Line.setP1(Point)) || call(void Line.setP2(Point)); after() : move() { Display.redraw(); } void FigElm.draw(Display d); void Point.draw(Display d) { … } …} 24 AspectJの主要概念 • • • • アスペクト アドバイス ポイントカット 型間定義 moveするとは FigElm.moveby, Point.setX, …を 呼び出すこと aspect DisplayUpdating { pointcut move() : call(int FigElm.moveby(int,int)) || call(void Point.setX(int)) || call(void Point.setY(int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)); after() : move() { Display.redraw(); } • メソッド呼出・フィールド void FigElm.draw(Display d); アクセス・コンストラクタ void Point.draw(Display d) { … } 実行・例外発生・etc. …} • 組み合わせ・名前付け 25 AspectJの主要概念 • • • • アスペクト アドバイス ポイントカット 型間定義 FigElmクラスにdraw メソッドを追加 DisplayUpdating { •aspect メソッド・フィールドの追加 pointcut move() : • 親クラス・実装クラスの追加 call(int FigElm.moveby(int,int)) || call(void Point.setX(int)) || call(void Point.setY(int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)); after() : move() { Display.redraw(); } void FigElm.draw(Display d); void Point.draw(Display d) { … } …} 26 Java AspectJの実行モデル ====== class class FigElm FigElm {{ … … }} p.setX(10); setX(10)を 呼び出す Point x=110 y=3 class class Point Point {{ int int x, x, y; y; int int getX() getX() void void moveby(int,int) moveby(int,int) void void setX(int) setX(int) }} class class Display Display {{ … … }} class class Line Line {… {… }} class class Main Main {… {… }} aspect aspect DisplayUpdating DisplayUpdating {{ pointcut pointcut move() move() :: call(int call(int FigElm.moveby(int,int)) FigElm.moveby(int,int)) || || …; …; after() after() :: move() move() {{ … … }} void void FigElm.draw(Display FigElm.draw(Display d); d); void void Point.draw(Display Point.draw(Display d) d) {{ … … }} … … }} 27 AspectJの実行モデル fig.draw(d); p.setX(10); drawを setX(10)を 呼び出す 呼び出す Point x=110 y=3 class class FigElm FigElm {{ … … }} class class Point Point {{ int int x, x, y; y; int int getX() getX() void void moveby(int,int) moveby(int,int) void void setX(int) setX(int) }} 画面を 更新 class class Display Display {{ … … }} class class Line Line {… {… }} class class Main Main {… {… }} aspect aspect DisplayUpdating DisplayUpdating {{ pointcut pointcut move() move() :: call(int call(int FigElm.moveby(int,int)) FigElm.moveby(int,int)) || || …; …; after() after() :: move() move() {{ … … }} void void FigElm.draw(Display FigElm.draw(Display d); d); void void Point.draw(Display Point.draw(Display d) d) {{ … … }} … … }} ポイントカットに 合致 アドバイス本体を実行 結合点と呼ぶ 追加されたメソッドを実行 28 AspectJの実行モデル • ポイントカット+アドバイス機構 – メソッド呼び出し等の各動作(=結合点)が – ポイントカットに合致した場合に – アドバイスを前,後,あるいは置き換えて実行 • 型間宣言機構 – 各クラス(=結合点)が – 型パターンに合致した場合に – メソッド等の宣言が追加されたものとして扱う 29 what’s good? ― ポイントカット! • FigElm・Point・Line に対する動作 → まとめて指定 ↓ 横断的関心事を モジュール化! aspect aspect DisplayUpdating DisplayUpdating {{ pointcut move() : call(int FigElm.moveby(int,int)) || call(void Point.setX(int)) || call(void Point.setY(int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)); after() after() :: move() move() {{ Display.redraw(); Display.redraw(); }} … … }} 30 what’s good? ― ポイントカット! aspect aspect DisplayUpdating DisplayUpdating {{ • ワイルドカードに pointcut move() : call(int FigElm.moveby(int,int)) || よって簡潔に call(* FigElm+.set*(..)); 定義できる after() after() :: move() move() {{ Display.redraw(); Display.redraw(); }} • 例: FigElmとその …… }} 子クラスにある “set”で始まる名前のメソッドが 呼び出されたとき 31 what’s good? ― ポイントカット! • 複雑な条件も 指定できる Q: p.moveby(2,3);は 何回redrawを呼ぶ? A: 3回 1回にするには? aspect aspect DisplayUpdating DisplayUpdating {{ pointcut move() : call(int FigElm.moveby(int,int)) || call(* FigElm+.set*(..)); after() after() :: move() move() {{ Display.redraw(); Display.redraw(); }} … … }} class class Point Point … … {{ void moveby(int dx, int dy) { setX(getX()+dx); setY(getY()+dy); } } 32 what’s good? ― ポイントカット! • 複雑な条件の例: 「move()の中から 呼ばれたものを除く move()」 setX after() after() :: move() … … }} && !cflowbelow(move()) {{ …… }} setY moveby main aspect aspect DisplayUpdating DisplayUpdating {{ pointcut pointcut move() move() :: call(int call(int FigElm.moveby(int,int)) FigElm.moveby(int,int)) || || call(* call(* FigElm+.set*(..)); FigElm+.set*(..)); class class Point Point … … {{ void moveby(int dx, int dy) { setX(getX()+dx); setY(getY()+dy); } } 33 AspectJ入門: まとめ • アスペクト: 横断的関心事のためのモジュール • ポイントカット+アドバイス: 複数のモジュールで起きる動作に対し 動作を追加する機構 • 型間定義 複数のモジュールに対し, 宣言を追加する機構 対象を 対象を指定する 指定する方法 する方法が 方法が強力 (e.g.,ポイントカット) 34 AOPにまつわる神話 • AOPの生い立ち サブジェクト指向 適応的 開放型 自己反映 プログラミング プログラミング 実現 プログラミング 特定領域向けAOP (分散・並列・・・) 汎用AOP 35 神話一: AOPはオブジェクト指向 の次に来るパラダイムだ • プログラム言語は構造化プログラミング→ 抽象データ型→オブジェクト指向→ アスペクト指向と進化している • AOP ≠ オブジェクト指向の置き換え – むしろ追加的な機構 36 神話二: AOPはOOPの 欠点を改善する技術だ • クラス・メソッドを使っても 上手くモジュール化できなかったものを モジュール化するための技術 • OOPに限らず モジュールのある言語に有効 – AspectC [Coady他02] – AspectML [Dantas, Walker03] – レシピ!? 37 神話三: アスペクトととは 従属的なモジュールだ • 支配的なモジュール: 例) 図形の種類 → クラスでモジュール化 • 従属的モジュール: 例) 画面更新 → アスペクトでモジュール化 • 対等なモジュール分割どうしを合成する という考え方もあるHype/J [Ossher,Tarr01], CME + = 38 Hyper/J [Ossher01] • 関心事ごとにクラス階層を作成 (支配的なモジュールがない) • 階層どうしを合成して実行 Display Display update(FigElm) update(FigElm) Figure Figure elements elements – 合成方法を指定する言語 • 既存プログラムから 関心事を抽出 Observable moved() { display.update(this); } mma mma attcchh Point Point P mma attcchh Pooiinnt getX() getX() P a t t . P mma tcchh ooiinnt .sseettX getY() getY() attcc LLiin t.s.se X w hh L nee.s ettY wiit setX(int) t h . Y Liinne seet setX(int) h w O e..sse tPP11 wiitthh Obbs setY(int) setY(int) ettPP wwiit OOb seerrv 22 w thh O bsse vaab moveBy(int,int) wiitth Obbs errvva bllee. moveBy(int,int) h OO seer abble .mmoo bbs rvvaab le..mm vveed seer oov d rvvaa bllee.m bble .moov veedd le.m .mo veedd ovve edd display display FigElm FigElm Line Line getP1() getP1() getP2() getP2() setP1(Point) setP1(Point) setP2(Point) setP2(Point) moveBy(int,int) moveBy(int,int) Display update(FigElm) 39 神話四: AOPは名前ベースの技術だ • ポイントカットは メソッドの名前を 使って時点を指定 →名前を正しく 付けないとダメ aspect aspect DisplayUpdating DisplayUpdating {{ pointcut move() : call(int FigElm.moveby(int,int)) || call(* FigElm+.set*(..)); after() after() :: move() move() {{ Display.redraw(); Display.redraw(); }} … … }} • 現状はその通り • 「意味」に基くポイントカットが研究中 – 呼出し文脈: cflow・実行履歴 [Walker]・将来の呼出し 可能性: predicted cflow [Kiczales03]・データフロー [河内・増原03]・条件分岐, ループ 40 神話五: AOPはプログラム変換だ • アスペクトとは、元のプログラムに埋め込まれて 実行されるコードである → つまりAOPはプログラム変換の一種 • コードの埋め込み以上のことができる – 例: 「setXが実行時にmoveByを経ずに 実行されたときには再描画」 • コード変換に依らない処理系もある – steamloom:仮想機械による織り込み[Bockisch他04] – Wool:VMフックによる織り込み[佐藤・千葉・立堀03] 41 神話六: AOPは遅い • メソッド起動のたびにいちいちアドバイスを 探すのでオーバーヘッドが大きい • 実際はそうでもない – コンパイル時にアドバイスを埋め込む [Hilsdale,Hugnin04][増原・Kiczales・Dutchyn03] – 静的解析による/実行時の最適化 [Sereni,de Moor03][Hanenberg, Hirschfeld, Unland04] – オーバーヘッドの計測 [Dufour et al.04] 42 神話七: AOPはログをとるための技術だ • それ以外の応用ってあるの? • ログ以外にも最適化・エラー処理などへの 実際的な応用がある – FreeBSDカーネルをAOPで分離 [Coady他02,03] アスペクト: プリフェッチ・クォータ・ スケジューリング – WebSphereのコードをAOPで分離[Coyler04] アスペクト: ログ・エラー処理・監視 43 see: Y. Coady, G. Kiczales M. Feeley and G. Smolyn, Using AspectC to improve the modularity of path-specific customization in operating system code, in Proceedings of ESEC-8 / FSE-9, pp.88-98, 2001,Vienna, Austria. 44 神話八: AOPなんかいらない、 デザインパターンで充分 • 「画面の再描画」は Subject-Observerパターンで実現可能 • 「実現」はできるが、 – 記述は散在してしまう – パターンを再利用できない (AOPなら可能になる場合も[Hannemann他02]) 45 神話九: AOPはプログラミングを 難しくする • AOPで例えば「画面更新」を分離 →Point, Lineの定義から「画面更新」が無くなる →「画面更新」の影響範囲が特定できない →誤りを見つけるのが難しくなる • 開発ツールによる支援 – AJDT [Kiczales他01,…] • OOPでも同じ議論が可能? →継承によって一部の定義を子クラスに →親クラスの定義の影響範囲が特定できない 46 see: Kersten, Clement, and Harley, AspectJ Tools - New and Noteworthy, AOSD 2004 Demo. 47 神話十: アスペクトは リファクタリングで抽出するもの ※ リファクタリング = プログラムの機能を変えずに 構造を改良する技術 • 横断的関心事は,プログラムを作ってみないと 「横断的」かは分からないだろう • そういう例は多い • 研究レベルでは,アスペクトの利用を前提とした 開発方法論が提案 – “Early Aspects” / “Aspect-Oriented Modeling” / Use-case Pointcuts [Sillito,Dutchyn,Eisenberg,De Volder04] 48 まとめにかえて: AOPの現状と将来 現状 ≅ 1980年代のOOP • 沢山の懐疑論者 • 沢山の言語・技術が提案 (Smalltalk, C++, Flavors, バイトコード, JITコンパイ ル, GC, IDE…) – 「便利そうだけど,大きな プログラムは書けない?」 – 「同じことは××機構で 頑張ればできるよ?」 – 「××には使えないよ?」 将来 • 理論よりも実践が先行 • それから「当たり前」にな るのに10年 当たり前 or 「そういえば昔, AOPなんてのがあったなあ」 49 プログラミング言語の歴史 1970 Simula-goto有害説 67 1980 Smalltalk-80 Smalltalk, C Prolog 1975 Scheme 1985 C++ Ada SML 現存する最古の言語達 階層性・再利用 1965 PL/1 モジュール化 1955 Fortran Lisp 1960 Algol-60, COBOL 最初のオブジェクト指向言語 実用的なオブジェクト指向言語 Perl 1990 Haskell Python SOP Ruby 1995 Java, GoF “Design Patterns” 2000 C# オブジェクト指向があたり前の時代 AspectJ 0.3AspectJ 1.0 最初の汎用AOP言語 実用的AOP言語 50 情報源など • 会議・論文誌 – Aspect-Oriented Software Development (AOSD) OOPSLA, ECOOP, ICSE, FSE, ICFP, … – Trans. AOSD (Springer) • ポータルサイト: aosd.net • メール: – [email protected] – [email protected] – [email protected] • Kiczales’ talk @ ソフトウェア科学会大会 (2004年9月15日, 東工大) 51 おまけ 52 AOP機構の共通性 A XJPjoin point Bprogram B id me ID en an A tif s yi of ng EF F A ID Aprogram m EF ef ea FBB fe n s ct o in f g A&B are parallel weaving happens at X X - computation or program 53 開発支援ツール • 既存のソフトウェアか らアスペクトを抜き出 せる? • Yes, 発見ツールが 研究されています – JQuery [Jansen,De Volder03] – FEAT [Robillard, Murphy02] see: Jan Hannemann and Gregor Kiczales, Design Pattern Implementation in Java and AspectJ, OOPSLA2002,pp.161173 – CME 54
© Copyright 2026 Paperzz