はじめに
GoFデザインパターンの一つで,構造に関するパターンである
Decoratorパターンについて見ていきます.
Decoratorパターンについて
DecoratorパターンはVisitorパターン同様に,
既存の階層構造に新しい操作を追加することができます.
また,新しい機能を追加しても既存のクラスに影響を与えることがありません.
そのため,オブジェクトに対してコードを変更させることなく追加の振る舞いを
実行時に与えたい場合,Decoratorパターンを適用します.
Decoratorパターンの構造
Decortorパターンとは|GoFデザインパターンの解説によると,構成要素は以下のようになります.
- コンポーネント (Component):基本機能を定義するインターフェースまたは抽象クラス
- 具体的コンポーネント (Concrete Component):インターフェースや抽象クラスを実装し、基本機能を提供するクラス
- デコレーター (Decorator):コンポーネントを保持し、機能を拡張する抽象クラスまたはインターフェース
- 具体的デコレーター (Concrete Decorator):デコレータークラスを拡張し、追加機能を実装するクラス
クラス図は以下のようになります.
クラス図
Decoratorパターンの実装
ネット販売システムを例にして考えてみます.
以下は商品やサービスの購入を題材にしたクラス図とソースコードとなります.
クラス図
ソースコード
public interface Purchase {
void process();
}
public class ProductPurchase implements Purchase {
private String productName;
public ProductPurchase(String productName) {
this.productName = productName;
}
@Override
public void process() {
System.out.println(productName + " を購入しました。");
}
}
public class ServicePurchase implements Purchase {
private String serviceName;
public ServicePurchase(String serviceName) {
this.serviceName = serviceName;
}
@Override
public void process() {
System.out.println(serviceName + " サービスを購入しました。");
}
}
public abstract class PurchaseDecorator implements Purchase {
protected Purchase purchase;
public PurchaseDecorator(Purchase purchase) {
this.purchase = purchase;
}
@Override
public void process() {
purchase.process();
}
}
public class InsuranceDecorator extends PurchaseDecorator {
public InsuranceDecorator(Purchase purchase) {
super(purchase);
}
@Override
public void process() {
super.process();
addInsurance();
}
private void addInsurance() {
System.out.println("購入に保険を追加しました。");
}
}
public class DiscountDecorator extends PurchaseDecorator {
public DiscountDecorator(Purchase purchase) {
super(purchase);
}
@Override
public void process() {
super.process();
applyDiscount();
}
private void applyDiscount() {
System.out.println("割引を適用しました。");
}
}
public class DecoratorPatternExample {
public static void main(String[] args) {
// 商品購入の基本処理
Purchase productPurchase = new ProductPurchase("ノートパソコン");
// サービス購入の基本処理
Purchase servicePurchase = new ServicePurchase("オンラインコース");
// 割引付きの商品購入
Purchase discountedProduct = new DiscountDecorator(productPurchase);
// 保険付きのサービス購入
Purchase insuredDiscountedService = new DiscountDecorator(servicePurchase);
// 処理を実行
System.out.println("割引付き商品購入:");
productPurchase.process();
System.out.println("\n保険付きサービス購入:");
insuredDiscountedService.process();
}
}
商品購入:
ノートパソコン を購入しました。
割引を適用しました。
保険付きサービス購入:
オンラインコース サービスを購入しました。
購入に保険を追加しました。
各クラス概要は以下の通りです.
1.Purchase(Component)
購入プロセスの基本となるインタフェースです.
processメソッドで購入処理を行います.
2.ProductPurchase(ConcreteComponent)
商品の購入処理を実装するクラスです.
3.ServicePurchase(ConcreteComponent)
サービスの購入処理を実装するクラスです.
4.PurchaseDecorator(Decorator)
追加機能を拡張できるようにするためのDecoratorクラスです.
5.InsuranceDecorator(ConcreteDecorator)
商品やサービスの購入に保険を追加するためのDecoratorクラスです.
6.DiscountDecorator(ConcreteDecorator)
購入時に割引を適用するためのDecoratorクラスです.
Decoratorパターンのメリット
Decoratorパターンを適用することによるメリットを見ていきます.
機能の動的な追加
オブジェクトに対して必要なときに機能を追加することができます.
例えば,ある商品(ノートPC)を買うという処理に対して「割引適用」の機能を追加する際,
Decoratorを使うことで機能を追加することができます.
その他の機能も追加したければ,さらに別のDecoratorを用いることで
いくつもの機能に対して柔軟に対応することができます.
(必要がないなら取り外すこともできます)
また,基本となっているクラス(ConcreteComponent)に対して変更を加えることなく
機能を追加することができます.
Decoratorパターンの問題点
Decoratorパターンを適用することによる問題点を見ていきます.
構造の複雑化
Decoratorパターンを適用すると,多数のDecoratorクラスが作成され,
構造が複雑になり,それらの管理が難しくなる場合があります.
さらに,Decoratorは他のオブジェクトに依存するため,
依存関係が複雑化する可能性もあります.
そのため,他のDecoratorと重複する内容を含むDecoratorを作成する場合は,
新たに基本クラスとなるDecoratorを設計し,それを基に派生させることで,
重複を避けるなどの工夫をする必要があります.
参考