ラベル OOP の投稿を表示しています。 すべての投稿を表示
ラベル OOP の投稿を表示しています。 すべての投稿を表示

2013年1月9日水曜日

"Object Calisthenics" (『オブジェクト指向エクササイズ』)

"Object Calisthenics" (『オブジェクト指向エクササイズ』) を読んだ。最初にルールだけを知ったときは、無茶を感じたけれど、そもそも何のためのルールで、各ルールがなぜ設定されているか、を知ったら納得できた。

"Object Calisthenics"のルールは、遵守すべきコーディング規約でも良い設計・コードのための原則でもない。オブジェクト指向のコンセプトを自分のものにして実践できるようにするためのエクササイズに使うギプスのようなものだ。それも、少なくとも自分にとっては、かなり強力なギプスだ。

ルールは全部で9つある。いずれのルールも、オブジェクト指向のコンセプトは知っている上で、実践に移すのを助けるためのものなので、かなり具体的。長くなるので、個々のルールには立ち入らないけれど、覚え書きを兼ねて()内に私訳を付けておく。
  1. One level of indentation per method(各メソッド、インデントは1段階)
  2. Don't use the ELSE keyword(else句は使わない)
  3. Wrap all primitives and Strings(プリミティブ型と文字列にはラッパークラスを作る)
  4. First class collections(コレクション型にもラッパークラスを作る)
  5. One dot per line(メソッドをチェーンさせない)
  6. Don't abbreviate(略語は使わない)
  7. Keep all entities small(クラスは50行以下、パッケージは10クラス以下を保つ)
  8. No classes with more than two instance variables(インスタンス変数は2つまで)
  9. No getters/setters/properties(ゲッター/セッターあるいはプロパティを使わない)
これらのルールは、いずれも凝集度を上げ結合度を下げ、最小化した責務を局所化し、情報隠蔽・ポリモーフィズムを実践を助けてくれる。1, 2はポリモーフィズムを推し進め、3, 4, 5, 9は問題領域外の情報を隠蔽し、6, 7, 8は責務の割り当てを最小化・局所化する。

その結果、再利用性もメンテナビリティも高いクラスになるので、保守・運用が容易になる。リーダビリティ・テスタビリティも上がるので、開発者にとっても嬉しい。

著者によると、100%これらのルールに従って20時間くらいかけて1000行ほどのコーディングすると、これまでの習慣(手続き指向)から抜け出せるとのこと。自分は割と本を読む方だけれど、どれだけ読んでも手を動かしていない内容は簡単に忘れてしまう。100%は無理そうだけれど、意識してみるつもり。

References


2012年12月28日金曜日

Getter/Setterを減らす

Getter/Setterばかりが目立つようなクラスは、きっとドメインモデル貧血症に陥っている。だから、もっと振る舞いを持たせて、カラフルな名前を付けたり。それに、Setterで内部状態を外から操作できるようでは、カプセル化しきれていない。状態が変化しないImmutableなクラスを目指して、少なくともSetterは減らしていきたい。ただ、フレームワークが要求したり、と完全に消し去ることはできないと思う。というわけで、なるべくうまく付き合いたい。

なんてことを考えるようになった。そのキッカケや経緯、参照したパターンについてまとめておく。

『オブジェクト指向エクササイズ』("Object Calisthenics") の9つのルールに、Getter/Setterを禁じるものがある。
ルール9:Getter、Setter、プロパティを使用しないこと
Rule 9: No getters/setters/properties
初めてこのルールを知ったときは、意図には同意できたけれど、適用しようとは思わなかった。同意できたのは、単にフィールドをプライベートにして値の取得/設定をすることが、オブジェクト指向の特徴の一つであるカプセル化だという説明に違和感を持っていたから。Getter/Setterを通せばAPI変更に強くなるとは言え、値を自由に設定していたら、隠蔽できているとは思えない。適用しようと思わなかった最大の理由は、今思うと、実装をイメージできなかったからだと思う。だから、適用しようにもできなかった、の方が実態に近い。

「求めるな、命じよ("Tell, don't ask")」なんて言い換えられても、やっぱりよく分からなかったけれど、最近、少しずつ分かってきた気がする。痛い目を見たり(Setterでインスタンスへの参照を返していたせいで意図しない変更を加えてしまったり、名前がgetで始まるメソッドをGetterだと思って気楽に使っていたら思わぬ副作用があって面食らったり)、それで『Java言語で学ぶリファクタリング入門』や『リーダブルコード』、"Effective Java 2nd Edition"の内容を応用しようと試行錯誤したりして、ようやく使う/使わないの判断が少しクリアになった。

例えば、Remove Setting Methodのリファクタリングを適用すると、初期化のためだけのSetterを使わずに済ませられる。代わりに、(デザインパターン"factory method"を適用していたら、factory methodを経由して)コンストラクタで初期化することになる。そのまま、"Effective Java 2nd Edition"の"Item 15: Minimize mutability"してMutableにできれば、Setterは要らなくなる。あるいは、『リーダブルコード』のいう〈カラフルな単語〉を使った名前を付けようとすれば、そんな名前が付けられるくらい十分に抽象化しようとすれば、自然とGetter/Setterのような無味乾燥な名前は相応しくなくなる。

