JavaにおけるVisitorデザインパターン
ビジターデザインパターンは、行動型デザインパターンの一つです。
訪問者デザインパターン
訪問者パターンは、類似の種類のオブジェクトに対して操作を実行する必要があるときに使用されます。訪問者パターンのおかげで、オブジェクトから操作ロジックを別のクラスに移動することができます。例えば、ショッピングカートを考えてみましょう。さまざまな種類のアイテム(要素)を追加することができます。チェックアウトボタンをクリックすると、支払うべき総額が計算されます。この計算ロジックをアイテムクラスに持たせるか、別のクラスに移動するかは、訪問者パターンを使って自由に選択できます。訪問者パターンの例を使って、この実装を行いましょう。
ビジターデザインパターンのJavaの例
訪問者パターンを実装するために、まずはショッピングカートで使用する異なる種類のアイテム(要素)を作成します。ItemElement.java
package com.scdev.design.visitor;
public interface ItemElement {
public int accept(ShoppingCartVisitor visitor);
}
acceptメソッドがVisitor引数を受け取ることに注意してください。アイテムに特有の他のメソッドもあり得ますが、単純化のために、詳細には触れずにVisitorパターンに焦点を当てています。さまざまな種類のアイテムについて、具体的なクラスを作成しましょう。例えば、Book.javaです。
package com.scdev.design.visitor;
public class Book implements ItemElement {
private int price;
private String isbnNumber;
public Book(int cost, String isbn){
this.price=cost;
this.isbnNumber=isbn;
}
public int getPrice() {
return price;
}
public String getIsbnNumber() {
return isbnNumber;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
フルーツ.java
package com.scdev.design.visitor;
public class Fruit implements ItemElement {
private int pricePerKg;
private int weight;
private String name;
public Fruit(int priceKg, int wt, String nm){
this.pricePerKg=priceKg;
this.weight=wt;
this.name = nm;
}
public int getPricePerKg() {
return pricePerKg;
}
public int getWeight() {
return weight;
}
public String getName(){
return this.name;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
具体的なクラスでaccept()メソッドの実装に注目してください。それはVisitorのvisit()メソッドを呼び出し、自身を引数として渡します。Visitorインターフェースには、具体的な訪問者クラスによって実装されるさまざまなタイプのアイテムのためのvisit()メソッドがあります。ShoppingCartVisitor.java
package com.scdev.design.visitor;
public interface ShoppingCartVisitor {
int visit(Book book);
int visit(Fruit fruit);
}
今、私たちは訪問者インターフェイスを実装し、すべてのアイテムには独自のコスト計算ロジックがあります。ShoppingCartVisitorImpl.java
package com.scdev.design.visitor;
public class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
@Override
public int visit(Book book) {
int cost=0;
//apply 5$ discount if book price is greater than 50
if(book.getPrice() > 50){
cost = book.getPrice()-5;
}else cost = book.getPrice();
System.out.println("Book ISBN::"+book.getIsbnNumber() + " cost ="+cost);
return cost;
}
@Override
public int visit(Fruit fruit) {
int cost = fruit.getPricePerKg()*fruit.getWeight();
System.out.println(fruit.getName() + " cost = "+cost);
return cost;
}
}
クライアントアプリケーションでの訪問者パターンの例を使用する方法を見てみましょう。 ShoppingCartClient.java
package com.scdev.design.visitor;
public class ShoppingCartClient {
public static void main(String[] args) {
ItemElement[] items = new ItemElement[]{new Book(20, "1234"),new Book(100, "5678"),
new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")};
int total = calculatePrice(items);
System.out.println("Total Cost = "+total);
}
private static int calculatePrice(ItemElement[] items) {
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int sum=0;
for(ItemElement item : items){
sum = sum + item.accept(visitor);
}
return sum;
}
}
上記のビジターパターンのクライアントプログラムを実行すると、以下の出力が得られます。
Book ISBN::1234 cost =20
Book ISBN::5678 cost =95
Banana cost = 20
Apple cost = 25
Total Cost = 160
全ての項目のaccept()メソッドの実装が同じであることに注意してくださいが、実際には異なることがあります。例えば、アイテムが無料の場合にはvisit()メソッドを呼び出さないようにするためのロジックがあるかもしれません。
訪問者デザインパターンのクラス図について
私たちの訪問者デザインパターンの実装のためのクラス図は次の通りです。
訪問者パターンの利点
このパターンの利点は、操作の論理が変わった場合、アイテムクラス全体で変更するのではなく、訪問者の実装だけで変更できる点です。また、システムに新しいアイテムを追加するのも簡単で、訪問者のインターフェースと実装を変更するだけで、既存のアイテムクラスに影響を与えることはありません。
訪問者パターンの制約
訪問者パターンの欠点は、設計時にvisit()メソッドの戻り値の型を事前に知っておかなければならないことです。さもないと、インターフェースとその実装全体を変更しなければなりません。もうひとつの欠点は、訪問者インターフェースの実装が多すぎる場合、拡張が困難になることです。これで訪問者デザインパターンに関する説明は終わりですが、もしも何か見落としがあれば教えてください。また、気に入った場合は他の人とも共有してください。