C#のファクトリーパターン完全入門!初心者向けにオブジェクト生成の基本と実践例を徹底解説
生徒
「C#のプログラムで、部品をたくさん作るときに、毎回新しく作るコードを書くのが大変になってきました。何か良い方法はありますか?」
先生
「C#では、ファクトリーパターンという設計パターンを使うことで、部品の作成を一箇所にまとめて管理することができます。」
生徒
「ファクトリーパターンというのは、具体的にどのような仕組みで、どうやって使うのですか?」
先生
「それでは、初心者の方にも分かりやすいように、基本的な使い方と具体的な実践例を一緒に見ていきましょう!」
1. ファクトリーパターンとは?
C#の世界には、プログラムを綺麗に、そして後から直しやすいように組み立てるための決まり文句のようなものが存在します。それを設計パターンまたはデザインパターンと呼びます。今回学習するファクトリーパターンは、その設計パターンの中でも特に重要で、よく使われるもののひとつです。
ファクトリーという言葉は、日本語に翻訳すると工場という意味になります。つまり、ファクトリーパターンとは、プログラム内で必要となる様々な部品を、専門の工場のような場所でまとめて作り出す仕組みのことです。
プログラミング未経験の方に向けて言葉の解説をすると、プログラムにおける部品のことをオブジェクトやインスタンスと呼びます。パソコンの画面に表示されるボタンや、ゲームに登場するキャラクターなど、プログラムが扱うデータや機能のまとまりすべてがオブジェクトです。ファクトリーパターンは、このオブジェクトの生成を専門に行う仕組みを整えるための設計技法となります。
2. なぜファクトリーパターンが必要なのか?
通常、C#で新しいオブジェクトを作るときは、ニューという命令を使って直接作成します。しかし、プログラムの規模が大きくなってくると、あちこちの場所でこの作成命令を何度も書くことになります。これが後々の大きな問題を引き起こす原因になります。
例えば、作ろうとしている部品の名前が変更になったり、新しく作るための準備手順が変わったりした場合、プログラム全体のあらゆる場所を探し出して、すべてのコードを書き直さなければならなくなります。これは非常に面倒ですし、修正の引き忘れによる間違いの原因にもなります。
ここでファクトリーパターンが必要になります。部品を作る処理を一箇所の工場に任せておけば、もし作り方が変わったとしても、その工場の中身だけを修正すれば良くなります。他の場所にあるコードは一切書き直す必要がなくなるため、プログラムの変更や管理が圧倒的に楽になります。
3. ファクトリーパターンの仕組みを現実世界で例えると?
この仕組みを理解するために、身近なレストランの注文を例にして考えてみましょう。あなたがレストランに入って料理を注文するとき、厨房の中にいる料理人に直接指示を出して、フライパンを火にかけたり、調味料を量らせたりはしないはずです。
あなたはただ、メニュー表を見てハンバーグやオムライスといった料理の名前を選び、店員さんに注文を伝えるだけです。あとは、厨房という名の工場が、指定された通りの料理を自動的に作ってテーブルまで運んできてくれます。
このとき、注文する側であるあなたは、料理がどのように調理されているかという細かい手順を知る必要はありません。ファクトリーパターンもこれと全く同じです。プログラムを利用する側は、工場に対してこれが欲しいと種類を指定するだけで、中身がどのように組み立てられているかを意識することなく、完成したオブジェクトを受け取って使うことができるのです。
4. ファクトリーパターンの基本コードを作ってみよう
それでは、C#を使った最も基本的なファクトリーパターンのプログラムを作成してみましょう。ここでは、様々な種類の挨拶をするロボットを作る工場をテーマにしてみます。
まずは、すべてのロボットが共通して持っている挨拶をするという機能を定義するインターフェースを用意します。インターフェースとは、クラスが持つべき機能の共通のルールを取り決めたものです。そして、そのルールに従って実際に動く個別のクラスを作ります。最後に、それらを生成する工場クラスを作成します。
using System;
// 1. ロボット共通のルール(インターフェース)を決めます
public interface IRobot
{
void Greet();
}
// 2. 日本語で挨拶する具体的なロボットを作ります
public class JapaneseRobot : IRobot
{
public void Greet()
{
Console.WriteLine("こんにちは!");
}
}
// 3. 英語で挨拶する具体的なロボットを作ります
public class EnglishRobot : IRobot
{
public void Greet()
{
Console.WriteLine("Hello!");
}
}
// 4. ロボットを作る専門の工場(ファクトリー)クラスです
public class RobotFactory
{
public static IRobot CreateRobot(string lang)
{
if (lang == "JP")
{
return new JapaneseRobot();
}
else if (lang == "EN")
{
return new EnglishRobot();
}
return null;
}
}
// 5. 実際に動かすためのメインの処理です
class Program
{
static void Main()
{
// 工場に注文してロボットを作ってもらいます
IRobot robot1 = RobotFactory.CreateRobot("JP");
robot1.Greet();
IRobot robot2 = RobotFactory.CreateRobot("EN");
robot2.Greet();
}
}
上記のプログラムを実行すると、画面には次のように出力されます。工場に対して文字列を渡すだけで、適切なロボットが作られていることが分かります。
こんにちは!
Hello!
5. ファクトリーパターンを使った実践例その1:スマートフォンの製造
次の実践例として、スマートフォンの製造工場をプログラムで表現してみましょう。現代の生活に欠かせないスマートフォンには、いくつかの異なる種類が存在します。今回はアイフォンとアンドロイドの二つの機種を工場で作る流れを再現します。
ここでは、スマートフォンを表す共通の型を用意し、それぞれの機種ごとに異なる起動画面の表示処理を実装します。ファクトリークラスを使うことで、利用する側は機種の名前を指定するだけで、正しいスマートフォンオブジェクトを手にいれることができます。
using System;
// スマートフォンの基本ルールです
public interface ISmartphone
{
void BootUp();
}
// アイフォンの具体的なクラスです
public class iPhone : ISmartphone
{
public void BootUp()
{
Console.WriteLine("リンゴのマークが表示されて起動します。");
}
}
// アンドロイドの具体的なクラスです
public class Android : ISmartphone
{
public void BootUp()
{
Console.WriteLine("アンドロイドのロゴが表示されて起動します。");
}
}
// スマートフォンを製造する工場クラスです
public class SmartphoneFactory
{
public static ISmartphone Manufacture(string modelType)
{
if (modelType.ToLower() == "iphone")
{
return new iPhone();
}
else if (modelType.ToLower() == "android")
{
return new Android();
}
throw new ArgumentException("指定された機種は製造できません。");
}
}
class Program
{
static void Main()
{
// 工場を使ってアイフォンを製造します
ISmartphone myPhone = SmartphoneFactory.Manufacture("iphone");
myPhone.BootUp();
// 工場を使ってアンドロイドを製造します
ISmartphone workPhone = SmartphoneFactory.Manufacture("android");
workPhone.BootUp();
}
}
このプログラムを実行すると、それぞれのスマートフォンの特徴に合わせた起動時のメッセージが表示されます。作成の手続きがすべて工場の中に隠されているのがポイントです。
リンゴのマークが表示されて起動します。
アンドロイドのロゴが表示されて起動します。
6. ファクトリーパターンを使った実践例その2:ゲームのモンスター出現
もう一つの実践的な例として、ロールプレイングゲームなどで敵キャラクターが画面に出現する仕組みを考えてみましょう。ゲームの中では、プレイヤーの進行度や戦う場所に応じて、スライムやドラゴンといった様々なモンスターが次々と現れます。
これらを毎回手動で生成していると、新しいモンスターを追加するたびにゲームの根幹のプログラムを修正しなければならなくなり、バグの原因になります。出現処理をファクトリーパターンでまとめて管理すれば、ゲームの進行処理自体をシンプルに保つことができます。
using System;
// モンスターの共通ルールです
public interface IMonster
{
void Attack();
}
// スライムクラスです
public class Slime : IMonster
{
public void Attack()
{
Console.WriteLine("スライムが体当たりしてきた!");
}
}
// ドラゴンクラスです
public class Dragon : IMonster
{
public void Attack()
{
Console.WriteLine("ドラゴンが激しい炎を吐いた!");
}
}
// モンスターを出現させる工場クラスです
public class MonsterFactory
{
public static IMonster SpawnMonster(string areaRank)
{
if (areaRank == "Beginner")
{
return new Slime();
}
else if (areaRank == "Advanced")
{
return new Dragon();
}
return null;
}
}
class Program
{
static void Main()
{
// 初心者エリアのモンスターを出現させます
IMonster enemy1 = MonsterFactory.SpawnMonster("Beginner");
enemy1.Attack();
// 上級者エリアのモンスターを出現させます
IMonster enemy2 = MonsterFactory.SpawnMonster("Advanced");
enemy2.Attack();
}
}
このゲームの例を実行すると、指定したエリアの難易度に応じて適切なモンスターが自動で選ばれて攻撃を行います。エリアごとの条件分岐が工場に集約されているため、見通しの良いコードになります。
スライムが体当たりしてきた!
ドラゴンが激しい炎を吐いた!
7. ファクトリーパターンを使った実践例その3:お支払方法の選択
最後に、インターネット通販サイトなどでよく見かける、お支払方法の切り替えシステムをファクトリーパターンで表現してみます。買い物をする際、クレジットカードで払うか、あるいは電子マネーで払うかを選択することができます。
これらは支払いを行うという目的は同じですが、実際に行われる処理の手順や通信の宛先は全く異なります。これらを一つの場所に詰め込んで書くと非常に複雑なコードになってしまいますが、ファクトリーパターンを使えば綺麗に分離することができます。
using System;
// 決済方法の共通ルールです
public interface IPayment
{
void ProcessPayment(int amount);
}
// クレジットカード決済の処理クラスです
public class CreditCardPayment : IPayment
{
public void ProcessPayment(int amount)
{
Console.WriteLine($"クレジットカードで {amount} 円の決済を承認しました。");
}
}
// 電子マネー決済の処理クラスです
public class ElectronicMoneyPayment : IPayment
{
public void ProcessPayment(int amount)
{
Console.WriteLine($"電子マネー残高から {amount} 円を差し引きました。");
}
}
// 決済処理のオブジェクトを作り出す工場クラスです
public class PaymentFactory
{
public static IPayment SelectPaymentMethod(string method)
{
switch (method)
{
case "Card":
return new CreditCardPayment();
case "E-Money":
return new ElectronicMoneyPayment();
default:
throw new NotSupportedException("対応していない決済方法です。");
}
}
}
class Program
{
static void Main()
{
// カード決済を選択した場合の処理です
IPayment userChoice1 = PaymentFactory.SelectPaymentMethod("Card");
userChoice1.ProcessPayment(5000);
// 電子マネー決済を選択した場合の処理です
IPayment userChoice2 = PaymentFactory.SelectPaymentMethod("E-Money");
userChoice2.ProcessPayment(1200);
}
}
プログラムの実行結果は次のようになります。注文金額や選んだ手段に応じて、適切な決済処理が呼び出されていることが確認できます。
クレジットカードで 5000 円の決済を承認しました。
電子マネー残高から 1200 円を差し引きました。
8. ファクトリーパターンを使うメリットと注意点
ファクトリーパターンを導入することで得られる最大の利点は、プログラムの保守性が高まることです。保守性とは、後からプログラムを改造したり、不具合を直したりするときの作業のしやすさのことを指します。オブジェクトを作る場所が工場クラスの中に一元化されているため、何か変更があったときも一箇所の修正で完了します。
また、オブジェクトを利用する側のコードが非常にシンプルになるというメリットもあります。複雑な組み立て工程や初期設定の処理を工場の中に隠蔽することができるため、全体のコードの読みやすさが大幅に向上します。
一方で、注意しなければならない点もあります。それは、プログラム全体のクラスの数が増えてしまうということです。インターフェースや工場クラスを新しく追加する必要があるため、非常に規模が小さく、今後拡張する予定もない単純なプログラムに適用すると、逆にコードが複雑になってしまい、分かりにくくなることがあります。そのため、将来的に機能が増える可能性がある場合や、複数の種類を柔軟に切り替えたい場合に狙いを絞って導入するのが賢い使い方です。開発するシステムの規模を見極めながら上手に活用していきましょう。