OpenCourseWare(OCW)を勉強するWiki
6.170 Laboratory in Software Engineering Lecture 3
最終更新:
匿名ユーザー
-
view
MIT OpenCourseWare > 6.170 Laboratory in Software Engineering, Fall 2001 > 6.170 Laboratory in Software Engineering Lecture 3
MIT OpenCourseWare 6.170 Laboratory in Software Engineering, Fall 2001, Lecture 3: Decoupling2 のまとめ
ラジオの方では vol. にあたりました。Lecture Noteを読むときの助けにしてください。
6.170 Laboratory in Software Engineering, Fall 2001のLecture NoteのPDFはこちら
(※2006年4月16日現在、上記講義は6.170 Laboratory in Software Engineering, Fall 2005にアップデートされたようですが、PDFはまだ拾うことができます。)
6.170 Laboratory in Software Engineering, Fall 2001のLecture NoteのPDFはこちら
(※2006年4月16日現在、上記講義は6.170 Laboratory in Software Engineering, Fall 2005にアップデートされたようですが、PDFはまだ拾うことができます。)
3.5まではしゃべってたひとJFの超訳。3.6からはしゃべってたひとJMの超訳を元にしています。(ありがとう)
3.0 Introduction
良いプログラミング言語とは、
- 部分ごとの依存(dependences)を表現できる
- 依存をコントロールできる(想定外のdependenceがおきない)
この講義ではJavaをつかってdependenceを表現するよ。
簡単なコーディングの問題に対するいろんな解決法や、インターフェースの詳しい役割を学ぶよ。
(註: ちなみにこの章では MDD というと module dependency diagram のこと。)
簡単なコーディングの問題に対するいろんな解決法や、インターフェースの詳しい役割を学ぶよ。
(註: ちなみにこの章では MDD というと module dependency diagram のこと。)
3.1 Review: Module Dependency Diagrams
まずは、この講義でずっと使ってるMDDさんが表現することの復習とか。わかんないひとはLecture2を見てね。ここでは絵の見方を端的に。
- implementation パート(Javaでいうclasses) は、上の方に1本横線が入ってる四角。
- specification パートは、上の方と下の方に1本ずつ横線が入ってる四角。
- パートを1つにグルーピングしたもの(Javaでいうpackageとか)はベン図みたいに囲んでる。
- AからSに出てる、先っちょが閉じてなくて三角形になってない(山型)矢印は、AはSに意味的にも依存してる。
- AがSに弱い依存(Sというオブジェクトの中身に触らないけど参照する)のときは矢印が点々になる。
- AからSに先っちょが三角の矢印が出てたら、AはSにmeetするもの。Specificationを実装するクラスはなん個かあってもいいよ。
- specificationはとても大事なので、常にあると考えて省略するよ。AからBに矢印があるときは、Aは直でBに依存してるのではなくBのspecificationに依存すると考えてね。
3.2 Java Namespace
3.2から3.5までは、Java使いな人はさらっと流しちゃってね。
大規模システムでは階層的な構造にすると良いよ。JavaのNamespace 、ネーミングシステムはこの階層構造をサポートしてる。階層構造のいいところとしては、
- 階層の上から下にみていく、つまり大きい塊から細かい方へ見ていくと、しばしばわかりやすいから。
- 違うネームスペースで同じ名前をつかっちゃっても名前の影響はネームスペース内でとどまるから、開発する人は名前について気にしなくて良いよ。
JavaのNamespaceはこんなもの。
- Javaでは名前のついたもの(変数とか関数とかクラスとか)にはscopeがある。クラスのローカル変数はクラス以外には通用しない。
- 1つのクラス・インターフェースが1つのファイルになっている。
- Packageはディレクトリ構造として保存される。たとえばdnj.browserパッケージの中のdnj.browser.Protocolというクラスは、dnj/browser/ というディレクトリの下のProtocol.javaとして保存される。
MDDの図(p.31)ではあるパッケージのあるクラス(1つでも複数でも)が、ほかのパッケージのあるクラス(1つでも複数でも)に依存してる、というときには中のクラス同士をつなぐんじゃなくて、パッケージ同士を矢印でつなぐことで表現したりするよ。
3.3 Access Control
public宣言されたクラスは他のどのクラスからも使える。宣言してなかったらpackage内からしか見えないよ。
publicならほかのどのクラスからも見れて、そうでなければ同じパッケージからしか見えない。なので、このクラスへパッケージ外のほかのクラスから依存させたくなければpublicつけないでおく。
クラスのメンバ(フィールドやメソッド)は以下のmodifierがつけれる。
クラスのメンバ(フィールドやメソッド)は以下のmodifierがつけれる。
- public アクセスはどこからでも
- private そのクラス内からのみ
- protected パッケージ内もしくはサブクラスから
"a dependence of A on B"がAがBのspecificationに依存するってことだったのを思い出してみる。Bのメンバーのmodifierが、どのメンバーがBのspecificationに依存するかをコントロールする。Bのフィールドのアクセスコントロールは独立性を表現するのを助けるけど、常に保証してるわけじゃない。
3.4 Safe Languages
プログラムのキーとなる特性は、あるパートは、別のパートに依存するときは名前がわかってるものだけになってるべき。でもこれ'safe languages'だけが実現してる? unsafeな言語では、名前が共有されずに1つのパートのテキストが他の振る舞いに影響しちゃうのでいろいろバグとか出る。
(※A key property of a program is that one part should only depend on another if it names it. This seems obvious, but in fact it’s a property that only holds for programs written in so-called ‘safe languages’ ってところがよくわかんなかった)
(※A key property of a program is that one part should only depend on another if it names it. This seems obvious, but in fact it’s a property that only holds for programs written in so-called ‘safe languages’ ってところがよくわかんなかった)
unsafeな言語として、Cでarrayを触るときの、a配列のメモリ確保領域を超えてfaultで落ちるときの例。
safeな言語はこのあたり防ぐ技術が何個かある。
- arrayの大きさを動的にチェック Javaだと超えるとexceptionが吐かれる
- 自動的なメモリマネジメント 変な再利用とかされない
この2つは'strong typing'の考え方に基づいてる。プログラムのテキストの中で型tの値へのアクセスだと宣言されたアクセスは、ランタイムでの型tへのアクセスになること。配列とデザインされたコードがstringとかintegerとかと間違えられることがない。
safeな言語は1960年代からいろいろ出てきてて、Algol-60, Pascal, Modula, LISP, CLU, Ada, ML, ときて今はJava。Javaはappletが最初うまくいかなかったこともあってlibraryとかがんばった結果今safeな言語として使えるよ。
safeな言語のいくつかはコンパイル時にtype correctnessをチェックする(static typing)一方、SchemeとかLISPとかは実行時にチェックする。
3.5 Interfaces
static typingな言語では、型の選択で依存性をコントロールする。簡単に言えば、あるクラスが型Tのことしか言ってなかったら、他の型T'に依存してないってこと。もしくは、依存してるクラスのことだけ言えば良いってこと。
でも、subtypingを考えるとちょっと興味深いことが起こる。クラスAがクラスBしか言及していないとする。これは、Bが作ったオブジェクトのメソッドしか呼べないということを意味してない。Javaでは、BのサブクラスCから作ったオブジェクトは型Bを持ってるとされるので、AがクラスCを直接作ってなくても他のクラスから渡せることができる。この型Cは型Bのサブタイプ(subtype)という。オブジェクトBが期待されてるところでオブジェクトCが使えるから。これを‘substitutability’という。
サブクラス(sublcass)ってのは以下の2つの別々のことが1つになったもの。
- サブタイプ(subtyping) たとえば型Bと互換性のある型として型Cとみなせる。
- 継承(inheritance) 型CはBのコードを再利用できる
でも後のほうのレクチャーで、substitutabilityがうまくいかない例を挙げるよ。
ここではサブタイプのことだけ考える。Javaでは、サブタイプについて、インターフェース(interfaces)のほうがサブクラスよりべんり。インターフェースってのは、この講義での言い方で説明すると純粋なspecificationパート。実行可能なコードもないし、decouplingをするためだけにある。
クラスAがクラスBに依存してる場合を考えながらインターフェースIの紹介をする。AがBじゃなくてIのみ言及してて、BがIのspecificationに合うことを求められる。もちろんJavaのコンパイラは振る舞いのspecificationは扱わなくて、BがIに対してあってるか型チェックしか行わないけど、実行時にはこのAとI、BとIの関係はチェックされる。
たとえばjava.util.LinkedListを使ってるとき。もしもこれが「Linked」リストでなくて良いのなら、java.util.LinkedListが実装してるjava.util.Listインターフェースを使えば良い。VectorだってArraylistだってこのインターフェースを実装してるから同じように使える。
いくつかのクラスが同じインターフェースを実装してることもある(multiple specification inheritance)けど、サブクラスは1つの親クラスしか持てない。
interfaceは2つのいいことがある。
- 純粋なspecificationをコードの中で表現できる。ので、クラスAが使うクラスBの部分はspecificationSだけなことが保証できて、Bの詳細を知る必要がない。
- 1つのspecificationに合ったいろんなクラスが提供できて、これはコンパイル時とかランタイムで選択できる。
3.6 Example: Instrumenting a Program
今、ログのような機能をモジュールに追加することを考える。
もっとも単純なものは、モジュールのいたるところに
System.out.println ("Starting download");
を書くこと。
でも、これじゃぁログの形式が変わったら、えらいこっちゃ。例えば日付をログに追加しようとしたら地獄をみる。(System.out.printlnの行全部書き換えなきゃダメだからね)
そこでこんなコードをいたるところにはりつけるのは止めて
public class StandardOutReporter {
public static void report (String msg) {
System.out.println (msg);
}
}
というふうにログの部分を抜き出そう。(カプセル化?上の文献にはこれをカプセル化と呼んでた)
ちなみにこれを「引数による抽象化」と呼ぶ。
ちなみにこれを「引数による抽象化」と呼ぶ。
上のやりかたは悪くは無いが、まだよくない。なんでかってーと、あらゆるモジュールがStandardOutReporter に依存してまうから。もし、これがGUIにログを出力ってなったらどうするよ?つうわけで、インターフェースによる分割を行う。
public interface Reporter {
void report (String msg);
}
と、インターフェースをきって↓みたいに利用すればsysoもGUIにもいける。これぞOCP。
void なんかのめそっど(Reporter r, …) {
r.report (“Starting downloading” );
…
}
ちなみにアブストクラスやなくてインターフェース使ってるのは、アブスト使うと依存関係が強いし、一個しかない継承つかうのはもったいないから。
で、次にインターフェースきってもあれやから、別方向でstatic変数を使う方法もある。引数でReporter使ってるのってださいし。
static使うのはこんな感じ。
(註; 原文のコードは記述ミスあり。classのReporter の変数の名前をrとして、setReporter関数でthis.r=rとしていましたが、Javaではstaticな関数からはthisは呼べません。)
(註; 原文のコードは記述ミスあり。classのReporter の変数の名前をrとして、setReporter関数でthis.r=rとしていましたが、Javaではstaticな関数からはthisは呼べません。)
public class StaticReporter {
static Reporter rep;
static void setReporter (Reporter r) {
rep = r;
}
static void report (String msg) {
rep.report (msg);
}
}
こうすると、
void なんかのめそっど( …) {
StaticReporter.report (“Starting downloading” );
…
}
とできるし、動的に振る舞いを変更できる。
これは、インターフェースによる分割とstatic変数による分割の複合技。
これは、インターフェースによる分割とstatic変数による分割の複合技。
で、上の方法って単純な場合にはいいけど、こまかい制御したくなると脆い。頻繁にstatic変数のインスタンスを変更したり、メソッド追加したりせないかんくなる。 まぁstatic変数使うときは気をつけろってこっちゃ。
この節のまとめとしては、今回の話はどう分割していくがポイント。
メソッド化→引数による抽象化→インターフェースによる分割→インターフェース+static変数による分割
と進んだ。 それぞれにはトレードオフがあるので注意。
today's visitor: -
total visitor: -
total visitor: -


