メソッドチェーンは、単一の文で、同一オブジェクトの複数のメソッド呼出しを可能にする便利な仕組みである。メソッドチェーンの実装はthis参照を返す一連のメソッドから構成される。チェーンの中で先に呼び出されたメソッドの返り値に対して次のメソッドを呼び出すことで、複数のメソッド呼出しが可能になる。
メソッドチェーンの中で使用される個々のメソッドがアトミックであっても、それらのメソッドから構成されるチェーンはアトミックではない。したがって、「VNA03-J. アトミックなメソッドをまとめた呼び出しがアトミックであると仮定しない」で示すように、メソッドチェーンに用いられるメソッドの呼出し元が適切なロックを提供し、メソッドチェーン全体がアトミックであることを保証しなくてはならない。
違反コード
メソッドチェーンは、オブジェクトの構築とオプションフィールドの設定を行うのに便利なデザインパターンである。メソッドチェーンを実装するクラスは、返り値としてthis参照を返す複数のセッターメソッドを提供する。しかし、複数スレッドが並行アクセスした場合、あるスレッドは整合性を欠いた値を保持する共有フィールドを参照するかもしれない。以下の違反コードに、スレッドセーフではないJavaBeansパターンを示す。
final class USCurrency {
// 両替の単位 (オプションフィールド)
private int quarters = 0;
private int dimes = 0;
private int nickels = 0;
private int pennies = 0;
public USCurrency() {}
// セッターメソッド
public USCurrency setQuarters(int quantity) {
quarters = quantity;
return this;
}
public USCurrency setDimes(int quantity) {
dimes = quantity;
return this;
}
public USCurrency setNickels(int quantity) {
nickels = quantity;
return this;
}
public USCurrency setPennies(int quantity) {
pennies = quantity;
return this;
}
}
// クライアント側のコード
class exampleClientCode {
private final USCurrency currency = new USCurrency();
// ...
public exampleClientCode() {
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
currency.setQuarters(1).setDimes(1);
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override public void run() {
currency.setQuarters(2).setDimes(2);
}
});
t2.start();
//...
}
}
JavaBeansパターンでは、引数なしのコンストラクタと一連のセッターメソッドを使用してオブジェクトを構築する。このパターンはスレッドセーフではないため、複数のメソッドによって同時にオブジェクトの変更が行われた場合、オブジェクトは整合性を欠いた状態になる可能性がある。この違反コードでは、クライアントはUSCurrencyオブジェクトを構築し、メソッドチェーンを使用する2つのスレッド起動して USCurrency のオプションフィールドに値を代入している。しかし、USCurrencyインスタンスは、quarters が 2 でdimes が 1 であったり、quarters が 1 でdimes が 2 であるような不整合な状態におかれる可能性がある。
適合コード
以下の適合コードは、Bloch [Bloch 2008] が提案する Builderパターン[Gamma 1995]の変形を使用し、オブジェクトの構築におけるスレッドの安全性とアトミック性を確保している。
final class USCurrency {
private final int quarters;
private final int dimes;
private final int nickels;
private final int pennies;
public USCurrency(Builder builder) {
this.quarters = builder.quarters;
this.dimes = builder.dimes;
this.nickels = builder.nickels;
this.pennies = builder.pennies;
}
// staticなクラスメンバ
public static class Builder {
private int quarters = 0;
private int dimes = 0;
private int nickels = 0;
private int pennies = 0;
public static Builder newInstance() {
return new Builder();
}
private Builder() {}
// セッターメソッド
public Builder setQuarters(int quantity) {
this.quarters = quantity;
return this;
}
public Builder setDimes(int quantity) {
this.dimes = quantity;
return this;
}
public Builder setNickels(int quantity) {
this.nickels = quantity;
return this;
}
public Builder setPennies(int quantity) {
this.pennies = quantity;
return this;
}
public USCurrency build() {
return new USCurrency(this);
}
}
}
// クライアント側のコード:
class exampleClientCode {
private volatile USCurrency currency;
// ...
public exampleClientCode() {
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
currency = USCurrency.Builder.newInstance().
setQuarters(1).setDimes(1).build();
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override public void run() {
currency = USCurrency.Builder.newInstance().
setQuarters(2).setDimes(2).build();
}
});
t2.start();
//...
}
}
必要な引数でファクトリメソッドBuilder.newInstance()が呼び出され、Builderインスタンスが生成されている。オプションパラメータには、ビルダーのセッターメソッドを使用して値が代入される。オブジェクトの構築は、build()メソッドを呼び出すことで完了する。このデザインパターンを実装することで、USCurrencyクラスは不変となり、結果的にスレッドセーフとなる。
currencyフィールドには新規の不変オブジェクトへの参照が代入されるため、final宣言できないことに注意。一方、currencyフィールドは、「VNA01-J. 不変オブジェクトへの共有参照の可視性を確保する」に従ってvolatile宣言されている。
Builderクラスは「OBJ08-J. 入れ子クラスから外側のクラスのprivateメンバを公開しない」にも適合している。これは、Builderクラスがクラスのスコープ内で定義されている変数のコピーを保持しているからである。入れ子クラスのプライベートメンバが優先されるため、カプセル化は維持される。
リスク評価
マルチスレッド環境でメソッドチェーンを使用する場合にチェーンの外側でロックを行わないと、プログラムの予期せぬ動作につながる。
| ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
|---|---|---|---|---|---|
| VNA04-J | 低 | 中 | 中 | P4 | L3 |
参考文献
| [API 2006] | |
| [Bloch 2008] | Item 2, Consider a builder when faced with many constructor parameters |
翻訳元
これは以下のページを翻訳したものです。
VNA04-J. Ensure that calls to chained methods are atomic (revision 77)