ただ、それでGetter/Setterがなくせるかというと、そうはならないと思う。現実問題、フレームワークから利用されるためにEntity Beanにする必要があるクラスも出てくるし、抽象度の低いクラスで融通が利かなくなる。Getter/Setterを使用しないというのは、あくまでエクササイズのルールであって、実戦における規約ではない。だから、リファクタリングSelf Encapsulate Fieldや"Effective Java 2nd Edition"の"Item 14: In public classes, use accessor methods, not public fields"があるんだと思う。

References


2012年12月22日土曜日

ジェネリクスでAPIを柔軟にする

あるクラスの任意のサブクラスのリストを引数に取れるメソッドは、ジェネリクスの型引数にバインドされワイルドカードを指定すれば書ける。

当てずっぽうで書いたらダメだったから何とかその場しのぎのコードを書いていたのだけれど、その後読んだ"Effective Java 2nd Edition"の"Chapter 5 Generics"でいくぶんスッキリしたので整理してみる。

まずリストじゃない場合で前提の確認。Superクラスを継承するSubクラスがあるとき、どちらも引数に取れるメソッドはこう書ける。こう定義しておけば、どちらを渡しても大丈夫。
public void printInstance(Super instance) {
    System.out.println(instance);
}
printInstance(new Super());
printInstance(new Sub());

リストでも同じようにいけるかな? と思って、当てずっぽうで書いたダメな例がこれ。こう書いて、Subのリストを渡そうとすると、コンパイルエラーになる。実は、そもそも、List<Sub>はList<Super>のサブクラスではない。
public void printList(List<Super> list) {
    System.out.println(lst);
}
printList(new ArrayList<Sub>);

この場合はBounded wildcard type (バインドされたワイルドカードタイプ)を使って、こう書く。Superのサブクラスのリストだけじゃなくて、Superのリストも渡せる。
public void printList(List<? extends Super> list) {
    System.out.println(lst);
}

反対にあるクラスのスーパークラスのリストとそのクラス自身のリストを引数に取れるようにも書ける。その場合は、<? extends Super>の代わりに<? super Sub>を使う。

使い分けの指針には"PECS"や"get-put principle"がある。よくかみ砕けていないのだけれど、同じことを言っているように思う。こうしておくと型安全で使いやすいAPIになるとのこと。
PECS stands for producer-extends, consumer-super
"Effective Java 2nd Edition", Item 28: Use bounded wilidcards to increase flexibility
構造から値を取得する (get) だけの場合には extends ワイルドカードを使い、構造の中に値を格納する (put) だけの場合には super ワイルドカードを使い、そして両方を行う場合にはワイルドカードを使ってはなりません。
『Java の理論と実践: Generics のワイルドカードを使いこなす、第 2 回』

References

2012年12月19日水曜日

Rでオブジェクト指向 (S3)

RにはS3, S4, R5の3種類のクラスがある。S3はclass属性で判定しているだけの緩やかな型付け。S4は厳格に定義しておく必要がある厳格な型付け。R5は2.12で追加された新しいクラスで、厳格さはS3とS4の中間。参照渡しという大きな特徴がある。

今回はS3クラスについて。まだS4, R5は理解していないし、"Google's R Style Guide"がS3を推奨しているので、まずはS3から勉強している。
Use S3 objects and methods unless there is a strong reason to use S4 objects or methods.
Google's R Style Guide
S3クラスができるのは、class属性と汎用関数を使ってポリモーフィズムを与えること。つまり、クラスごとに同じ名前で異なる実装の関数を呼び出せる。printやsummaryが分かりやすい例だと思う。

自分で簡単な例を実装してみる。

まず、インスタンスにクラスを教える。S3クラスではclass属性にベクトルを設定するだけ。ベクトルなので、複数のクラスを設定できる。この例では、hogeクラスやfugaクラスを設定している。
a <- 1:10
attr(a, "class") <- "hoge"

b <- 1:10
attr(b, "class") <- c("fuga", "hoge")

c <- 1:10
続いて、汎用関数を宣言・実装する。Javaでいう抽象メソッドを宣言してから、クラスごとに実装していくイメージ。最後の.defaultはデフォルト実装で、対応するクラスがないときに呼ばれる。
calc <- function(x, ...) {
  UseMethod("calc")
}

calc.hoge <- function(x){
  return (x + x)
}

calc.fuga <- function(x) {
  return (x * x)
}

calc.default <- function(x) {
  return (x + 1)
}
実際に呼び出すと、class属性に応じて違う実装が呼び出されていることが確認できる。calc(b)でcalc.fugaが呼ばれていることから、ベクトルを先頭から探しているみたい。
calc(a)
# calc.hoge is called
# 2  4  6  8 10 12 14 16 18 20

calc(b)
# calc.fuga is called
# 1   4   9  16  25  36  49  64  81 100

calc(c)
# calc.default is called
# 2  3  4  5  6  7  8  9 10 11
これでポリモーフィズムが使える。ということは、ポリモーフィズムを活用しているデザインパターンも適用できるはず。

References