اكتشف أسرار هندسة البرمجيات مع إتقان هندسة البرمجيات: شرح 11 نمط تصميم رئيسي .
2. نمط التصميم — المحول
3. نمط التصميم — Builder
4. كيفية استخدام نمط سلسلة المسؤولية
5. نمط التصميم — المُزيّن
6. نمط التصميم - طريقة المصنع
7. نمط التصميم — المُكرر
8. نمط التصميم - الوسيط
9. نمط التصميم — المراقب
10. نمط خاصية متقدم في C# 8.0
11. نمط التصميم — Singleton
وفقا لعصابة الأربعة، يمكن اعتبار أنماط المصانع المجردة بمثابة المصنع لإنشاء المصانع.
نمط المصنع المجرد هو مجرد طريقة مصنع تمديد؛ ومن المستحسن الاطلاع على طريقة المصنع قبل فهم تصميم المصنع المجرد.
لنأخذ نفس المثال لأي بنك به أنواع حسابات مثل حسابات التوفير والحسابات الجارية. الآن، لنطبق المثال أعلاه باستخدام نمط تصميم المصنع المجرد.
أولاً، قم بتنفيذ واجهتي 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، يقوم نمط المحول بتحويل واجهات فئة إلى واجهات يتطلبها العميل.
بعبارة أخرى، يساعد نمط تصميم المحول الواجهات غير المتوافقة على العمل بشكل جماعي.
لنتأمل مثالاً عن اندماج منظمتين؛ حيث تتولى المنظمة X مسؤولية المنظمة Y، ولكن أثناء دمج التعليمات البرمجية، لا تتوافق الواجهات. لنفترض أن الواجهة التي توفر قائمة بالمعاملات الخاصة بالمنظمة Y غير متوافقة مع المنظمة X.
لذا، يساعد نمط تصميم المحول في حل هذه المشكلة، حيث يعد تنفيذها بسيطًا للغاية.
لنقم بإنشاء قائمة بالمعاملات من المؤسسة Y التي يتم تحويلها إلى أنماط يتطلبها تطبيق العميل للمؤسسة X. تُعرف الفئة أعلاه باسم "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); } } }
إذا نظرت عن كثب إلى الاستخدام أدناه، فقد استخدمنا واجهة الهدف ITransactions وفئة المحول TransAdapter دون النظر في شكل واجهات فئة OrgYTransactions التابعة لجهة خارجية. هذه هي قوة نمط تصميم المحول، فهو يحول واجهات فئة إلى واجهات يحتاجها العميل.
وفقًا لـ Gang of Four، فإن نمط الإبداع "Builder" يسمح للشخص بفصل وإعادة استخدام طريقة محددة لبناء شيء ما.
لنأخذ مثالاً لسيارة، ويريد المستخدم بناء نموذجين، أي سيارة رياضية متعددة الاستخدامات وسيارة سيدان.
يُعد نمط تصميم الباني مفيدًا في حالة الاستخدام المذكورة أعلاه، ولنرى عرضًا توضيحيًا خطوة بخطوة.
تتمتع فئة السيارة بالخصائص التالية:
public class Car{ public string Name { get; set; } public double TopSpeed { get; set; } public bool IsSUV { get; set; } }
أولاً، دعنا ننفذ منشئ فئة مجردة ممتد بواسطة نماذج سيارات مختلفة مثل سيارات الدفع الرباعي أو سيارات السيدان حسب حالة الاستخدام.
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، فإنه يتطلب موافقة استثنائية من المدير الأعلى.
يمكن تنفيذ حالة الاستخدام المذكورة أعلاه بسهولة باستخدام نمط تصميم سلسلة المسؤولية. لذا، تتمتع فئة المطالبة بالخصائص التالية.
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
بالنسبة لمخرجات الخط الأول، كان المبلغ ضمن النطاق، لذلك وافق عليه المدير.
بالنسبة لمخرجات الخط الثاني، على الرغم من موافقة المدير الأعلى عليها، إلا أن المبلغ كان خارج النطاق.
وفقًا لـGang of Four، يضيف النمط مسؤوليات إضافية إلى كائن الفئة بشكل ديناميكي.
لنأخذ مثال شراء سيارة بقيمة عشرة لاك روبية؛ حيث توفر الشركة الميزات الإضافية التالية.
مع بعض الميزات الإضافية، يزداد السعر الإجمالي للسيارة. دعنا ننفذ حالة الاستخدام المذكورة أعلاه باستخدام نمط الديكور.
دعنا ننفذ حالة الاستخدام المحددة أعلاه. أولاً، قم بتعريف فئة مجردة Car وطرقها الأساسية.
public abstract class Car{ public abstract int CarPrice(); public abstract string GetName(); }
خذ في الاعتبار سيارة صغيرة تمتد فوق فئة السيارة المجردة.
public class SmallCar : Car{ public override int CarPrice() => 10000; public override string GetName() => "Alto Lxi"; }
الآن، قم بتنفيذ فئة CarDecorator باستخدام مكون Car.
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(); }
الآن، دعنا نقوم بإنشاء فئة منفصلة لكل ميزة إضافية متاحة لـ Car التي ترث فئة CarDecorator.
وبحسب حالة الاستخدام، فإن الميزات الإضافية هي فتحة سقف ونظام موسيقى متقدم.
تجاوز الأساليب مثل
أضف التكلفة الإضافية لنظام الموسيقى المتقدم إلى السعر الإجمالي للسيارة.
تحديث اسم السيارة مع اسم الميزة الإضافية.
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"); } } }
هذا كل شيء. لقد قمت بنجاح بتنفيذ طريقة المصنع باستخدام مثال البنك.
ستقرر الفئة الفرعية أي كائن فئة "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++); } }
يتم تنفيذ متكرر السيارة عبر مجموعة 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(); }
أخيرًا، اكتب الفئات التي تنفذ واجهة التجميع المذكورة أعلاه. وفقًا لحالة الاستخدام، ستنفذ كل من فئتي السيارة والدراجة النارية واجهة التجميع.
تعيد طريقة واجهة المجمع المُكرر ذي الصلة كما هو موضح أدناه.
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()); } }
لا نعرف نوع المجموعة الأساسية، ولكن لا يزال يتم تكرارها عبر نمط تصميم المُكرر. إذا قمت بالمتابعة والتشغيل، فسوف يعرض الناتج التالي.
وفقًا لـ Gang of Four، فإن نمط الوسيط يجسد تفاعل الكائنات مع بعضها البعض.
يساعدنا نمط تصميم الوسيط في تصميم تطبيقات مرتبطة بشكل فضفاض من خلال تغليف تفاعلات الكائنات.
دعونا نفكر في مثال لغرفة دردشة حيث يقوم المشاركون بالتسجيل، وكيفية التواصل بشكل فعال.
يجب تنفيذ محادثة غرفة الدردشة التالية باستخدام نمط تصميم الوسيط.
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); }
وفئة تحدد طرقًا مجردة. وتتحقق الطرق من وجود المستخدم في القاموس. على سبيل المثال، تتحقق طريقة التسجيل من وجود المستخدم بالفعل أم لا. وإذا لم يكن موجودًا، فقم بتسجيل المستخدم في غرفة الدردشة فقط.
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; } }
أخيرًا، دعنا ننفذ الإجراءات التي يمكن للمستخدم القيام بها، مثل نشر رسالة إلى مستخدم في غرفة الدردشة أو تلقي رسالة مباشرة من مستخدم آخر.
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، يحدد نمط المراقب التبعية بين كائنين أو أكثر. لذا، عندما تتغير حالة أحد الكائنات، يتم إخطار جميع الكائنات التابعة له.
بعبارة أخرى، يؤدي التغيير في كائن واحد إلى بدء الإشعار في كائن آخر.
لنأخذ مثالاً لشخصية مؤثرة على إنستغرام لديها عدد " x " من المتابعين. لذا، في اللحظة التي يضيف فيها الشخص المشهور منشورًا، يتم إخطار جميع المتابعين.
دعونا ننفذ حالة الاستخدام المذكورة أعلاه باستخدام نمط تصميم المراقب.
وفقًا لحالة الاستخدام، يتم تنفيذ واجهة تحتوي على الإجراءات التي يمكن للمشاهير تنفيذها. تُعرف باسم " الموضوع ".
public interface ICelebrityInstagram{ string FullName { get; } string Post { get; set; } void Notify(string post); void AddFollower(IFollower fan); void RemoveFollower(IFollower fan); }
إعلام: لإعلام جميع المتابعين.
إضافة متابع: قم بإضافة متابع جديد إلى قائمة المشاهير.
إزالة المتابع: إزالة المتابع من قائمة المشاهير.
الآن، قم بتنفيذ واجهة المراقب "IFollower"، التي تحتوي على دالة العضو "Update" للإشعارات.
public interface IFollower{ void Update(ICelebrityInstagram celebrityInstagram); }
أخيرًا، حان الوقت لتطبيق "التنفيذ الملموس" لكل من " الموضوع " و" المراقب ".
إنه يوفر تنفيذًا لوظيفة العضو 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(); }
تصف المقالة كيف يوفر مطابقة الأنماط طريقة فعالة لاستخدام البيانات ومعالجتها في النماذج التي لم تكن جزءًا من النظام الأساسي.
دعنا نأخذ مثالاً لحاسبة الرسوم ونرى كيف يساعد مطابقة الأنماط في كتابة خوارزمية لذلك.
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 روبية
- إذا كانت السيارة هي DeliveryTruck => 200 روبية
- إذا كانت السيارة حافلة => 150 روبية
- إذا كانت السيارة عبارة عن سيارة أجرة => 120 روبية
إذا كان نوع السيارة يتطابق مع السيارة 100، يتم إرجاع & وما إلى ذلك. لاحظ أن null و{} هما حالتان افتراضيتان لنوع الكائن.
يمكن أيضًا استخدام "_" لبرمجة السيناريو الافتراضي. راجع بناء جملة التبديل الجديد.
إنها طريقة أكثر نظافة وكفاءة للترميز، كما يوصى أيضًا باستخدام أسماء المتغيرات المكونة من حرف واحد داخل بناء جملة التبديل.
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 روبية.
- تحصل السيارات وسيارات الأجرة التي تحمل راكبين على خصم بقيمة 10 روبية.
- تحصل السيارات وسيارات الأجرة التي تحمل ثلاثة ركاب أو أكثر على خصم بقيمة 20 روبية.
- تدفع الحافلات التي تقل عن 50% من الركاب رسومًا إضافية قدرها 30 روبية.
- تحصل الحافلات التي تحتوي على أكثر من 90% من الركاب على خصم 40 روبية.
- يتم فرض رسوم إضافية قدرها 100 روبية على الشاحنات التي يزيد وزنها عن 5000 رطل.
- الشاحنات الخفيفة التي يقل وزنها عن 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
"يجعل مطابقة الأنماط الكود أكثر قابلية للقراءة ويقدم بديلاً لتقنيات التوجه نحو الكائنات عندما لا تتمكن من إضافة الكود إلى فئاتك."
نمط تصميم عصابة الأربعة - يضمن أن فئة معينة لديها مثيل/كائن واحد فقط ونقطة وصول عالمية.
يتم استخدام فئات Singleton للتخلص من إنشاء أكثر من كائن واحد من فئة معينة.
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; }
دعنا نستدعي فئة singleton مرتين ونقوم بتعيين المثيل المُعاد إلى متغيرين مختلفين. أخيرًا، نتحقق مما إذا كان كلا الكائنين متساويين باستخدام الدالة 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؛ تهانينا. لقد نجحت في تنفيذ نمط Singleton.
تُعرف الفئة المذكورة أعلاه باسم فئة singleton، ولكنها ليست آمنة للخيوط حاليًا. في بيئة متعددة الخيوط، يمكن لخيطين الوصول إلى عبارة if (_instance == null) في نفس الوقت، وسوف ينتهي بنا الأمر بالحصول على مثيلات متعددة لفئة singleton.
إحدى الطرق للحصول على خيط أكثر أمانًا هي استخدام آلية القفل، والطريقة الأخرى هي إنشاء مثيل للقراءة فقط للحصول على نهج أنظف وأكثر كفاءة.
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#!