【デザインパターン】SingletonとFacadeについて

公開日: 2025/5/16

プログラミング言語について学習しているとオブジェクト指向を学ぶことが出てくると思います。

オブジェクト指向をある程度理解したあとはデザインパターンについて学ぶことが出てきます。


本日はデザインパターンについて概要を説明したあとSingletonとFacadeについて解説していきたいと思います。

1. デザインパターン


そもそもデザインパターンとは、オブジェクト指向において保守が楽になったり、読みやすくなるように色んなプログラムで再利用できる設計したコードのパターンのことです。

デザインパターンが生まれた背景にはいくつか理由があります。

ソフトウェアの規模や複雑さが多くなってくると問題が多くなってきます。

そのため、開発経験の長い技術者が問題解決や、コミュニケーションをスムーズに取れるように作られたパターンになります。


それではまずはSingleton(シングルトン)から解説していきます。

2. Singleton(シングルトン)


Singletonは「クラスのインスタンスがプログラム全体でたった1つしか作られないようにする」デザインパターンです。

クラスはオブジェクトの設計図ですので生成するオブジェクトはいくつあってもいいはずです。

しかし、Singletonはあえてクラスからインスタンスを一つだけしか作らないというデザインパターンです。

ではどういった場合にSingletonが使われるのでしょうか。


Singletonは一つしか無くても良いものや、この世に一つしかないものを作る時のインスタンスを生成する際に使用します。

たとえば「null」という概念は一つのプログラムで一つしか存在しません。

そのため一度インスタンスを作成すればその後は「null」オブジェクトは必要はありません。

その際に注意しておかなければならないのは、どこからでもアクセスできる状態でないといけない点です。


ではSingletonを使用するメリットとはどう言うものがあるのでしょうか。

2-1. メリット

1.唯一のインスタンス

Singletonでデザインされているクラスのインスタンスは一つしかないインスタンスだという証明ができます。

逆に言えば、一つしかインスタンスを存在させたくない場合に使用します。


例えば、ログのオブジェクトであれば一つだけで問題ありません。

もし複数のログのインスタンスがあると実行や、処理がバラバラに出力になってしまう可能性があります。

ログのオブジェクトが1つしかなければ、プログラムのどこでログを出力しても共通の出力先にまとめることができます。

この様にSingletonを使用しているクラスがあればその中のインスタンスは一つしか存在しないということが言えるはずです。

2.メモリ効率の向上

Singletonを使用すると生成されるインスタンスが一つだけであるため、生成する際の計算量やメモリを抑えることができるため、メモリの効率があがります。


「インスタンスが1つしかないことを保証したいとき」にSingletonが便利だと説明しました。

しかし、このような場面はほとんどありません。

先ほどのメリットで述べましたメモリ効率の向上は副産物のようなものです。


そのため、メモリの消費量を抑えるためにSingletonは設計上は望ましいという程度のものになります。

デザインパターンの一つとして覚えておくだけで十分です。

ライブラリにこのオブジェクトはSingletonですという記述があった際はこのオブジェクトはプログラム全体で一つしかないと理解できればよいです。


最後に簡単な例を示します。

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // private constructor to prevent instantiation
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

3. Facade(ファサード)


続いてFacadeについてです。Facadeとはフランス語で建物の正面を意味する言葉になるため、建物の中に色々な処理が入っているクラスを作るデザインパターンになります。

複雑な処理をだらだらと書き連ねてコーディングしているプロジェクトがあるかもしれません。

ただ読みづらく、まとまりがないコードになっています。

それを解決するために生まれたものになります。


例を挙げて説明します。コンビニで商品を買う時にレジに持っていくと思います。

その後商品を読み取り、そして会員カードを読み取り、支払いをしたら、レシートとクーポンを出すという処理があったとします。

その処理を全て一つのクラスに書き連ねるとかなり見にくいはずです。

それを解決するために、まずは「商品読み取り」、「会員カード」、「クーポン」という処理を別々のクラスにそれぞれまとめておきます。


その処理を、購入の処理があった際にそれぞれ呼び出すというような書き方に変更します。

そうすることで、会員カード読み取りにエラーが発生していたら「会員カード」のクラスを見に行けばよいというようにプログラム全体が見やすくなります。

プログラマーであれば普段のコーディングで当たり前のように見ているものになると思いますがこのような名前がついています。

3-1. メリット

1.簡潔に書ける

Facadeパターンを使用すると、複雑な処理を簡潔に書くことができます。

先ほどの例で挙げたように「商品読み取り」、「会員カード」、「クーポン」などの処理を別々に書くことで処理のまとまりがかなり簡潔になります。

2.カプセル化

Facadeパターンはサブシステム(上記では「商品読み取り」などの処理)の内部構造をクライアントから隠して、サブシステムで変更した内容がクライアントに影響がないようにしてくれます。

もし、商品を読み取る際にデータベースから呼び出すキーがID1からID2に変更されても、「商品読み取り」というサブシステムの中で簡潔するため実際の目に見える範囲で影響がでないということです。


ただなんでもかんでもFacadeにしてしまえばいいと言うものでは無くて、適切なサブシステムを設計の段階で導き出してからFacadeを使用しなければなりません。

そのため、設計の段階が一番大事になってきます。

では最後に簡単な例を下記に示します。

3-2. サブシステム

// 商品情報を取得するサブシステム
class ProductInfo {
    public String getProductDetails(String productId) {
        // 仮の実装: 商品情報をデータベースから取得する
        return "Product Name: ABC, Price: $10.00";
    }
}

// 会員カードを確認するサブシステム
class MembershipCard {
    public boolean isValidMembership(String memberId) {
        // 仮の実装: 会員カードが有効かどうかをチェックする
        return memberId.startsWith("VIP");
    }
}

// クーポンを発行するサブシステム
class CouponSystem {
    public void issueCoupon(String memberId) {
        // 仮の実装: 会員にクーポンを発行する
        System.out.println("Coupon issued for member: " + memberId);
    }
}

3-3. ファサードクラス

// ファサードクラス
public class ConvenienceStoreFacade {
    private ProductInfo productInfo;
    private MembershipCard membershipCard;
    private CouponSystem couponSystem;

    public ConvenienceStoreFacade() {
        this.productInfo = new ProductInfo();
        this.membershipCard = new MembershipCard();
        this.couponSystem = new CouponSystem();
    }

    public void purchaseProduct(String productId, String memberId) {
        // 商品情報を取得
        String productDetails = productInfo.getProductDetails(productId);
        System.out.println("Product Details: " + productDetails);

        // 会員カードを確認
        boolean isValidMember = membershipCard.isValidMembership(memberId);
        if (isValidMember) {
            System.out.println("Membership card verified: " + memberId);
        } else {
            System.out.println("Invalid membership card: " + memberId);
        }

        // クーポン発行
        if (isValidMember) {
            couponSystem.issueCoupon(memberId);
        }

        // 商品を購入する他の処理を追加...
        System.out.println("Product purchased successfully!");
    }
}

3-4. クライアント

// クライアントコード
public class Client {
    public static void main(String[] args) {
        // ファサードを使ってコンビニで商品を購入する
        ConvenienceStoreFacade facade = new ConvenienceStoreFacade();

        // 商品を購入する
        System.out.println("--- Purchasing Product ---");
        String productId = "123";
        String memberId = "VIP123";
        facade.purchaseProduct(productId, memberId);

        System.out.println("--- Another Purchase ---");
        String productId2 = "456";
        String memberId2 = "REG789";
        facade.purchaseProduct(productId2, memberId2);
    }
}