PDF file

= 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