「ソフトウェア アーキテクチャのマスター: 11 の主要な設計パターンの説明」で、ソフトウェア アーキテクチャの秘密を解き明かしましょう。
2. デザインパターン - アダプタ
3. デザインパターン — ビルダー
4. Chain of Responsibilityパターンの使い方
5. デザインパターン — デコレータ
6. デザインパターン - ファクトリーメソッド
7. デザインパターン — イテレータ
8. デザインパターン — メディエーター
9. デザインパターン — オブザーバー
10. 高度なプロパティ パターン C# 8.0
11. デザインパターン — シングルトン
Gang of Four によれば、抽象ファクトリー パターンはファクトリーを作成するためのファクトリーとして想定できます。
抽象ファクトリ パターンは純粋に拡張ファクトリ メソッドです。抽象ファクトリ設計を理解する前に、ファクトリ メソッドを理解することをお勧めします。
貯蓄口座や当座口座などの口座タイプを持つ銀行の同じ例を考えてみましょう。ここで、抽象ファクトリ設計パターンを使用して上記の例を実装してみましょう。
まず、ISavingAccount および ICurrentAccount インターフェイスを次のように実装します。
public interface ISavingAccount{ } public interface ICurrentAccount{ }
以下のクラスでインターフェースを継承する
public class CurrentAccount : ICurrentAccount { public CurrentAccount(string message) { Console.WriteLine(message); } } public class SavingsAccount : ISavingAccount { public SavingsAccount( string message) { Console.WriteLine(message); } }
各アカウント タイプごとに抽象メソッドを持つ抽象クラスを記述してみましょう。
public abstract class AccountTypeFactory { public abstract ISavingAccount SavingAccountFactory(string message); public abstract ICurrentAccount CurrentAccountFactory(string message); }
ここで、抽象メソッドの実装を提供する「Bank1Factory」という名前のファクトリ実装を作成しましょう。
public class Bank1Factory : AccountTypeFactory { public override ICurrentAccount CurrentAccountFactory(string message) { return new CurrentAccount(message); } public override ISavingAccount SavingAccountFactory(string message) { return new SavingsAccount(message); } }
抽象ファクトリ設計パターンは、定義に従ってファクトリを返すファクトリ プロバイダーを実装する必要があるという点でファクトリ メソッドとは異なります。
これで、すべての抽象化とファクトリが作成されました。ファクトリ プロバイダーを設計しましょう。以下に、ファクトリ プロバイダーのコード スニペットを示します。ここでは、静的メソッドがアカウント名に基づいてファクトリを作成します。
public class AccountFactoryProvider { public static AccountTypeFactory GetAccountTypeFactory(string accountName) { if (accountName.Contains("B1")) { return new Bank1Factory(); } else return null; } }
口座番号のリストの例を見てみましょう。口座名が文字通り「 B1 」で構成されている場合、ファクトリープロバイダーを介して返される Bank1Factory インスタンスが使用されます。
static void Main(string[] args) { List<string> accNames = new List<string> { "B1-456", "B1-987", "B2-222" }; for (int i = 0; i < accNames.Count; i++) { AccountTypeFactory anAbstractFactory = AccountFactoryProvider.GetAccountTypeFactory(accNames[i]); if (anAbstractFactory == null) { Console.WriteLine("Invalid " + (accNames[i])); } else { ISavingAccount savingAccount = anAbstractFactory.SavingAccountFactory("Hello saving"); ICurrentAccount currentAccount = anAbstractFactory.CurrentAccountFactory("Hello Current"); } } Console.ReadLine(); }
アカウント名に「B1」リテラルが含まれていない場合、プログラムは無効な{{accountName}}を出力します。
上記のコード スニペットからの出力を以下に示します。
Hello saving B1-456 Hello Current B1-456 Hello saving B1-987 Hello Current B1-987
Gang of Four によれば、アダプタ パターンはクラスのインターフェイスをクライアントが必要とするインターフェイスに変換します。
言い換えれば、アダプタ設計パターンは、互換性のないインターフェースが共同で動作するのに役立ちます。
2 つの組織が合併する例を考えてみましょう。X 組織が Y 組織を引き継ぎますが、コードを結合する際にインターフェイスに互換性がありません。組織 Y のトランザクション リストを提供するインターフェイスが X と互換性がないと仮定します。
したがって、アダプタ設計パターンは、実装が非常に簡単なこの問題を解決するのに役立ちます。
組織 X のクライアント アプリケーションに必要なパターンに変換される、組織 Y からのトランザクションのリストを作成しましょう。上記のクラスは「Adaptee」と呼ばれます。
public class OrgYTransactions { public List<string> GetTransactionsList() { List<string> transactions = new List<string>(); transactions.Add("Debit 1"); transactions.Add("Debit 2"); transactions.Add("Debit 3"); return transactions; } }
次に、ターゲット インターフェイスを作成しましょう。
public interface ITransactions{ List<string> GetTransactions(); }
最後に、次のようにアダプター クラスを実装します。
public class TransAdapter : OrgYTransactions, ITransactions { public List<string> GetTransactions() { return GetTransactionsList(); } }
上記の実装がすべて完了したら、コンソール アプリケーションでアダプター クラスを使用する方法を理解しましょう。
class Program { static void Main(string[] args) { ITransactions adapter = new TransAdapter(); foreach (var item in adapter.GetTransactions()) { Console.WriteLine(item); } } }
以下の使用例をよく見ると、サードパーティのクラス OrgYTransactions インターフェイスの外観を考慮せずに、ターゲット インターフェイス ITransactions とアダプタ クラス TransAdapter を使用していることがわかります。これが、クラスのインターフェイスをクライアントが必要とするインターフェイスに変換するアダプタ デザイン パターンの威力です。
Gang of Four によれば、創造パターン「Builder」を使用すると、特定のメソッドを分離して再利用し、何かを構築することができます。
車の例を見てみましょう。ユーザーは SUV とセダンという 2 つのモデルを構築したいと考えていました。
ビルダー デザイン パターンは上記のユース ケースで役立ちます。ステップ バイ ステップのデモンストレーションを見てみましょう。
Car クラスには次のプロパティがあります。
public class Car{ public string Name { get; set; } public double TopSpeed { get; set; } public bool IsSUV { get; set; } }
まず、ユースケースに応じて、SUV やセダンなどのさまざまな車種によって拡張された抽象クラス ビルダーを実装しましょう。
public abstract class CarBuilder { protected readonly Car _car = new Car(); public abstract void SetName(); public abstract void SetSpeed(); public abstract void SetIsSUV(); public virtual Car GetCar() => _car; }
抽象クラスは以下のメソッドから構成されます
ここで、CarBuilder クラスを利用してさまざまな自動車モデルを構築し、作成された自動車のインスタンスを返すファクトリーを作成しましょう。
public class CarFactory { public Car Build(CarBuilder builder) { builder.SetName(); builder.SetSpeed(); builder.SetIsSUV(); return builder.GetCar(); } }
最後に、さまざまな車種を実装します。
public class ModelSuv : CarBuilder { public override void SetIsSUV() { _car.IsSUV = true; } public override void SetName() { _car.Name = "Maruti SUV"; } public override void SetSpeed() { _car.TopSpeed = 1000; } }
public class ModelSedan : CarBuilder { public override void SetIsSUV() { _car.IsSUV = false; } public override void SetName() { _car.Name = "Maruti Sedan"; } public override void SetSpeed() { _car.TopSpeed = 2000; } }
最後に、factory.Build(<model>) メソッドを利用して、デザイン パターンを使用してさまざまな車のモデルを構築してみましょう。
static void Main(string[] args) { var sedan = new ModelSedan(); var suv = new ModelSuv(); var factory = new CarFactory(); var builders = new List<CarBuilder> { suv, sedan }; foreach (var b in builders) { var c = factory.Build(b); Console.WriteLine($"The Car details" + $"\n--------------------------------------" + $"\nName: {c.Name}" + $"\nIs SUV: {c.IsSUV}" + $"\nTop Speed: {c.TopSpeed} mph\n"); } }
上記の使用法は、ビルダー デザイン パターンを使用してさまざまな自動車モデルをいかにうまく構築できるかを示しています。
コード パターンは保守性と拡張性に優れています。将来、新しいモデルを開発する必要がある場合、新しいモデルで CarBuilder クラスを拡張するだけで済みます。
Gang of Four によれば、これはリクエストを処理するための責任の連鎖を定義します。言い換えると、オブジェクトが責任を受け入れるまで、あるオブジェクトから別のオブジェクトにリクエストを渡します。
企業の請求システムの例を考えてみましょう。承認できる価格帯と承認者の一覧を以下に示します。
100–1000 Rs => Junior/Senior Engineers => Approved by Manager 1001–10000 Rs => Managers => Approved by Senior Manager
金額が 10000 の範囲外の場合は、上級管理者からの例外的な承認が必要です。
上記のユースケースは、Chain of Responsibility デザイン パターンを使用して簡単に実装できます。したがって、クレーム クラスには次のプロパティがあります。
public class Claim{ public int Id{get;set;} public double amount{get;set;} }
まず、請求承認者が実行できる機能を定義し、さまざまなレベルの従業員の階層を設定しましょう。以下に示すように抽象クラスを実装します。
public abstract class ClaimApprover { protected ClaimApprover claimApprover; public void SetHierarchy(ClaimApprover claimApprover) { this.claimApprover = claimApprover; } public abstract void ApproveRequest(Claim claim); }
ユースケースに従って、クラス「ジュニア/シニア」の請求要求者を駆動してみましょう。このクラス/指定の従業員は、いかなる請求も承認できないことに注意してください。
public class Junior : ClaimApprover { public override void ApproveRequest(Claim claim) { System.Console.WriteLine("Cannot approve"); } }
同様に、マネージャーとシニアマネージャーのロールの実装を定義しましょう。
public class Manager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount >= 100 && claim.amount <= 1000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Manager"); } else if (claimApprover != null) { claimApprover.ApproveRequest(claim); } } }
金額の範囲に基づいて、マネージャーの範囲内であれば、請求はマネージャーによって承認されることに注意してください。それ以外の場合は、リクエストはシニア マネージャーに渡されます。
public class SeniorManager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount > 1000 && claim.amount <= 10000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } else { System.Console.WriteLine($"Exceptional approval for Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } } }
同様に、金額範囲がシニア マネージャーの範囲内であれば、マネージャーが請求を承認できます。それ以外の場合は、階層の最後尾にあるため、範囲外の金額に対しては例外的な承認が行われます。
ClaimApprover junior = new Manager(); ClaimApprover sukhpinder = new Manager(); ClaimApprover singh = new SeniorManager(); junior.SetHierarchy(sukhpinder); sukhpinder.SetHierarchy(singh); Claim c1 = new Claim() { amount = 999, Id = 1001 }; Claim c2 = new Claim() { amount = 10001, Id = 1002 }; junior.ApproveRequest(c1); sukhpinder.ApproveRequest(c2);
Claim reference 1001 with amount 999 is approved by Manager Exceptional approval for Claim reference 1002 with amount 10001 is approved by Senior Manager
行 1 の出力については、金額が範囲内であったため、管理者が承認しました。
ライン2の出力については、上級管理者が承認しましたが、量が範囲外でした。
Gang of Four によれば、このパターンはクラス オブジェクトに追加の責任を動的に追加します。
10 万ルピー相当の車を購入する例を考えてみましょう。会社は以下の追加機能を提供します。
いくつかの追加機能により、車の合計価格が上昇します。Decorator パターンを使用して上記のユースケースを実装してみましょう。
上記で定義したユースケースを実装してみましょう。まず、抽象クラス Car とその基本メソッドを定義します。
public abstract class Car{ public abstract int CarPrice(); public abstract string GetName(); }
抽象クラス Car の上に拡張された小型車について考えてみましょう。
public class SmallCar : Car{ public override int CarPrice() => 10000; public override string GetName() => "Alto Lxi"; }
ここで、Car コンポーネントを使用して CarDecorator クラスを実装します。
public class CarDecorator : Car { protected Car _car; public CarDecorator(Car car) { _car = car; } public override int CarPrice() => _car.CarPrice(); public override string GetName() =>_car.GetName(); }
ここで、CarDecorator クラスを継承して、Car で利用できる追加機能ごとに個別のクラスを作成しましょう。
使用例に応じて、追加機能はサンルーフと高度な音楽システムです。
メソッドをオーバーライドする
「高度な音楽システム」の追加費用を車両の合計価格に追加します。
追加の機能名で車名を更新します。
public class AdvanceMusic : CarDecorator { public AdvanceMusic(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 3000; public override string GetName()=> "Alto Lxi with advance music system"; }
メソッドをオーバーライドする
public class Sunroof : CarDecorator { public Sunroof(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 2000; public override string GetName() => "Alto Lxi with Sunroof"; }
SmallCar のインスタンスを作成し、車の名前と価格を出力します。
Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice());
それでは、以下に示すように追加機能を追加してみましょう。
var car1 = new Sunroof(car); var car2 = new AdvanceMusic(car);
static void Main(string[] args) { Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice()); var car1 = new Sunroof(car); Console.WriteLine($"Price of car {car1.GetName()} : " + car1.CarPrice()); var car2 = new AdvanceMusic(car); Console.WriteLine($"Price of car {car2.GetName()} : " + car2.CarPrice()); }
おめでとうございます。デコレータ パターンを使用してユース ケースを正常に実装しました。
Gang of Four によれば、ファクトリ メソッドを使用すると、サブクラスはどのクラス オブジェクトを作成するかを決定できます。
普通預金口座と普通預金口座の口座タイプを持つ銀行の例を考えてみましょう。上記の例をファクトリーデザインパターンを使用して実装してみましょう。
まず、アカウントタイプの抽象クラスを作成します。
public abstract class AccoutType { public string Balance { get; set; } }
以下に示すように、AccountType 抽象クラスを継承する当座預金口座クラスと普通預金口座クラスを実装します。
public class SavingsAccount : AccoutType { public SavingsAccount() { Balance = "10000 Rs"; } } public class CurrentAccount : AccoutType { public CurrentAccount() { Balance = "20000 Rs"; } }
最後に、クラス オブジェクトの作成に役立つコントラクトを提供するファクトリ インターフェイスを実装します。このインターフェイスは Creator とも呼ばれます。
public interface IAccountFactory { AccoutType GetAccoutType(string accountName); }
最後に、以下に示すように、クリエーター インターフェイス メソッドの実装を記述します。クリエーターを実装するクラスは、Concrete Creator と呼ばれます。
public class AccountFactory : IAccountFactory { public AccoutType GetAccoutType(string accountName) { if (accountName.Equals("SAVINGS", StringComparison.OrdinalIgnoreCase)) { return new SavingsAccount(); } else if (accountName.Equals("CURRENT", StringComparison.OrdinalIgnoreCase)) { return new CurrentAccount(); } else { throw new ArgumentException("Invalid account name"); } } }
これで完了です。Bank の例を使用してファクトリ メソッドを正常に実装できました。
サブクラスは、アカウント名に基づいて、どの「AccountType」クラス オブジェクトが作成されるかを決定します。
class Program { static void Main(string[] args) { IAccountFactory accountFactory = new AccountFactory(); var savingAccount = accountFactory.GetAccoutType("SAVINGS"); Console.WriteLine("Saving account balance: " + savingAccount.Balance); var currentAccount = accountFactory.GetAccoutType("CURRENT"); Console.WriteLine("Current account balance: " + currentAccount.Balance); } }
たとえば、アカウント名が「SAVINGS」の場合、「SavingAccount」クラスのオブジェクトが作成され、返されます。
同様に、アカウント名が「CURRENT」の場合、「CurrentAccount」クラスのオブジェクトがインスタンス化されて返されます。
Saving account balance: 10000 Rs Current account balance: 20000 Rs
Gang of Four によれば、イテレータ パターンは、実装を知らなくてもアグリゲータ オブジェクトを取得するプロセスを提供します。
自動車のコレクション リストとオートバイの配列 string[] の例を見てみましょう。リストか配列かを知らなくてもコレクションを反復処理できるように、アグリゲータ オブジェクトを設計する必要があります。
反復子設計パターンは、標準の反復子がさまざまなコレクション タイプを走査する際にこの問題を解決するのに役立ちます。
上記のユースケースを考慮して、リストと配列の反復子の抽象レイヤーとして機能するカスタム反復子インターフェースを定義しましょう。
public interface IVehicleIterator{ void First(); bool IsDone(); string Next(); string Current(); }
ここで、ユースケースに応じて上記のインターフェースを実装する自動車とオートバイの反復子を作成します。
public class CarIterator : IVehicleIterator { private List<string> _cars; private int _current; public CarIterator(List<string> cars) { _cars = cars; _current = 0; } public string Current() { return _cars.ElementAt(_current); } public void First() { _current = 0; } public bool IsDone() { return _current >= _cars.Count; } public string Next() { return _cars.ElementAt(_current++); } }
car イテレータは List<string> コレクション上に実装され、インターフェース メソッドの実装を提供します。
オートバイのイテレータは、string[] コレクション上に実装され、インターフェース メソッドの実装を提供します。
public class MotercycleIterator : IVehicleIterator { private string[] _motercylces; private int _current; public MotercycleIterator(string[] motercylces) { _motercylces = motercylces; _current = 0; } public string Current() { return _motercylces[_current]; } public void First() { _current = 0; } public bool IsDone() { return _current >= _motercylces.Length; } public string Next() { return _motercylces[_current++]; } }
上記のすべてのイテレータが定義されたら、イテレータを作成する標準のアグリゲーター オブジェクト インターフェイスを定義します。
public interface IVehicleAggregate{ IVehicleIterator CreateIterator(); }
最後に、上記のアグリゲーター インターフェイスを実装するクラスを書き留めます。ユース ケースによると、Car クラスと Motorcycle クラスの両方がアグリゲーター インターフェイスを実装します。
アグリゲータ インターフェイスのメソッドは、以下に示すように関連するイテレータを返します。
public class Car : IVehicleAggregate { private List<string> _cars; public Car() { _cars = new List<string> { "Car 1", "Car 2", "Car 3" }; } public IVehicleIterator CreateIterator() { return new CarIterator(_cars); } }
アグリゲータ インターフェイスのメソッドは、以下に示すように関連するイテレータを返します。
public class Motercycle : IVehicleAggregate { private string[] _motercycles; public Motercycle() { _motercycles = new[] { "Bike 1", "Bike 2", "Bike 3" }; } public IVehicleIterator CreateIterator() { return new MotercycleIterator(_motercycles); } }
PrintVehicles メソッドは、!iterator.isDone かどうかをチェックし、コレクション要素を出力します。どのコレクションを扱う場合でも、First、IsDone、Next などのメソッドを実装します。
static void Main(string[] args) { IVehicleAggregate car = new Vehicles.Car(); IVehicleAggregate motercycle = new Vehicles.Motercycle(); IVehicleIterator carIterator = car.CreateIterator(); IVehicleIterator motercycleIterator = motercycle.CreateIterator(); PrintVehicles(carIterator); PrintVehicles(motercycleIterator); } static void PrintVehicles(IVehicleIterator iterator) { iterator.First(); while (!iterator.IsDone()) { Console.WriteLine(iterator.Next()); } }
基礎となるコレクション型は不明ですが、それでも Iterator デザイン パターンによって反復処理されます。実行すると、次の出力が表示されます。
Gang of Four によれば、Mediator パターンはオブジェクト間の相互作用をカプセル化します。
メディエーター設計パターンは、オブジェクトの相互作用をカプセル化することで、疎結合アプリケーションの設計に役立ちます。
参加者が登録するチャットルームの例と、効率的なコミュニケーションの方法を考えてみましょう。
Mediator デザイン パターンを使用して、次のチャット ルームの会話を実装する必要があります。
David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?'
最初のステップは、チャットルーム内で使用されるユーザー名のリストを作成することです。そのためのパブリック列挙型を以下に示します。
public enum Username{ Ashley, David, Jennifer, Scott }
さて、まず第一に、チャットルームの抽象レイヤーを実装します。
public abstract class AChatroom { public abstract void Register(User user); public abstract void Post(string fromUser, string toUser, string msg); }
そして、抽象メソッドを定義するクラス。メソッドは、ユーザーが辞書内に存在するかどうかを検証します。たとえば、register メソッドは、ユーザーがすでに存在するかどうかを検証します。存在しない場合は、チャットルームにユーザーを登録するだけです。
public class Chatroom : AChatroom { private Dictionary<string, User> _users = new Dictionary<string, User>(); public override void Post(string fromUser, string toUser, string msg) { User participant = _users[toUser]; if (participant != null) { participant.DM(fromUser, msg); } } public override void Register(User user) { if (!_users.ContainsValue(user)) { _users[user.Name] = user; } user.Chatroom = this; } }
最後に、チャットルームでユーザーにメッセージを投稿したり、別のユーザーから DM を受信したりするなど、ユーザーが実行できるアクションを実装しましょう。
public class User { private Chatroom _chatroom; private string _name; public User(string name) => this._name = name; public string Name => _name; public Chatroom Chatroom { set { _chatroom = value; } get => _chatroom; } public void Post(string to, string message) => _chatroom.Post(_name, to, message); public virtual void DM(string from, string message) => Console.WriteLine("{0} to {1}: '{2}'", from, Name, message); }
static void Main(string[] args) { Chatroom chatroom = new Chatroom(); User Jennifer = new UserPersona(Username.Jennifer.ToString()); User Ashley = new UserPersona(Username.Ashley.ToString()); User David = new UserPersona(Username.David.ToString()); User Scott = new UserPersona(Username.Scott.ToString()); chatroom.Register(Jennifer); chatroom.Register(Ashley); chatroom.Register(David); chatroom.Register(Scott); David.Post(Username.Scott.ToString(), "Hey"); Scott.Post(Username.David.ToString(), "I am good how about you."); Jennifer.Post(Username.Ashley.ToString(), "Hey ashley... david is back in the group"); Jennifer.Post(Username.David.ToString(), "Where have you been?"); Ashley.Post(Username.David.ToString(), "How come you aren't active here anymore?"); Console.ReadKey(); }
プログラム実行では、ユーザー クラスの Post メソッドのみが記述されます。
出力:上記プログラム実行のチャットルーム履歴
David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?'
Gang of Four によれば、オブザーバー パターンは 2 つ以上のオブジェクト間の依存関係を定義します。そのため、1 つのオブジェクトの状態が変化すると、その依存関係にあるすべてのオブジェクトに通知されます。
つまり、あるオブジェクトが変更されると、別のオブジェクトで通知が開始されます。
フォロワー数が「 x 」の Instagram セレブインフルエンサーを例に挙げてみましょう。セレブが投稿を追加すると、すべてのフォロワーに通知が届きます。
オブザーバー デザイン パターンを使用して、前述のユース ケースを実装してみましょう。
ユースケースによれば、最初に有名人が実行できるアクションを含むインターフェースを実装します。これは「 Subject 」と呼ばれます。
public interface ICelebrityInstagram{ string FullName { get; } string Post { get; set; } void Notify(string post); void AddFollower(IFollower fan); void RemoveFollower(IFollower fan); }
通知:すべてのフォロワーに通知します。
AddFollower:有名人リストに新しいフォロワーを追加します。
RemoveFollower:有名人リストからフォロワーを削除します。
ここで、通知用の「Update」メンバー関数を含むオブザーバー「IFollower」インターフェイスを実装します。
public interface IFollower{ void Update(ICelebrityInstagram celebrityInstagram); }
最後に、「 Subject 」と「 Observer 」の両方に対して「具体的な実装」を実装します。
有名人の名前と投稿をコンソールに出力する Update メンバー関数の実装を提供します。
public class Follower : IFollower { public void Update(ICelebrityInstagram celebrityInstagram) { Console.WriteLine($"Follower notified. Post of {celebrityInstagram.FullName}: " + $"{celebrityInstagram.Post}"); } }
public class Sukhpinder : ICelebrityInstagram { private readonly List<IFollower> _posts = new List<IFollower>(); private string _post; public string FullName => "Sukhpinder Singh"; public string Post { get { return _post; } set { Notify(value); } } public void AddFollower(IFollower follower) { _posts.Add(follower); } public void Notify(string post) { _post = post; foreach (var item in _posts) { item.Update(this); } } public void RemoveFollower(IFollower follower) { _posts.Remove(follower); } }
次のユースケースは、以下のステートメントが実行されるたびに、sukhpinder.Post = “I love design patterns.”; 更新メソッドが各フォロワーに対してトリガーされ、各フォロワー オブジェクトに “Sukhpinder” からの新しい投稿が通知されることを示しています。
static void Main(string[] args) { var sukhpinder = new Sukhpinder(); var firstFan = new Follower(); var secondFan = new Follower(); sukhpinder.AddFollower(firstFan); sukhpinder.AddFollower(secondFan); sukhpinder.Post = "I love design patterns."; Console.Read(); }
この記事では、パターン マッチングによって、プライマリ システムの一部ではないフォームのデータを効果的に活用および処理する方法を説明します。
Toll Calculator の例を取り上げて、パターン マッチングがそのアルゴリズムの記述にどのように役立つかを見てみましょう。
public class Car { public int PassengerCount { get; set; } } public class DeliveryTruck { public int Weight { get; set; } } public class Taxi { public int Fare { get; set; } } public class Bus { public int Capacity { get; set; } public int RidersCount { get; set; } }
例 1: 次の条件に従って通行料金を計算します。
- 車両が車の場合 => 100ルピー
- 車両が配送トラックの場合 => 200 Rs
- バスの場合 => 150ルピー
- 車両がタクシーの場合 => 120ルピー
車両タイプが Car 100 と一致する場合は、次のように返されます。null と {} はオブジェクト タイプのデフォルト ケースであることに注意してください。
また、「_」を使用してデフォルトのシナリオをプログラムすることもできます。新しいスイッチ構文を参照してください。
これははるかにクリーンかつ効率的なコーディング方法であり、スイッチ構文内で 1 文字の変数名を使用することも推奨されています。
public static int TollFare(Object vehicleType) => vehicleType switch { Car c => 100, DeliveryTruck d => 200, Bus b => 150, Taxi t => 120, null => 0, { } => 0 };
コンソール アプリケーションの観点から例をテストします。以下のコードは、上記のパターン マッチング関数をメイン メソッドから呼び出す方法を示しています。
var car = new Car(); var taxi = new Taxi(); var bus = new Bus(); var truck = new DeliveryTruck(); Console.WriteLine($"The toll for a car is {TollFare(car)}"); Console.WriteLine($"The toll for a taxi is {TollFare(taxi)}"); Console.WriteLine($"The toll for a bus is {TollFare(bus)}"); Console.WriteLine($"The toll for a truck is {TollFare(truck)}");
The toll for a car is 100 The toll for a taxi is 120 The toll for a bus is 150 The toll for a truck is 200
例 2: 車両タイプに基づいて乗車料金を追加する
- 乗客が「いない」車とタクシーは、10 ルピーの追加料金がかかります。
- 2人乗りの車とタクシーは10ルピーの割引になります。
- 3人以上の乗客がいる車とタクシーには20ルピーの割引があります。
- 乗客が 50% 未満のバスは 30 ルピーの追加料金がかかります。
- 乗客の90%以上が乗車しているバスには40ルピーの割引が適用されます。
- 5000 ポンドを超えるトラックには 100 ルピーの追加料金がかかります。
- 3000 ポンド未満の小型トラックには 20 ルピーの割引が適用されます。
単一および複数のプロパティクラスを使用したパターンマッチング構文を参照してください。 リンク
Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20,
Taxi {Fare:0 }=>100+10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20,
Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150,
DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200,
以下の例は、パターン マッチングの利点を強調しています。パターン ブランチは順番にコンパイルされます。コンパイラは、到達不可能なコードについても警告します。
public static int OccupancyTypeTollFare(Object vehicleType) => vehicleType switch { Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20, Taxi { Fare: 0 } => 100 + 10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20, Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150, DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200, null => 0, { } => 0, };
コンソール アプリケーションの観点から例をテストします。以下のコードは、上記のパターン マッチング関数をメイン メソッドから呼び出す方法を示しています。
var car1 = new Car{ PassengerCount=2}; var taxi1 = new Taxi { Fare = 0 }; var bus1 = new Bus { Capacity = 100, RidersCount = 30 }; var truck1 = new DeliveryTruck { Weight = 30000 }; Console.WriteLine($"The toll for a car is {OccupancyTypeTollFare(car1)}"); Console.WriteLine($"The toll for a taxi is {OccupancyTypeTollFare(taxi1)}"); Console.WriteLine($"The toll for a bus is {OccupancyTypeTollFare(bus1)}"); Console.WriteLine($"The toll for a truck is {OccupancyTypeTollFare(truck1)}");
The toll for a car is 90 The toll for a taxi is 110 The toll for a bus is 180 The toll for a truck is 300
「パターン マッチングにより、コードが読みやすくなり、クラスにコードを追加できない場合にオブジェクト指向テクニックの代替手段が提供されます。」
Gang of Four — シングルトン設計パターンは、特定のクラスに 1 つのインスタンス/オブジェクトとグローバル アクセス ポイントのみが存在することを保証します。
シングルトン クラスは、特定のクラスの複数のオブジェクトのインスタンス化を排除するために使用されます。
public class SingletonExample { private string Name { get; set; } = "Hello from singleton"; private static SingletonExample _instance; public static SingletonExample Instance { get { if (_instance == null) { _instance = new SingletonExample(); } return _instance; } } public SingletonExample() { } public string GetName() => Name; }
シングルトン クラスを 2 回呼び出して、返されたインスタンスを 2 つの異なる変数に割り当ててみましょう。最後に、Object.Equals 関数を使用して、両方のオブジェクトが等しいかどうかを確認します。
static void Main(string[] args) { var response = SingletonExample.Instance; Console.WriteLine(response); var response1 = SingletonExample.Instance; Console.WriteLine(response1); Console.WriteLine(Object.Equals(response1, response)); }
コンソール出力は true を返します。おめでとうございます。シングルトン パターンを正常に実装しました。
上記のクラスはシングルトン クラスとして知られていますが、現時点ではスレッドセーフではありません。マルチスレッド環境では、2 つのスレッドが同時に if (_instance == null) ステートメントを実行する可能性があり、シングルトン クラスのインスタンスが複数存在することになります。
より安全なスレッドを実現する方法の 1 つはロック メカニズムを使用することです。もう 1 つの方法は、よりクリーンで効率的なアプローチとして読み取り専用インスタンスを作成することです。
public class ThreadSafeSingleton { private static readonly ThreadSafeSingleton _instance = new ThreadSafeSingleton(); public static ThreadSafeSingleton Instance { get { return _instance; } } public ThreadSafeSingleton() { } }
https://github.com/ssukhpinder/DesignPatterns
スポンサーシップは、このような新しいプロジェクトを維持し、構築し続けるのに役立ちます。
🙏 Pay、Noticed、または私の他のプロジェクトをご利用の場合、小さな寄付でも大きな意味を持ちます。オープンソースだけでは生活費を賄うことはできません。皆さんの助けがあれば、私の仕事を継続し続けることができ、本物の仕事に就く必要がなくなることを願っています😛。
C# コミュニティに参加していただきありがとうございます。