GoFデザインパターン - 構造パターン(Structural Patterns)
構造パターンは、クラスやオブジェクトをより大きな構造に組み立てる方法を提供します。
1. Adapter(アダプター)
概要
互換性のないインターフェースを持つクラス同士を連携させるための変換器を提供します。
使用場面
- 既存のクラスを新しいインターフェースに適合させる
- サードパーティライブラリの統合
- レガシーコードの統合
- 異なるデータフォーマットの統合
サンプルコード
// Target(クライアントが期待するインターフェース)
public interface IPaymentProcessor
{
void ProcessPayment(decimal amount, string currency);
bool ValidatePayment(string accountInfo);
}
// Adaptee(既存のクラス、互換性がない)
public class LegacyPaymentSystem
{
public void MakePayment(double amountInYen)
{
Console.WriteLine($"Legacy system processing payment: ¥{amountInYen}");
}
public int CheckAccount(string accountNumber)
{
// 1: Valid, 0: Invalid
Console.WriteLine($"Legacy system validating account: {accountNumber}");
return accountNumber.Length >= 10 ? 1 : 0;
}
}
// Adapter(LegacyPaymentSystemをIPaymentProcessorに適合させる)
public class PaymentAdapter : IPaymentProcessor
{
private readonly LegacyPaymentSystem _legacySystem;
private readonly Dictionary<string, decimal> _exchangeRates;
public PaymentAdapter(LegacyPaymentSystem legacySystem)
{
_legacySystem = legacySystem;
_exchangeRates = new Dictionary<string, decimal>
{
{ "JPY", 1.0m },
{ "USD", 150.0m },
{ "EUR", 160.0m }
};
}
public void ProcessPayment(decimal amount, string currency)
{
// 通貨を円に変換
decimal amountInYen = amount * _exchangeRates[currency];
// レガシーシステムの形式に変換して呼び出し
_legacySystem.MakePayment((double)amountInYen);
}
public bool ValidatePayment(string accountInfo)
{
// レガシーシステムの戻り値を変換
int result = _legacySystem.CheckAccount(accountInfo);
return result == 1;
}
}
// 新しいサードパーティの決済システム
public class StripePaymentService
{
public void Charge(int amountInCents, string currencyCode)
{
Console.WriteLine($"Stripe charging: {amountInCents} cents {currencyCode}");
}
public bool Verify(string token)
{
Console.WriteLine($"Stripe verifying token: {token}");
return token.StartsWith("tok_");
}
}
// Stripeのアダプター
public class StripeAdapter : IPaymentProcessor
{
private readonly StripePaymentService _stripeService;
public StripeAdapter(StripePaymentService stripeService)
{
_stripeService = stripeService;
}
public void ProcessPayment(decimal amount, string currency)
{
// 金額をセント単位に変換
int amountInCents = (int)(amount * 100);
_stripeService.Charge(amountInCents, currency);
}
public bool ValidatePayment(string accountInfo)
{
return _stripeService.Verify(accountInfo);
}
}
// 使用例
class Program
{
static void Main()
{
// レガシーシステムを使用
var legacySystem = new LegacyPaymentSystem();
IPaymentProcessor processor1 = new PaymentAdapter(legacySystem);
processor1.ProcessPayment(100m, "USD");
bool isValid1 = processor1.ValidatePayment("1234567890");
Console.WriteLine($"Valid: {isValid1}");
Console.WriteLine("\n---\n");
// Stripeを使用(同じインターフェース)
var stripeService = new StripePaymentService();
IPaymentProcessor processor2 = new StripeAdapter(stripeService);
processor2.ProcessPayment(100m, "USD");
bool isValid2 = processor2.ValidatePayment("tok_valid123");
Console.WriteLine($"Valid: {isValid2}");
}
}
2. Bridge(ブリッジ)
概要
抽象部分と実装部分を分離し、それぞれ独立に変更できるようにします。
使用場面
- プラットフォーム独立性が必要な場合
- 抽象と実装の両方を拡張したい
- データベース抽象化層
- デバイスドライバの抽象化
サンプルコード
// Implementation(実装インターフェース)
public interface IMessageSender
{
void SendMessage(string message, string recipient);
}
// Concrete Implementations
public class EmailSender : IMessageSender
{
public void SendMessage(string message, string recipient)
{
Console.WriteLine($"Sending EMAIL to {recipient}:");
Console.WriteLine($" Content: {message}");
}
}
public class SmsSender : IMessageSender
{
public void SendMessage(string message, string recipient)
{
Console.WriteLine($"Sending SMS to {recipient}:");
Console.WriteLine($" Content: {message}");
}
}
public class PushNotificationSender : IMessageSender
{
public void SendMessage(string message, string recipient)
{
Console.WriteLine($"Sending PUSH NOTIFICATION to {recipient}:");
Console.WriteLine($" Content: {message}");
}
}
// Abstraction(抽象クラス)
public abstract class Notification
{
protected IMessageSender _sender;
protected Notification(IMessageSender sender)
{
_sender = sender;
}
public abstract void Notify(string message, string recipient);
}
// Refined Abstractions
public class UrgentNotification : Notification
{
public UrgentNotification(IMessageSender sender) : base(sender) { }
public override void Notify(string message, string recipient)
{
string urgentMessage = $"[URGENT] {message}";
_sender.SendMessage(urgentMessage, recipient);
}
}
public class ReminderNotification : Notification
{
public ReminderNotification(IMessageSender sender) : base(sender) { }
public override void Notify(string message, string recipient)
{
string reminderMessage = $"[Reminder] {message}";
_sender.SendMessage(reminderMessage, recipient);
}
}
public class PromotionalNotification : Notification
{
private readonly string _discountCode;
public PromotionalNotification(IMessageSender sender, string discountCode)
: base(sender)
{
_discountCode = discountCode;
}
public override void Notify(string message, string recipient)
{
string promoMessage = $"{message}\nUse code: {_discountCode} for 20% off!";
_sender.SendMessage(promoMessage, recipient);
}
}
// 使用例
class Program
{
static void Main()
{
// EmailでUrgent通知
IMessageSender emailSender = new EmailSender();
Notification urgentEmail = new UrgentNotification(emailSender);
urgentEmail.Notify("Server is down!", "admin@example.com");
Console.WriteLine("\n---\n");
// SMSでReminder通知
IMessageSender smsSender = new SmsSender();
Notification reminderSms = new ReminderNotification(smsSender);
reminderSms.Notify("Meeting at 3 PM", "+81-90-1234-5678");
Console.WriteLine("\n---\n");
// Push NotificationでPromotion通知
IMessageSender pushSender = new PushNotificationSender();
Notification promoNotification = new PromotionalNotification(pushSender, "SAVE20");
promoNotification.Notify("Special sale today!", "user123");
Console.WriteLine("\n---\n");
// 同じPromotionをEmailで送信(送信方法を簡単に切り替え可能)
Notification promoEmail = new PromotionalNotification(emailSender, "EMAIL20");
promoEmail.Notify("Special sale today!", "customer@example.com");
}
}
3. Composite(コンポジット)
概要
オブジェクトをツリー構造に組み立て、個々のオブジェクトとオブジェクトの集合を同じように扱えるようにします。
使用場面
- ファイルシステム(ファイルとディレクトリ)
- GUIコンポーネント(ウィンドウ、パネル、ボタン)
- 組織階層
- メニュー構造
サンプルコード
// Component(基底クラス)
public abstract class FileSystemComponent
{
protected string _name;
public FileSystemComponent(string name)
{
_name = name;
}
public abstract void Display(int depth = 0);
public abstract long GetSize();
// Compositeのみで意味を持つメソッド
public virtual void Add(FileSystemComponent component)
{
throw new NotSupportedException();
}
public virtual void Remove(FileSystemComponent component)
{
throw new NotSupportedException();
}
public virtual FileSystemComponent GetChild(int index)
{
throw new NotSupportedException();
}
}
// Leaf(葉)
public class File : FileSystemComponent
{
private long _size;
public File(string name, long size) : base(name)
{
_size = size;
}
public override void Display(int depth = 0)
{
Console.WriteLine($"{new string('-', depth)} 📄 {_name} ({_size} bytes)");
}
public override long GetSize()
{
return _size;
}
}
// Composite(複合)
public class Directory : FileSystemComponent
{
private List<FileSystemComponent> _children = new List<FileSystemComponent>();
public Directory(string name) : base(name) { }
public override void Add(FileSystemComponent component)
{
_children.Add(component);
}
public override void Remove(FileSystemComponent component)
{
_children.Remove(component);
}
public override FileSystemComponent GetChild(int index)
{
return _children[index];
}
public override void Display(int depth = 0)
{
Console.WriteLine($"{new string('-', depth)} 📁 {_name}/ ({GetSize()} bytes total)");
foreach (var child in _children)
{
child.Display(depth + 2);
}
}
public override long GetSize()
{
long totalSize = 0;
foreach (var child in _children)
{
totalSize += child.GetSize();
}
return totalSize;
}
}
// より実践的な例:会社の組織構造
public abstract class OrganizationComponent
{
protected string _name;
protected string _position;
public OrganizationComponent(string name, string position)
{
_name = name;
_position = position;
}
public abstract void DisplayHierarchy(int depth = 0);
public abstract decimal GetTotalSalary();
public abstract int GetEmployeeCount();
public virtual void Add(OrganizationComponent component)
{
throw new NotSupportedException();
}
public virtual void Remove(OrganizationComponent component)
{
throw new NotSupportedException();
}
}
public class Employee : OrganizationComponent
{
private decimal _salary;
public Employee(string name, string position, decimal salary)
: base(name, position)
{
_salary = salary;
}
public override void DisplayHierarchy(int depth = 0)
{
Console.WriteLine($"{new string(' ', depth * 2)}👤 {_name} - {_position} (${_salary:N0})");
}
public override decimal GetTotalSalary()
{
return _salary;
}
public override int GetEmployeeCount()
{
return 1;
}
}
public class Manager : OrganizationComponent
{
private decimal _salary;
private List<OrganizationComponent> _subordinates = new List<OrganizationComponent>();
public Manager(string name, string position, decimal salary)
: base(name, position)
{
_salary = salary;
}
public override void Add(OrganizationComponent component)
{
_subordinates.Add(component);
}
public override void Remove(OrganizationComponent component)
{
_subordinates.Remove(component);
}
public override void DisplayHierarchy(int depth = 0)
{
Console.WriteLine($"{new string(' ', depth * 2)}👔 {_name} - {_position} (${_salary:N0})");
foreach (var subordinate in _subordinates)
{
subordinate.DisplayHierarchy(depth + 1);
}
}
public override decimal GetTotalSalary()
{
decimal total = _salary;
foreach (var subordinate in _subordinates)
{
total += subordinate.GetTotalSalary();
}
return total;
}
public override int GetEmployeeCount()
{
int count = 1; // 自分自身
foreach (var subordinate in _subordinates)
{
count += subordinate.GetEmployeeCount();
}
return count;
}
}
// 使用例
class Program
{
static void Main()
{
// ファイルシステムの例
Console.WriteLine("=== File System ===\n");
var root = new Directory("root");
var documents = new Directory("documents");
var photos = new Directory("photos");
documents.Add(new File("resume.pdf", 45000));
documents.Add(new File("cover-letter.docx", 32000));
photos.Add(new File("vacation.jpg", 2048000));
photos.Add(new File("family.jpg", 1536000));
root.Add(documents);
root.Add(photos);
root.Add(new File("readme.txt", 1200));
root.Display();
Console.WriteLine($"\nTotal size: {root.GetSize()} bytes");
Console.WriteLine("\n=== Organization ===\n");
// 組織構造の例
var ceo = new Manager("Alice Johnson", "CEO", 200000);
var cto = new Manager("Bob Smith", "CTO", 150000);
var cfo = new Manager("Carol White", "CFO", 150000);
var seniorDev = new Manager("David Brown", "Senior Developer", 120000);
var juniorDev1 = new Employee("Eve Davis", "Junior Developer", 80000);
var juniorDev2 = new Employee("Frank Miller", "Junior Developer", 75000);
seniorDev.Add(juniorDev1);
seniorDev.Add(juniorDev2);
cto.Add(seniorDev);
cto.Add(new Employee("Grace Wilson", "DevOps Engineer", 110000));
cfo.Add(new Employee("Henry Moore", "Accountant", 90000));
cfo.Add(new Employee("Ivy Taylor", "Financial Analyst", 85000));
ceo.Add(cto);
ceo.Add(cfo);
ceo.Add(new Employee("Jack Anderson", "Executive Assistant", 70000));
ceo.DisplayHierarchy();
Console.WriteLine($"\nTotal employees: {ceo.GetEmployeeCount()}");
Console.WriteLine($"Total salary cost: ${ceo.GetTotalSalary():N0}");
}
}
4. Decorator(デコレーター)
概要
オブジェクトに動的に新しい責任を追加します。サブクラス化よりも柔軟な機能拡張の代替手段を提供します。
使用場面
- ストリーム処理(圧縮、暗号化)
- GUIコンポーネントの装飾(スクロールバー、ボーダー)
- ミドルウェアパイプライン
- ロギング、キャッシング
サンプルコード
// Component(基底インターフェース)
public interface ICoffee
{
string GetDescription();
decimal GetCost();
}
// Concrete Component(基本実装)
public class SimpleCoffee : ICoffee
{
public string GetDescription()
{
return "Simple Coffee";
}
public decimal GetCost()
{
return 5.0m;
}
}
public class Espresso : ICoffee
{
public string GetDescription()
{
return "Espresso";
}
public decimal GetCost()
{
return 6.0m;
}
}
// Decorator(基底デコレーター)
public abstract class CoffeeDecorator : ICoffee
{
protected ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee)
{
_coffee = coffee;
}
public virtual string GetDescription()
{
return _coffee.GetDescription();
}
public virtual decimal GetCost()
{
return _coffee.GetCost();
}
}
// Concrete Decorators
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return $"{_coffee.GetDescription()}, Milk";
}
public override decimal GetCost()
{
return _coffee.GetCost() + 1.5m;
}
}
public class SugarDecorator : CoffeeDecorator
{
public SugarDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return $"{_coffee.GetDescription()}, Sugar";
}
public override decimal GetCost()
{
return _coffee.GetCost() + 0.5m;
}
}
public class WhippedCreamDecorator : CoffeeDecorator
{
public WhippedCreamDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return $"{_coffee.GetDescription()}, Whipped Cream";
}
public override decimal GetCost()
{
return _coffee.GetCost() + 2.0m;
}
}
public class CaramelDecorator : CoffeeDecorator
{
public CaramelDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return $"{_coffee.GetDescription()}, Caramel";
}
public override decimal GetCost()
{
return _coffee.GetCost() + 1.8m;
}
}
// より実践的な例:データストリーム処理
public interface IDataStream
{
string Read();
void Write(string data);
}
public class FileDataStream : IDataStream
{
private string _filename;
public FileDataStream(string filename)
{
_filename = filename;
}
public string Read()
{
return $"[Reading from {_filename}]";
}
public void Write(string data)
{
Console.WriteLine($"[Writing to {_filename}]: {data}");
}
}
public abstract class DataStreamDecorator : IDataStream
{
protected IDataStream _stream;
public DataStreamDecorator(IDataStream stream)
{
_stream = stream;
}
public virtual string Read()
{
return _stream.Read();
}
public virtual void Write(string data)
{
_stream.Write(data);
}
}
public class CompressionDecorator : DataStreamDecorator
{
public CompressionDecorator(IDataStream stream) : base(stream) { }
public override string Read()
{
string data = _stream.Read();
return Decompress(data);
}
public override void Write(string data)
{
string compressed = Compress(data);
_stream.Write(compressed);
}
private string Compress(string data)
{
return $"[COMPRESSED({data})]";
}
private string Decompress(string data)
{
return $"[DECOMPRESSED({data})]";
}
}
public class EncryptionDecorator : DataStreamDecorator
{
public EncryptionDecorator(IDataStream stream) : base(stream) { }
public override string Read()
{
string data = _stream.Read();
return Decrypt(data);
}
public override void Write(string data)
{
string encrypted = Encrypt(data);
_stream.Write(encrypted);
}
private string Encrypt(string data)
{
return $"[ENCRYPTED({data})]";
}
private string Decrypt(string data)
{
return $"[DECRYPTED({data})]";
}
}
// 使用例
class Program
{
static void Main()
{
// コーヒーの例
Console.WriteLine("=== Coffee Shop ===\n");
ICoffee coffee1 = new SimpleCoffee();
Console.WriteLine($"{coffee1.GetDescription()} - ${coffee1.GetCost()}");
ICoffee coffee2 = new MilkDecorator(new SimpleCoffee());
Console.WriteLine($"{coffee2.GetDescription()} - ${coffee2.GetCost()}");
ICoffee coffee3 = new WhippedCreamDecorator(
new CaramelDecorator(
new MilkDecorator(
new Espresso()
)
)
);
Console.WriteLine($"{coffee3.GetDescription()} - ${coffee3.GetCost()}");
Console.WriteLine("\n=== Data Stream Processing ===\n");
// データストリームの例
IDataStream stream1 = new FileDataStream("data.txt");
stream1.Write("Hello World");
Console.WriteLine(stream1.Read());
Console.WriteLine();
// 圧縮のみ
IDataStream stream2 = new CompressionDecorator(
new FileDataStream("compressed.txt")
);
stream2.Write("Hello World");
Console.WriteLine(stream2.Read());
Console.WriteLine();
// 圧縮と暗号化
IDataStream stream3 = new EncryptionDecorator(
new CompressionDecorator(
new FileDataStream("secure.txt")
)
);
stream3.Write("Sensitive Data");
Console.WriteLine(stream3.Read());
}
}
5. Facade(ファサード)
概要
サブシステムの複雑な処理を隠蔽し、シンプルな統一されたインターフェースを提供します。
使用場面
- 複雑なライブラリやAPIの簡素化
- レガシーコードのラッピング
- マイクロサービスの統合
- サブシステムの依存関係の管理
サンプルコード
// サブシステムクラス群(複雑な内部処理)
public class VideoFile
{
private string _filename;
public VideoFile(string filename)
{
_filename = filename;
Console.WriteLine($"Loading video file: {_filename}");
}
public string GetCodecType()
{
return _filename.EndsWith(".mp4") ? "MPEG-4" : "Unknown";
}
}
public class CodecFactory
{
public static ICodec GetCodec(string codecType)
{
if (codecType == "MPEG-4")
{
Console.WriteLine("Creating MPEG-4 codec");
return new MPEG4Codec();
}
else
{
Console.WriteLine("Creating OGG codec");
return new OGGCodec();
}
}
}
public interface ICodec
{
string Decode(VideoFile file);
}
public class MPEG4Codec : ICodec
{
public string Decode(VideoFile file)
{
Console.WriteLine("Decoding MPEG-4 video...");
return "MPEG-4 decoded data";
}
}
public class OGGCodec : ICodec
{
public string Decode(VideoFile file)
{
Console.WriteLine("Decoding OGG video...");
return "OGG decoded data";
}
}
public class AudioMixer
{
public void FixAudio(string decodedData)
{
Console.WriteLine("Fixing audio track...");
}
}
public class VideoRenderer
{
public void Render(string decodedData)
{
Console.WriteLine("Rendering video to screen...");
}
}
// Facade(複雑なサブシステムを隠蔽)
public class VideoConverter
{
public void ConvertVideo(string filename, string format)
{
Console.WriteLine($"Starting video conversion: {filename} -> {format}");
// 複雑な処理を内部で実行
var file = new VideoFile(filename);
var codecType = file.GetCodecType();
var codec = CodecFactory.GetCodec(codecType);
var decodedData = codec.Decode(file);
var audioMixer = new AudioMixer();
audioMixer.FixAudio(decodedData);
var renderer = new VideoRenderer();
renderer.Render(decodedData);
Console.WriteLine($"Conversion complete: {format}");
}
}
// より実践的な例:Eコマースシステム
public class InventorySystem
{
public bool CheckStock(string productId, int quantity)
{
Console.WriteLine($"Checking inventory for {productId} x {quantity}");
return true; // 簡略化
}
public void ReserveStock(string productId, int quantity)
{
Console.WriteLine($"Reserving {quantity} units of {productId}");
}
public void ReleaseStock(string productId, int quantity)
{
Console.WriteLine($"Releasing {quantity} units of {productId}");
}
}
public class PaymentGateway
{
public bool ProcessPayment(string cardNumber, decimal amount)
{
Console.WriteLine($"Processing payment: ${amount} with card {cardNumber}");
return true; // 簡略化
}
public void RefundPayment(string transactionId, decimal amount)
{
Console.WriteLine($"Refunding ${amount} for transaction {transactionId}");
}
}
public class ShippingService
{
public string CreateShipment(string address, string productId)
{
Console.WriteLine($"Creating shipment for {productId} to {address}");
return "SHIP-" + Guid.NewGuid().ToString().Substring(0, 8);
}
public void TrackShipment(string shipmentId)
{
Console.WriteLine($"Tracking shipment: {shipmentId}");
}
}
public class NotificationService
{
public void SendOrderConfirmation(string email, string orderId)
{
Console.WriteLine($"Sending order confirmation to {email} for order {orderId}");
}
public void SendShippingNotification(string email, string trackingNumber)
{
Console.WriteLine($"Sending shipping notification to {email}, tracking: {trackingNumber}");
}
}
// Facade
public class OrderFacade
{
private readonly InventorySystem _inventory;
private readonly PaymentGateway _payment;
private readonly ShippingService _shipping;
private readonly NotificationService _notification;
public OrderFacade()
{
_inventory = new InventorySystem();
_payment = new PaymentGateway();
_shipping = new ShippingService();
_notification = new NotificationService();
}
public bool PlaceOrder(string productId, int quantity, string cardNumber,
decimal amount, string shippingAddress, string customerEmail)
{
Console.WriteLine("=== Starting Order Process ===\n");
// 在庫確認
if (!_inventory.CheckStock(productId, quantity))
{
Console.WriteLine("Order failed: Out of stock");
return false;
}
// 在庫予約
_inventory.ReserveStock(productId, quantity);
// 決済処理
if (!_payment.ProcessPayment(cardNumber, amount))
{
Console.WriteLine("Order failed: Payment declined");
_inventory.ReleaseStock(productId, quantity);
return false;
}
// 配送手配
string shipmentId = _shipping.CreateShipment(shippingAddress, productId);
// 通知送信
string orderId = "ORD-" + Guid.NewGuid().ToString().Substring(0, 8);
_notification.SendOrderConfirmation(customerEmail, orderId);
_notification.SendShippingNotification(customerEmail, shipmentId);
Console.WriteLine("\n=== Order Completed Successfully ===");
return true;
}
}
// 使用例
class Program
{
static void Main()
{
// ビデオ変換の例
Console.WriteLine("=== Video Conversion ===\n");
var converter = new VideoConverter();
converter.ConvertVideo("vacation.mp4", "avi");
Console.WriteLine("\n\n=== E-Commerce Order ===\n");
// Eコマース注文の例
var orderFacade = new OrderFacade();
bool success = orderFacade.PlaceOrder(
productId: "PROD-123",
quantity: 2,
cardNumber: "1234-5678-9012-3456",
amount: 99.99m,
shippingAddress: "123 Main St, Tokyo",
customerEmail: "customer@example.com"
);
Console.WriteLine($"\nOrder result: {(success ? "Success" : "Failed")}");
}
}
6. Flyweight(フライウェイト)
概要
多数の細かいオブジェクトを効率的に共有することで、メモリ使用量を削減します。
使用場面
- テキストエディタの文字表現
- ゲームの粒子システム
- アイコンやイメージのキャッシュ
- データベース接続プール
サンプルコード
// Flyweight(共有される軽量オブジェクト)
public class CharacterStyle
{
// Intrinsic state(共有される状態)
public string FontFamily { get; }
public int FontSize { get; }
public string Color { get; }
public bool IsBold { get; }
public bool IsItalic { get; }
public CharacterStyle(string fontFamily, int fontSize, string color,
bool isBold = false, bool isItalic = false)
{
FontFamily = fontFamily;
FontSize = fontSize;
Color = color;
IsBold = isBold;
IsItalic = isItalic;
Console.WriteLine($"Creating new CharacterStyle: {fontFamily}, {fontSize}pt, {color}");
}
public void Display(char character, int position)
{
// Extrinsic state(共有されない状態)として位置と文字を受け取る
string style = $"{(IsBold ? "Bold " : "")}{(IsItalic ? "Italic " : "")}";
Console.WriteLine($"[{position}] '{character}' - {style}{FontFamily} {FontSize}pt {Color}");
}
}
// Flyweight Factory
public class CharacterStyleFactory
{
private Dictionary<string, CharacterStyle> _styles =
new Dictionary<string, CharacterStyle>();
public CharacterStyle GetStyle(string fontFamily, int fontSize, string color,
bool isBold = false, bool isItalic = false)
{
string key = $"{fontFamily}-{fontSize}-{color}-{isBold}-{isItalic}";
if (!_styles.ContainsKey(key))
{
_styles[key] = new CharacterStyle(fontFamily, fontSize, color, isBold, isItalic);
}
else
{
Console.WriteLine($"Reusing existing CharacterStyle: {key}");
}
return _styles[key];
}
public int GetStyleCount()
{
return _styles.Count;
}
}
// Character(文字オブジェクト)
public class Character
{
private char _symbol;
private CharacterStyle _style; // Flyweightへの参照
public Character(char symbol, CharacterStyle style)
{
_symbol = symbol;
_style = style;
}
public void Display(int position)
{
_style.Display(_symbol, position);
}
}
// より実践的な例:ゲームの木
public class TreeType
{
// Intrinsic state(全ての同じ種類の木で共有)
public string Name { get; }
public string Color { get; }
public string Texture { get; }
public TreeType(string name, string color, string texture)
{
Name = name;
Color = color;
Texture = texture;
Console.WriteLine($"Creating new TreeType: {name}");
}
public void Draw(int x, int y, int height)
{
Console.WriteLine($"Drawing {Name} tree at ({x}, {y}) with height {height}");
Console.WriteLine($" Color: {Color}, Texture: {Texture}");
}
}
public class TreeFactory
{
private Dictionary<string, TreeType> _treeTypes = new Dictionary<string, TreeType>();
public TreeType GetTreeType(string name, string color, string texture)
{
string key = $"{name}-{color}-{texture}";
if (!_treeTypes.ContainsKey(key))
{
_treeTypes[key] = new TreeType(name, color, texture);
}
else
{
Console.WriteLine($"Reusing existing TreeType: {name}");
}
return _treeTypes[key];
}
public int GetTreeTypeCount()
{
return _treeTypes.Count;
}
}
public class Tree
{
// Extrinsic state(各木固有の状態)
private int _x;
private int _y;
private int _height;
// Intrinsic state(共有)
private TreeType _type;
public Tree(int x, int y, int height, TreeType type)
{
_x = x;
_y = y;
_height = height;
_type = type;
}
public void Draw()
{
_type.Draw(_x, _y, _height);
}
}
public class Forest
{
private List<Tree> _trees = new List<Tree>();
private TreeFactory _factory = new TreeFactory();
public void PlantTree(int x, int y, int height, string name, string color, string texture)
{
TreeType type = _factory.GetTreeType(name, color, texture);
Tree tree = new Tree(x, y, height, type);
_trees.Add(tree);
}
public void Draw()
{
foreach (var tree in _trees)
{
tree.Draw();
}
}
public void DisplayStats()
{
Console.WriteLine($"\nForest Statistics:");
Console.WriteLine($"Total trees: {_trees.Count}");
Console.WriteLine($"Unique tree types: {_factory.GetTreeTypeCount()}");
Console.WriteLine($"Memory saved by sharing tree types!");
}
}
// 使用例
class Program
{
static void Main()
{
// テキストエディタの例
Console.WriteLine("=== Text Editor ===\n");
var styleFactory = new CharacterStyleFactory();
var normalStyle = styleFactory.GetStyle("Arial", 12, "Black");
var boldStyle = styleFactory.GetStyle("Arial", 12, "Black", isBold: true);
var headerStyle = styleFactory.GetStyle("Arial", 24, "Blue", isBold: true);
var characters = new List<Character>
{
new Character('H', headerStyle),
new Character('e', headerStyle),
new Character('l', headerStyle),
new Character('l', headerStyle),
new Character('o', headerStyle),
new Character(' ', normalStyle),
new Character('W', boldStyle),
new Character('o', boldStyle),
new Character('r', boldStyle),
new Character('l', boldStyle),
new Character('d', boldStyle),
};
for (int i = 0; i < characters.Count; i++)
{
characters[i].Display(i);
}
Console.WriteLine($"\nTotal unique styles created: {styleFactory.GetStyleCount()}");
Console.WriteLine($"Total characters: {characters.Count}");
Console.WriteLine("\n\n=== Game Forest ===\n");
// ゲームの森の例
var forest = new Forest();
// 1000本の木を植える(実際には3種類のTreeTypeのみ作成される)
Random random = new Random(42);
for (int i = 0; i < 100; i++)
{
int treeType = random.Next(3);
switch (treeType)
{
case 0:
forest.PlantTree(random.Next(1000), random.Next(1000),
random.Next(10, 30), "Oak", "Green", "OakTexture");
break;
case 1:
forest.PlantTree(random.Next(1000), random.Next(1000),
random.Next(15, 40), "Pine", "DarkGreen", "PineTexture");
break;
case 2:
forest.PlantTree(random.Next(1000), random.Next(1000),
random.Next(5, 15), "Birch", "LightGreen", "BirchTexture");
break;
}
}
Console.WriteLine("\nDrawing first 5 trees:");
var firstFiveTrees = new Forest();
firstFiveTrees.PlantTree(100, 200, 25, "Oak", "Green", "OakTexture");
firstFiveTrees.PlantTree(300, 150, 30, "Pine", "DarkGreen", "PineTexture");
firstFiveTrees.PlantTree(500, 400, 12, "Birch", "LightGreen", "BirchTexture");
firstFiveTrees.PlantTree(700, 300, 28, "Oak", "Green", "OakTexture");
firstFiveTrees.PlantTree(900, 500, 35, "Pine", "DarkGreen", "PineTexture");
firstFiveTrees.Draw();
forest.DisplayStats();
}
}
7. Proxy(プロキシ)
概要
他のオブジェクトへのアクセスを制御するための代理オブジェクトを提供します。
使用場面
- 遅延初期化(Virtual Proxy)
- アクセス制御(Protection Proxy)
- リモートオブジェクトの代理(Remote Proxy)
- キャッシング、ロギング
サンプルコード
// Subject(共通インターフェース)
public interface IImage
{
void Display();
void Rotate(int degrees);
string GetInfo();
}
// Real Subject(実際のオブジェクト)
public class HighResolutionImage : IImage
{
private string _filename;
private byte[] _imageData;
public HighResolutionImage(string filename)
{
_filename = filename;
LoadFromDisk();
}
private void LoadFromDisk()
{
Console.WriteLine($"Loading high-resolution image from disk: {_filename}");
// 重い処理をシミュレート
System.Threading.Thread.Sleep(2000);
_imageData = new byte[10000000]; // 10MB
Console.WriteLine($"Image loaded: {_filename} ({_imageData.Length / 1000000}MB)");
}
public void Display()
{
Console.WriteLine($"Displaying image: {_filename}");
}
public void Rotate(int degrees)
{
Console.WriteLine($"Rotating image {_filename} by {degrees} degrees");
}
public string GetInfo()
{
return $"HighResolutionImage: {_filename}, Size: {_imageData.Length / 1000000}MB";
}
}
// Virtual Proxy(遅延初期化)
public class ImageProxy : IImage
{
private string _filename;
private HighResolutionImage _realImage;
public ImageProxy(string filename)
{
_filename = filename;
Console.WriteLine($"ImageProxy created for: {_filename} (not loaded yet)");
}
private HighResolutionImage GetRealImage()
{
if (_realImage == null)
{
Console.WriteLine("Lazy loading real image...");
_realImage = new HighResolutionImage(_filename);
}
return _realImage;
}
public void Display()
{
GetRealImage().Display();
}
public void Rotate(int degrees)
{
GetRealImage().Rotate(degrees);
}
public string GetInfo()
{
if (_realImage == null)
{
return $"ImageProxy: {_filename} (not loaded)";
}
return GetRealImage().GetInfo();
}
}
// Protection Proxy(アクセス制御)
public interface IDocument
{
void Display();
void Edit(string content);
void Delete();
}
public class Document : IDocument
{
private string _name;
private string _content;
public Document(string name, string content)
{
_name = name;
_content = content;
}
public void Display()
{
Console.WriteLine($"Document: {_name}");
Console.WriteLine($"Content: {_content}");
}
public void Edit(string content)
{
_content = content;
Console.WriteLine($"Document '{_name}' edited successfully");
}
public void Delete()
{
Console.WriteLine($"Document '{_name}' deleted");
}
}
public class DocumentProxy : IDocument
{
private Document _document;
private string _userRole;
public DocumentProxy(Document document, string userRole)
{
_document = document;
_userRole = userRole;
}
public void Display()
{
Console.WriteLine($"[Access Check] User role: {_userRole}");
_document.Display();
}
public void Edit(string content)
{
if (_userRole == "Admin" || _userRole == "Editor")
{
Console.WriteLine($"[Access Granted] User role: {_userRole}");
_document.Edit(content);
}
else
{
Console.WriteLine($"[Access Denied] User role '{_userRole}' cannot edit documents");
}
}
public void Delete()
{
if (_userRole == "Admin")
{
Console.WriteLine($"[Access Granted] User role: {_userRole}");
_document.Delete();
}
else
{
Console.WriteLine($"[Access Denied] Only Admin can delete documents");
}
}
}
// Caching Proxy
public interface IDataService
{
string GetData(string key);
}
public class DatabaseService : IDataService
{
public string GetData(string key)
{
Console.WriteLine($"[Database] Fetching data for key: {key}");
// データベースアクセスをシミュレート
System.Threading.Thread.Sleep(1000);
return $"Data for {key} from database";
}
}
public class CachingProxy : IDataService
{
private IDataService _realService;
private Dictionary<string, string> _cache;
public CachingProxy(IDataService realService)
{
_realService = realService;
_cache = new Dictionary<string, string>();
}
public string GetData(string key)
{
if (_cache.ContainsKey(key))
{
Console.WriteLine($"[Cache Hit] Returning cached data for key: {key}");
return _cache[key];
}
Console.WriteLine($"[Cache Miss] Key not in cache: {key}");
string data = _realService.GetData(key);
_cache[key] = data;
return data;
}
}
// 使用例
class Program
{
static void Main()
{
// Virtual Proxy(遅延初期化)
Console.WriteLine("=== Virtual Proxy Example ===\n");
IImage image1 = new ImageProxy("vacation.jpg");
IImage image2 = new ImageProxy("family.jpg");
IImage image3 = new ImageProxy("pets.jpg");
Console.WriteLine("\nImages created (but not loaded yet)\n");
Console.WriteLine("Getting info...");
Console.WriteLine(image1.GetInfo());
Console.WriteLine(image2.GetInfo());
Console.WriteLine("\nNow displaying first image...");
image1.Display();
Console.WriteLine("\nDisplaying first image again (already loaded)...");
image1.Display();
// Protection Proxy(アクセス制御)
Console.WriteLine("\n\n=== Protection Proxy Example ===\n");
var document = new Document("Confidential Report", "Top secret information");
Console.WriteLine("--- Viewer attempting to access ---");
IDocument viewerProxy = new DocumentProxy(document, "Viewer");
viewerProxy.Display();
viewerProxy.Edit("Modified content");
viewerProxy.Delete();
Console.WriteLine("\n--- Editor attempting to access ---");
IDocument editorProxy = new DocumentProxy(document, "Editor");
editorProxy.Edit("Modified by editor");
editorProxy.Delete();
Console.WriteLine("\n--- Admin attempting to access ---");
IDocument adminProxy = new DocumentProxy(document, "Admin");
adminProxy.Edit("Modified by admin");
adminProxy.Delete();
// Caching Proxy
Console.WriteLine("\n\n=== Caching Proxy Example ===\n");
IDataService service = new CachingProxy(new DatabaseService());
Console.WriteLine("First request:");
Console.WriteLine(service.GetData("user:123"));
Console.WriteLine("\nSecond request (same key):");
Console.WriteLine(service.GetData("user:123"));
Console.WriteLine("\nThird request (different key):");
Console.WriteLine(service.GetData("user:456"));
Console.WriteLine("\nFourth request (cached key):");
Console.WriteLine(service.GetData("user:123"));
}
}
まとめ
構造パターンの比較:
| パターン | 主な目的 | 使用場面 |
|---|---|---|
| Adapter | インターフェースの変換 | レガシーコード統合、サードパーティAPI |
| Bridge | 抽象と実装の分離 | プラットフォーム独立性、DB抽象化 |
| Composite | ツリー構造の統一的な扱い | ファイルシステム、組織階層、GUI |
| Decorator | 動的な機能追加 | ストリーム処理、ミドルウェア |
| Facade | 複雑なサブシステムの簡素化 | API簡素化、レガシーコードラッピング |
| Flyweight | メモリ効率的な共有 | 大量の類似オブジェクト、キャッシュ |
| Proxy | アクセス制御と遅延初期化 | セキュリティ、遅延ロード、キャッシング |
選択のガイドライン
- 既存のインターフェースを変換 → Adapter
- 抽象と実装を独立に拡張 → Bridge
- 部分-全体の階層構造 → Composite
- オブジェクトに動的に機能追加 → Decorator
- 複雑なシステムを簡単に → Facade
- 大量のオブジェクトを効率的に管理 → Flyweight
- オブジェクトへのアクセスを制御 → Proxy