Windows7が発売されましたね。
開発部長にWindows7搭載のノートPCをおねだり中の大崎です。
今回はどどーんと、項目20,21,22の3項目進んじゃいます。
項目20タグ付クラスよりクラス階層を選ぶ
- タグ付クラスは新しい特性を追加した場合に、それに対応したコードを追加しなければならない
タグ付クラスとそれをクラス階層に置き換えた例を以下に示します。
// タグ付けクラス - クラス階層よりかなり劣る!
class Figure {
enum Shape { RECTANGLE, CIRCLE };
// タグフィールド - 図の形
final Shape shape;
// shape が RECTANGLE である場合にだけ、これらのフィールドは使用される
double length;
double width;
// shape が CIRCLE である場合にだけ、このフィールドは使用される
double radius;
// 円のためのコンストラクタ
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// 四角のためのコンストラクタ
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch(shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Maht.PI * (radius * radius);
default:
throw new AssertionError();
}
}
}
// タグ付けクラスに対するクラス階層よる置き換え
abstract class Figure {
abstract double area();
}
class Circle extends Figure {
double radius;
Circle(double radius) { this.radius = radius; }
double area() { return Maht.PI * (radius * radius); }
}
class Rectangle extends Figure {
double length;
double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() { return length * width; }
}
ここでTriangleを追加してみます。
// タグ付けクラス class Figure { enum Shape { RECTANGLE, CIRCLE, TRIANGLE }; final Shape shape; double length; double width; double radius; double height; double bottomLine; Figure(double radius) { shape = Shape.CIRCLE; this.radius = radius; } Figure(double length, double width) { shape = Shape.RECTANGLE; this.length = length; this.width = width; } // 三角のためのコンストラクタ // 四角のためのコンストラクタとシグネチャが被ってしまいコンパイルエラーになりますが、ここでは説明のためにとりあえず無視します。 Figure(double height, double bottomLine) { shape = Shape.TRIANGLE; this.height = height; this.bottomLine = bottomLine; } double area() { switch(shape) { case RECTANGLE: return length * width; case CIRCLE: return Maht.PI * (radius * radius); case TRIANGLE: return (height * bottomLine) / 2; default: throw new AssertionError(); } } }
赤字で書かれたところが追加箇所です。
クラス階層の場合は以下のTriangleクラスを追加するだけになります。
class Triangle extends Figure { double height; double bottomLine; Triangle(double height, double bottomLine) { this.height = height; this.bottomLine = bottomLine; } double area() { return (height * bottomLine) / 2; } }
また、この場合コンストラクタのシグネチャが被ることも回避することができます。
ここで「タグ付クラスが有用な場合があるか?」という質問がありました。
「クラス階層に付加してタグをつける(たとえばtypeとか)ことはある。」 という意見があり、タグだけでオブジェクトの種類を表す場合の良い例は出ませんでした。
項目21戦略を表現するために関数オブジェクトを使用する
- 関数ポインタを使用する為には、Strategy(戦略)パターンを使用する。
- Strategy パターンを使用するには Strategyインターフェイスと、それを実装した ConcreteStrategy (具象戦略)が必要
- ConcreteStrategy を一回しか使わない場合は無名クラスでも良いが、複数回使う場合は private static final のメンバーとして保持する
を提供するためにホストクラスについて説明しています。 ホストクラスの例を以下に示します。
// 具象戦略を提供する
class Host {
private static class StrLenCmp implements Comparator, Serializable {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
// 返されたコンパレータはシリアライズ可能
public static final Comparator STRING_LENGTH_COMPARATOR = new StrLenCmp();
}
ここで、ホストクラスが何のために必要かという議論になりました。
「StrLenCmpクラスを隠したいのではないか」
「上の理由だけだとインナークラスにする必要はなくて無名クラスでやればいいのだけど、 Serializableも実装したい場合はインナークラスにする必要がある。」
などの意見が出ました。
この戦略インターフェースはデザインパターンの戦略パターンとして知られています。
「このようなデザインパターンをいつのタイミングで採用するか?」という質問がありました。
「問題が出てきてから」
「最初から決めちゃってる」
「作りながら」
「フレームワーク提供者とアプリケーション作成者では違うのではないか」
など、人によって、状況によって違っています。
そのほかにも、
「パターンによる。生成とかメディエータみたいなのは初めから決めるが、ストラテジとかはあとで決まる。」
という意見もありました。
結論としては、「パターンありきでは考えない」ということになりました。
項目22非staticのメンバークラスよりstaticのメンバークラスを選ぶ
ネストされたクラスは4種類存在する
- static のメンバークラス
- 非 static のメンバークラス
- 無名クラス
- ローカルクラス
- static のメンバークラス
- 一般的な利用方法は有用な public のヘルパークラス
- 非 static のメンバークラス
- ネストしているクラス(エンクロージングクラス)のインスタンス無しにはインスタンスが生成できない
- this 構文を使って、エンクロージングインスタンスへの参照が可能
- エンクロージングインスタンスへのアクセスが必要のないクラスを宣言する場合は常に static にすべき(public の場合の話?
- 無名クラス
- その場で関数オブジェクトを生成する
- プロセスオブジェクトの生成?
- static ファクトリーメソッドでの使用?
- ローカルクラス
- メソッド内で宣言するクラス
ここでは
「まず、staticにすべきエンクロージングインスタンスにアクセスする必要がある場合にのみstaticを外すべき。」
という意見がでました。