Interview Handbook by Sayed Elmahdy English

المحور 15 · التصميم والـ Architecture

Design Patterns

الـ 23 pattern بتوع GoF بكود C#، ومعاهم Repository و Unit of Work و Specification و Result.

18 سؤالJunior · 5Mid · 8Senior · 512 اختيارات

المفاهيم

هنفهم إيه؟ لما إضافة طريقة تصدير أو قاعدة شحن جديدة تعمل switch أكبر، هتعرف إمتى pattern تساعدك تفصل القرار عن التنفيذ، وإمتى تبقى تعقيد زيادة. كل pattern هنا يبدأ بمشكلة عملية، ثم شكل الحل، وبعده الثمن أو الحد اللي لازم تلاحظه.

قبل ما تبدأ: المثال مكتوب بـ C#: object نسخة من class، وmethod عملية عليه، وinterface عقد بين أجزاء التطبيق. لازم تعرف تقرأ class وmethod بسيطة؛ مش مطلوب تحفظ أسماء أو تستخدم pattern لمجرد إن لها اسم معروف.

المصطلحات اللي تحتها خط منقّط تقدر تضغط عليها لمعرفة معناها، وترجع تكمل من نفس مكانك.

#Patterns و Singleton: الاسم وراه مشكلةPatterns and Singleton: start with the problem

Pattern اسم لحل تصميم متكرر مع سياق وتكلفة، مش requirement ولا library. الـ 23 patterns متقسمين Creational لإنشاء objects، وStructural لتركيبها، وBehavioral لتوزيع السلوك والتواصل. الأمثلة هنا مستقلة؛ افترض usings المعتادة، وماتجمعش تعريفات بنفس الاسم من كذا مثال.

Singleton بتحل احتياج instance مشتركة مع نقطة وصول واحدة. تنفيذ static تقليدي بيخبي dependency ويصعّب استبدالها. Lazy initialization آمنة مش معناها إن methods بتاعة الـ object thread-safe.

public sealed class OrderNumberFormatter
{
    private static readonly Lazy<OrderNumberFormatter> _instance =
        new(() => new OrderNumberFormatter());                  // (1)
    public static OrderNumberFormatter Instance => _instance.Value;
    private OrderNumberFormatter() { }
    public string Format(int number) => $"ORD-{number:000000}";
}

// Alternative in a .NET application's composition root:
builder.Services.AddSingleton(TimeProvider.System);              // (2)
🧾 شرح الكود
  1. (1) Lazy تنشئ formatter عند أول طلب؛ المثال عديم state متغيرة، ومش مولد أرقام فريدة.
  2. (2) تملك shared service وتمررها صراحة. Singleton lifetime واحدة لكل service provider، مش لكل cluster، ومش منع عالمي لـ new.
تقليديغالبًا أنسب في
Static access على singletonDI singleton قابلة للاستبدال والـ lifetime واضحة
ساعة static في business rulesTimeProvider محقونة لسهولة الاختبار

إمتى لأ؟ state تخص request أو user، أو خدمة محتاجة scoped . ماتمسكش scoped service جوه singleton، وماتحوّلش كل الخدمات لـ singleton عشان تقلل .

Creational: إنشاء الـ objects

#Factory Method و Abstract FactoryFactory Method and Abstract Factory

Factory Method: غيّر المنتج داخل workflow ثابت

عندك workflow لتصدير invoices، وكل نوع تصدير محتاج writer مختلف. Subclass class وارثة سلوك مشترك من base class؛ وoverride يعني تستبدل تنفيذ method مسموح بتغييرها. Factory Method تخلي النوع المتخصص يختار المنتج من غير ما يكرر workflow. switch بترجع implementation مفيدة كـ simple factory، لكنها مش نفس الشكل الكلاسيكي.

public interface IInvoiceWriter { string Write(decimal total); }
public sealed class TextInvoiceWriter : IInvoiceWriter
{
    public string Write(decimal total) => $"Total: {total}";
}
public abstract class InvoiceExport
{
    protected abstract IInvoiceWriter CreateWriter();           // (1)
    public string Export(decimal total) => CreateWriter().Write(total);
}
public sealed class TextInvoiceExport : InvoiceExport
{
    protected override IInvoiceWriter CreateWriter() =>
        new TextInvoiceWriter();                               // (2)
}
🧾 شرح الكود
  1. (1) الـ base بتحدد خطوات التصدير وتسيب اختيار النوع للـ subclass.
  2. (2) الـ subclass تختار المنتج من غير تعديل workflow.

: factory زي IHttpClientFactory شائعة، لكن اسم factory مش دليل إنها GoF Factory Method. إمتى لأ؟ لو constructor injection لـ writer كافية، inheritance hierarchy هتبقى زيادة.

Abstract Factory: عيلة منتجات متوافقة

لو payment client لازم تيجي مع verifier لنفس provider، factory للعيلة تمنع خلط الاختيارات في كل call site. المثال بيعرض configuration بس؛ مش تنفيذ دفع أو تحقق signature.

public sealed record ChargeClient(string Provider);
public sealed record WebhookVerifier(string Provider);
public interface IPaymentKit
{
    ChargeClient CreateClient();
    WebhookVerifier CreateVerifier();                           // (1)
}
public sealed class SandboxKit : IPaymentKit
{
    public ChargeClient CreateClient() => new("Sandbox");
    public WebhookVerifier CreateVerifier() => new("Sandbox");  // (2)
}
🧾 شرح الكود
  1. (1) عقد الإنشاء لعيلة منتجات، مش منتج واحد.
  2. (2) implementation واحدة تختار الاتنين مع بعض. النظام الحقيقي يحتاج عقود وسلوك يحافظوا على التوافق.

.NET: DbProviderFactory تنشئ عيلة database objects زي connections وcommands. إمتى لأ؟ لو منتج واحد فقط أو المنتجات مالهاش توافق مطلوب. إضافة نوع منتج جديد للعيلة بتغيّر factory interface وكل implementations.

Patternالتغيير الأساسي
Factory Methodsubclass تختار منتج من خلال override
Abstract Factoryobject متخصصة تنشئ عيلة متوافقة؛ ممكن تستخدم factory methods من جوه
Simple factorymethod مركزية تختار النوع، مثل switch أو keyed

#Builder و PrototypeBuilder and Prototype

Builder: خطوات بدل constructor صعب

لما إنشاء تقرير محتاج خيارات مترابطة، builder تجمعها وتتحقق منها عند Build. Fluent syntax لوحدها مش pattern كاملة؛ المهم إدارة عملية الإنشاء وفصلها عن المنتج.

public sealed record ReportDefinition(DateOnly From, DateOnly To);
public sealed class ReportBuilder
{
    private DateOnly? _from, _to;
    public ReportBuilder Between(DateOnly from, DateOnly to)
    {
        _from = from; _to = to; return this;                    // (1)
    }
    public ReportDefinition Build()
    {
        if (_from is null || _to is null || _from > _to)
            throw new InvalidOperationException("Invalid date range");
        return new(_from.Value, _to.Value);                    // (2)
    }
}
🧾 شرح الكود
  1. (1) نجمع الإعدادات؛ return this تسمح بالـ chaining.
  2. (2) نرجع منتج جديد بدل تسريب mutable state بتاعة builder.

: WebApplicationBuilder وconnection-string builders أمثلة للبناء التدريجي. إمتى لأ؟ object بسيطة باثنين parameters؛ constructor أو initializer أوضح.

Prototype: ابدأ من نسخة موجودة

بدل بناء إعداد طلب مشابه من الصفر، انسخ template وعدل المختلف. لازم تحدد shallow ولا deep copy؛ نسخ reference مش نسخ object اللي وراها.

public sealed record OrderTemplate(string Name, List<string> Tags);
// Usage fragment:
var original = new OrderTemplate("Standard", ["Gift"]);
var shallow = original with { Name = "Express" };                // (1)
var independent = original with { Tags = [.. original.Tags] };   // (2)
🧾 شرح الكود
  1. (1) record with تنسخ shallow؛ Tags لسه مشتركة.
  2. (2) list جديدة تكفي هنا لأن عناصرها strings immutable؛ objects متغيرة جواها كانت هتحتاج سياسة نسخ أعمق.

.NET: records وcopy constructors أدوات تنفيذ؛ ICloneable مش بتحدد shallow ولا deep. إمتى لأ؟ resource owners زي connections، أو objects بهوية لازم تتولد من جديد؛ النسخ ممكن يكرر identity أو ownership غلط.

Structural: تركيب الـ objects

#Adapter و FacadeAdapter and Facade

Adapter: interface مختلفة لنفس الاحتياج

قديمة بتتعامل بالسنت، والتطبيق بيتعامل بالمبلغ العشري. Adapter زي محوّل الفيشة: تعرض العقد اللي العميل محتاجه وتترجم البيانات. المثال بيفترض عملة بمئة وحدة صغيرة، والمبلغ متحقق منه قبل الاستدعاء.

public sealed class LegacyPayment
{
    public string ChargeCents(long cents) => $"receipt-{cents}";
}
public interface IPayment { string Charge(decimal amount); }
public sealed class PaymentAdapter(LegacyPayment legacy) : IPayment
{
    public string Charge(decimal amount)
    {
        long cents = checked((long)Math.Round(amount * 100m));   // (1)
        return legacy.ChargeCents(cents);                       // (2)
    }
}
🧾 شرح الكود
  1. (1) ترجمة وحدة القياس بسياسة rounding صريحة في النظام الحقيقي.
  2. (2) legacy مابتتسربش للـ caller.

: adapter حوالين generated أو client ممكن تشكل كمان. إمتى لأ؟ لو العقدين متوافقين أصلًا؛ wrapper فارغة مش بتحل مشكلة.

Facade: باب بسيط لـ subsystem

عميل عرض فاتورة محتاج بيانات وشكل نهائي، مش تفاصيل parser وrenderer. Facade تجمع التفاعل في عملية واضحة من غير ما تدعي إنها بتغيّر عقود المكونات.

public sealed class InvoiceData
{
    public decimal Parse(string input) =>
        decimal.Parse(input, System.Globalization.CultureInfo.InvariantCulture);
}
public sealed class InvoiceRenderer
{
    public string Render(decimal total) => $"Invoice total: {total}";
}
public sealed class InvoiceFacade(InvoiceData data, InvoiceRenderer renderer)
{
    public string Create(string input) =>
        renderer.Render(data.Parse(input));                     // (1)
}
🧾 شرح الكود
  1. (1) العميل يعرف Create بس؛ Parse ممكن تفشل ولازم الـ boundary تتعامل مع input غير صالح.

.NET: خدمة application فوق مكتبات parsing وrendering مثال طبيعي. إمتى لأ؟ لو facade بقت God object بتعرف كل subsystem وكل قواعد business؛ قسمها حسب use case. Facade مش ضمان أو rollback.

#Decorator و Proxy والفرق عن AdapterDecorator, Proxy and their difference from Adapter

Decorator تضيف مسؤولية حوالين object بنفس العقد، من غير subclass لكل توليفة. Proxy تتحكم في الوصول: lazy creation أو remote access أو حماية. شكلهم ممكن يتشابه؛ النية هي الفرق، ونفس wrapper ممكن تخدم أكتر من نية.

public interface IPriceReader { decimal Read(int productId); }
public sealed class FixedPrices : IPriceReader
{
    public decimal Read(int productId) => 100m;
}
public sealed class LoggedPrices(IPriceReader inner, Action<int> log)
    : IPriceReader
{
    public decimal Read(int id)
    {
        log(id); return inner.Read(id);                         // (1) Decorator
    }
}
public sealed class LazyPrices(Func<IPriceReader> factory) : IPriceReader
{
    private readonly Lazy<IPriceReader> _inner = new(factory);   // (2) Proxy
    public decimal Read(int id) => _inner.Value.Read(id);
}
🧾 شرح الكود
  1. (1) logging مسؤولية إضافية حوالين القراءة؛ ترتيب decorators ممكن يغير السلوك.
  2. (2) proxy تؤخر إنشاء القارئ لأول استخدام؛ هي ماخلتش التنفيذ نفسه thread-safe.

: GZipStream تلف Stream وتضيف compression كـ Decorator. lazy-loading proxies مثال Proxy، مع خطر ، وgenerated remote clients يخفي شكلها المحلي تكلفة الشبكة.

إمتى لأ؟ Decorator لو tracing بسيط مركزي كفاية ومافيش composition مطلوبة؛ Proxy لو lazy behavior بيخبي مفاجئة أو lifecycle مش واضح. لازم تحدد ownership وdisposal للـ inner، خصوصًا streams و scopes.

Patternالعقدالنية
Adapterمختلفترجمة لواجهة متوقعة
Decoratorنفس العقدإضافة behavior قابل للتركيب
Proxyنفس العقد عادةالتحكم في الوصول

#Composite و Bridge و FlyweightComposite, Bridge and Flyweight

Composite: ورقة ومجموعة بنفس العقد

خصم bundle محتاج يحسب عناصر مفردة وbundles متداخلة. Composite تخلي العميل يتعامل مع tree من غير if لكل نوع node.

public interface ICartNode { decimal Total { get; } }
public sealed record CartItem(decimal Price) : ICartNode
{
    public decimal Total => Price;                            // (1) leaf
}
public sealed class Bundle(IReadOnlyList<ICartNode> children) : ICartNode
{
    public decimal Total => children.Sum(c => c.Total);       // (2) composite
}
🧾 شرح الكود
  1. (1) leaf تحسب نفسها.
  2. (2) المجموعة تفوض للأطفال اللي ممكن يكونوا مجموعات كمان.

: expression trees فيها leaf nodes وcompound nodes؛ مش كل method مطلوبة لكل نوع. إمتى لأ؟ flat list كفاية، أو العقد بتجبر leaf على AddChild وهمية. امنع cycles لو بنية الإدخال متغيرة.

Bridge: محورين تغيير مستقلين

لو عندك report types وoutput formats، وراثة لكل توليفة تعمل انفجار classes. Abstraction هنا نوع التقرير، وimplementation طريقة الإخراج؛ Bridge تفصل المحورين عشان كل واحد يتغير باستقلال.

public interface IReportOutput { string Write(string content); }
public sealed class TextOutput : IReportOutput
{
    public string Write(string content) => content;
}
public abstract class Report(IReportOutput output)
{
    protected string Emit(string text) => output.Write(text);  // (1)
    public abstract string Render();
}
public sealed class OrderSummary(IReportOutput output) : Report(output)
{
    public override string Render() => Emit("Order summary");  // (2)
}
🧾 شرح الكود
  1. (1) report abstraction تفوض طريقة الإخراج.
  2. (2) report جديدة مش محتاجة subclass لكل output؛ output جديدة تنفذ العقد.

.NET: تصميم report services بformat adapters تطبيق طبيعي؛ Stream implementations لوحدها مش دليل Bridge من غير المحورين. إمتى لأ؟ لو محور تغيير واحد، Strategy أو interface واحدة غالبًا كفاية.

Flyweight: شارك الحالة الثابتة المتكررة

مليون product tile بتكرر نفس style الكبير؛ شارك style وخلي price وproductId بره كـ extrinsic state. قيس memory الأول.

public sealed record TileStyle(string Font, string Color);
public sealed class StylePool
{
    private readonly System.Collections.Concurrent.ConcurrentDictionary
        <string, TileStyle> _styles = new();
    public TileStyle Get(string color) =>
        _styles.GetOrAdd(color, c => new("Inter", c));          // (1)
}
public sealed record ProductTile(int ProductId, decimal Price, TileStyle Style); // (2)
🧾 شرح الكود
  1. (1) shared style immutable؛ factory ممكن تتنفذ أكتر من مرة تحت التنافس، فمفيش side effect.
  2. (2) الحالة الخاصة بكل منتج خارج الـ flyweight.

.NET: string interning مثال مشاركة قيم متطابقة، مش توصية تعمل Intern لكل input. إمتى لأ؟ بيانات معظمها فريدة أو pool غير محدودة من user input؛ lookup والاحتفاظ ممكن يستهلكوا أكتر مما وفرت.

Behavioral: السلوك والتواصل

#Strategy و StateStrategy and State

مثال خطوة بخطوة
  1. متجر بيحسب الشحن بثلاث طرق: استلام من الفرع، وتوصيل عادي، وتوصيل سريع. نفس السؤال له خوارزمية مختلفة حسب اختيار العميل.
  2. Strategy تضع كل طريقة حساب وراء عقد مشترك، وخدمة الطلب تختار الاستراتيجية المناسبة. إضافة طريقة جديدة لا تحتاج نسخ خدمة إنشاء الطلب كلها.
  3. State تعالج اختلافًا آخر: الطلب نفسه يغير سلوكه حسب حالته، مثل منع تعديل طلب بعد الشحن. اسأل هل الاختلاف طريقة تنفيذ مهمة، أم سلوك يتغير مع حالة الكائن؟

Strategy: اختار algorithm

طرق شحن مختلفة بتحسب نفس الناتج بطرق مختلفة. Strategy تفوض الحساب لخوارزمية قابلة للتبديل، والعميل أو configuration تختارها.

public interface IShippingFee { decimal Calculate(decimal weight); }
public sealed class FlatFee(decimal fee) : IShippingFee
{
    public decimal Calculate(decimal weight) => fee;
}
public sealed class WeightFee(decimal perUnit) : IShippingFee
{
    public decimal Calculate(decimal weight) => weight * perUnit;
}
public sealed class ShippingQuote(IShippingFee strategy)
{
    public decimal Get(decimal weight) => strategy.Calculate(weight); // (1)
}
🧾 شرح الكود
  1. (1) quote تعرف العقد بس؛ validate الوزن والrates عند الحدود.

: IComparer<T> يغير خوارزمية المقارنة. إمتى لأ؟ algorithm واحدة بسيطة أو صغيرة كافية؛ مش لازم class لكل expression.

State: السلوك يتغير مع الحالة

Order فيها Pay وCancel وسلوكهم مختلف حسب الحالة؛ if على status في كل method بتكرر القواعد. State تجمع سلوك كل حالة وتوضح الانتقالات.

public interface IOrderState { void Pay(Order order); }
public sealed class Pending : IOrderState
{
    public void Pay(Order order) => order.ChangeState(new Paid()); // (1)
}
public sealed class Paid : IOrderState
{
    public void Pay(Order order) { }                              // (2)
}
public sealed class Order
{
    private IOrderState _state = new Pending();
    internal void ChangeState(IOrderState state) => _state = state;
    public void Pay() => _state.Pay(this);                        // (3)
}
🧾 شرح الكود
  1. (1) Pending تختار الانتقال.
  2. (2) تكرار Pay هنا transition بس؛ المثال مابيعملش charge خارجي.
  3. (3) context تفوض للحالة؛ actual persistence وconcurrency مش ضمن المثال.

.NET: lifecycle معقدة في order processing ممكن تستخدم State objects أو state-machine library. إمتى لأ؟ حالات قليلة ثابتة؛ enum وswitch متحقق منها أوضح.

StrategyState
اختيار algorithm من بره عادةانتقال مرتبط بحالة context
strategies عادة مستقلة عن بعضstates ممكن تعرف الانتقالات لبعض

#Observer و MediatorObserver and Mediator

Observer: حدث ومشتركين

إضافة رد فعل على تأكيد order مش لازم تعدل publisher. Observer علاقة one-to-many: publisher تبلغ subscribers. C# event عادية sync وداخل process، مش ولا durable delivery.

public sealed class OrderEvents
{
    public event Action<int>? Confirmed;
    public void RaiseConfirmed(int id) => Confirmed?.Invoke(id); // (1)
}
// Subscription fragment:
Action<int> record = id => Console.WriteLine($"Confirmed {id}");
publisher.Confirmed += record;
// When the subscriber's lifetime ends:
publisher.Confirmed -= record;                               // (2)
🧾 شرح الكود
  1. (1) الاستدعاء يبلغ subscribers الحاليين؛ exception من handler ممكن توقف الباقي.
  2. (2) إلغاء الاشتراك مهم لو publisher أطول عمرًا عشان ماتمسكش subscriber في memory.

: events وIObservable<T>. إمتى لأ؟ لما محتاج نتيجة واحدة واضحة أو ترتيب وضمانات تنفيذ ؛ event عادية مش بديل .

Mediator: التنسيق في وسيط

لو cart وstock وnotifications عارفين بعض في كل اتجاه، Mediator هو object وسيط يستقبل الطلب وينسّق تفاعل المكونات بدل ما كل واحد يمسك references للباقي. ده مش معناه كل استدعاء method محتاج dispatcher.

public interface IStock { bool Reserve(int productId); }
public interface IOrderNotice { void Reserved(int productId); }
public sealed class CheckoutMediator(IStock stock, IOrderNotice notices)
{
    public bool RequestReservation(int id)
    {
        if (!stock.Reserve(id)) return false;                  // (1)
        notices.Reserved(id); return true;                     // (2)
    }
}
🧾 شرح الكود
  1. (1) صاحب الطلب يكلم mediator؛ المكونات مش بتحتاج references لبعض.
  2. (2) التنسيق مركزي، لكن مفيش rollback تلقائي لو notification فشلت.

.NET: MediatR request dispatch وpipeline behaviors تطبيق شائع لفكرة الوساطة؛ مش شرط تستخدمه. راجع الترخيص التجاري للإصدارات المعنية. إمتى لأ؟ لو بيضيف indirection بس، أو mediator بقى God object؛ direct handler injection بديل بسيط.

ObserverMediator
ينشر حقيقة لعدة مشتركينينظم تفاعل components أو يوجه request
الناشر مش مسؤول عن workflow كاملالوسيط يعرف قواعد التنسيق

#Command و Chain of ResponsibilityCommand and Chain of Responsibility

Command: الطلب object مستقلة

لو عايز تفصل زرار submit عن التنفيذ أو تعمل queue، اجمع نية الطلب وبياناته في command. Retry وundo محتاجين تصميم إضافي، مش مزايا تلقائية من record.

public sealed record ReserveProduct(Guid RequestId, int ProductId, int Quantity); // (1)
public interface IReservationStore
{
    Task ReserveOnceAsync(ReserveProduct command, CancellationToken ct);
}
public sealed class ReserveProductHandler(IReservationStore store)
{
    public Task Handle(ReserveProduct command, CancellationToken ct) =>
        store.ReserveOnceAsync(command, ct);                    // (2)
}
🧾 شرح الكود
  1. (1) data-only command سهلة تتسجل أو تتبعت؛ مفيش أو في payload.
  2. (2) handler تنفذ؛ store هنا عقد مطلوب يضمن بشكل atomic بالـ RequestId، مش implementation جاهزة.

: command handlers مثال لفصل command عن executor؛ ICommand في شكل تاني. إمتى لأ؟ استدعاء بسيط ومش محتاج queue أو history أو فصل زمني. Undo بيعني inverse operation أو compensation، وممكن مايكونش ممكن.

Chain of Responsibility: تعامل أو مرّر

موافقة على خصم بتتصعد حسب الحد. كل handler تعرف قدرتها واللي بعدها، مش لازم caller تعرف سلسلة كاملة.

public sealed class Approver(string name, decimal limit, Approver? next = null)
{
    public string Approve(decimal discount)
    {
        if (discount <= limit) return name;                    // (1)
        return next?.Approve(discount)
            ?? throw new InvalidOperationException("No approver"); // (2)
    }
}
// Usage fragment; discount has already been validated as nonnegative.
var chain = new Approver("Cashier", 5, new Approver("Manager", 15));
🧾 شرح الكود
  1. (1) أول handler مناسبة تمسك الطلب.
  2. (2) النهاية غير المعالجة failure واضحة؛ امنع cycles في chain.

.NET: وDelegatingHandler قرايب من الفكرة، مع before/after work وshort-circuit؛ مش لازم handler واحدة بس تشتغل في كل pipeline. إمتى لأ؟ لو ترتيب المعالجة خفي أو لازم كل validation تتجمع؛ اختار pipeline بعقد واضح بدل سلسلة تتوقف بالغلط.

#Template Method و Iterator و VisitorTemplate Method, Iterator and Visitor

Template Method: خطوات ثابتة وتفاصيل متغيرة

عمليات استيراد invoices ماشية بنفس التسلسل: parse ثم validate ثم save. حط الهيكل في base وسيب hooks للتفاصيل.

public abstract class InvoiceImport
{
    public void Run(string input)
    {
        decimal total = Parse(input);                         // (1)
        ArgumentOutOfRangeException.ThrowIfNegative(total);
        Save(total);
    }
    protected abstract decimal Parse(string input);
    protected abstract void Save(decimal total);               // (2)
}
🧾 شرح الكود
  1. (1) Run مش virtual عشان تحافظ على التسلسل.
  2. (2) subclasses توفر الخطوات، والـ base تفرض الفحص.

: BackgroundService فيها lifecycle في base وخطوة ExecuteAsync متغيرة. إمتى لأ؟ لو الخطوات بتتبدل مستقلًا؛ composition مع Strategy أريح من inheritance أعمق.

Iterator: امشي من غير ما تكشف التخزين

المستهلك محتاج عناصر invoice، مش يعرف array ولا tree. Iterator تفصل traversal عن تمثيل collection؛ C# بتوفرها من غير كتابة classes يدوية غالبًا.

static IEnumerable<int> ProductIds()
{
    yield return 10;                                         // (1)
    yield return 20;
}
// Usage: foreach (int id in ProductIds()) Console.WriteLine(id); // (2)
🧾 شرح الكود
  1. (1) compiler تعمل state machine؛ إنتاج العنصر وقت MoveNext مش وقت استدعاء method بس.
  2. (2) foreach تستخدم وIEnumerator وتعمل disposal المناسب.

.NET: IEnumerable<T> وIAsyncEnumerable<T>. إمتى لأ؟ لو محتاج random access متكرر أو snapshot ثابتة؛ لما ده المقصود. إعادة enumeration ممكن تعيد أو الحساب.

Visitor: عمليات جديدة على أنواع ثابتة

عندك بأنواع مستقرة، وعايز تضيف printing وvalidation من غير ما تحط كل عملية جوه كل node. Visitor تنقل العملية لobject خارجية، وAccept تعمل double dispatch.

public interface IRuleVisitor
{
    string Visit(MinimumTotal rule);
    string Visit(AllRules rule);
}
public interface IRule { string Accept(IRuleVisitor visitor); }
public sealed record MinimumTotal(decimal Amount) : IRule
{
    public string Accept(IRuleVisitor visitor) => visitor.Visit(this); // (1)
}
public sealed record AllRules(IRule Left, IRule Right) : IRule
{
    public string Accept(IRuleVisitor visitor) => visitor.Visit(this);
}
public sealed class RulePrinter : IRuleVisitor
{
    public string Visit(MinimumTotal rule) => $"Total >= {rule.Amount}";
    public string Visit(AllRules rule) =>
        $"({rule.Left.Accept(this)} AND {rule.Right.Accept(this)})";    // (2)
}
🧾 شرح الكود
  1. (1) النوع الفعلي يختار overload المناسبة داخل Accept.
  2. (2) عملية الطباعة تزور الأطفال؛ visitor جديدة تضيف operation جديدة.

.NET: ExpressionVisitor تزور أو تعيد كتابة expression trees. إمتى لأ؟ node types بتزيد باستمرار، لأن كل visitor لازم تتعدل. Type-pattern switch في C# بديل أصغر لحالات بسيطة؛ مش ضمان compile-time exhaustiveness لكل class hierarchy.

#Memento و InterpreterBonusMemento and Interpreter

اختياري بس بيفرق: مش هيتسأل فيه في كل انترفيو، بس معرفته بتعمّق فهمك وبتميّزك عن غيرك.

Memento: رجوع لحالة من غير كشف تفاصيلها

محرر order draft محتاج undo قبل الحفظ. Memento تخلي object نفسها تعمل snapshot وتسترجعها، والـ caretaker تحتفظ بالنسخة من غير تعديل محتواها.

public sealed class OrderDraft
{
    private sealed record Snapshot(Guid Owner, string Note);
    private readonly Guid _owner = Guid.NewGuid();
    public string Note { get; set; } = "";
    public object Capture() => new Snapshot(_owner, Note);       // (1)
    public void Restore(object token)
    {
        if (token is not Snapshot s || s.Owner != _owner)
            throw new ArgumentException("Wrong snapshot", nameof(token));
        Note = s.Note;                                         // (2)
    }
}
🧾 شرح الكود
  1. (1) snapshot نوعها private ومحتواها ؛ caller تمسك opaque .
  2. (2) restore تتحقق إنها snapshot لنفس draft.

: undo stack في محرر مبني بـ C# تطبيق طبيعي. مش كل event-sourcing snapshot Memento؛ الهدف هناك تسريع replay. إمتى لأ؟ undo لpayment خارجي أو snapshot ضخمة؛ تحتاج compensation أو diffs، وحدد retention لتجنب استهلاك memory.

Interpreter: grammar صغيرة وقواعد تقييم

لو قواعد الخصم فيها TotalAtLeast وAND، interpreter تمثل الجملة كـ tree وكل node تعرف تقيّم نفسها. ده evaluator بعد parsing؛ parsing نفسها مش معروضة.

public interface IDiscountExpression { bool Evaluate(decimal total); }
public sealed record TotalAtLeast(decimal Minimum) : IDiscountExpression
{
    public bool Evaluate(decimal total) => total >= Minimum;    // (1)
}
public sealed record Both(IDiscountExpression Left, IDiscountExpression Right)
    : IDiscountExpression
{
    public bool Evaluate(decimal total) =>
        Left.Evaluate(total) && Right.Evaluate(total);          // (2)
}
🧾 شرح الكود
  1. (1) terminal expression بمعنى محدد.
  2. (2) nonterminal تركب expressions أصغر.

.NET: rule evaluators وexpression-based engines تطبيقات قريبة؛ Regex مثال language evaluation، مش ادعاء إن تنفيذها الداخلي نفس class tree. إمتى لأ؟ grammar كبيرة أو input غير موثوق من غير حدود عمق وحجم؛ استخدم parser مناسبة، وماتعملش dynamic code execution لقواعد المستخدم.

Patterns إضافية و Anti-patterns

#Repository و Unit of Work و Specification و Result و Options و Null Object و PipelineRepository, Unit of Work, Specification, Result, Options, Null Object and Pipeline

Repository و Unit of Work

Repository تعرض وصول شبه collection لـ aggregates، وUnit of Work تجمع تغييرات في commit. بتقدم tracking ووحدة حفظ، وDbSet وصول repository-like. Generic wrapper مش شرط تضيف قيمة؛ عقد حسب ممكن تحمي حدود domain.

public interface IOrderRepository
{
    Task<Order?> GetAsync(Guid id, CancellationToken ct);
    void Add(Order order);
}
// Handler fragment: repository and db share the same scoped DbContext.
orders.Add(order);                                           // (1)
await db.SaveChangesAsync(ct);                               // (2)
🧾 شرح الكود
  1. (1) Add تسجل التغيير من غير commit داخلي منفصل.
  2. (2) commit واحدة للتغييرات المتتبعة؛ كذا SaveChanges تحتاج صريحة لو عايز atomicity مشتركة.

إمتى لأ؟ repository بتكرر EF أو ترجع وبتوعد بعزل provider كامل. UoW مش distributed transaction، وoutbound مش جزء من SaveChanges.

Specification و Result

Specification تسمي business predicate قابلة لإعادة الاستخدام؛ خليها expression لو provider محتاج يترجمها. Result تمثل نجاح أو فشل متوقع كقيمة بعقد واضح بدل exceptions لكل رفض coupon.

public sealed record OrderRow(DateTime DueAt, bool IsPaid);
public sealed class OverdueOrders(DateTime now)
{
    public System.Linq.Expressions.Expression<Func<OrderRow, bool>> Criteria =>
        o => !o.IsPaid && o.DueAt < now;                      // (1)
}
public sealed class CouponResult
{
    public bool IsSuccess { get; }
    public string? Error { get; }
    private CouponResult(bool success, string? error)
        => (IsSuccess, Error) = (success, error);
    public static CouponResult Applied() => new(true, null);
    public static CouponResult Expired() => new(false, "coupon.expired"); // (2)
}
🧾 شرح الكود
  1. (1) وقت الفحص input ثابت؛ EF تقدر تترجم predicate لو mapping مدعومة. ماتعملش Compile قبل Where على IQueryable.
  2. (2) constructor مقفولة تمنع حالات زي success مع error. الـ boundary تترجم الفشل لـ HTTP مناسب؛ مش كل فشل 500.

إمتى لأ؟ Specification لفلتر صغير عمره ما بيتكرر، أو Result بتخلي callers يتجاهلوا failures. Infrastructure exceptions غير المتوقعة ماينفعش تتحول كلها لcoupon.expired.

Options و Null Object و Pipeline

Options تجمع configuration مترابطة في type وتتحقق منها. Null Object تنفيذ مقصود بلا تأثير بدل null checks. Pipeline خطوات قابلة للتركيب بعقد وترتيب واضحين، وغالبًا تتقاطع مع Chain وDecorator.

public sealed class InvoiceOptions
{
    public int MaxLines { get; set; } = 100;
}
// ASP.NET Core composition-root fragment:
builder.Services.AddOptions<InvoiceOptions>()
    .BindConfiguration("Invoices")
    .Validate(o => o.MaxLines > 0, "MaxLines must be positive")
    .ValidateOnStart();                                      // (1)

// Null Object from Microsoft.Extensions.Logging.Abstractions:
var log = Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance; // (2)

// Small pipeline: validation before rendering.
Func<decimal, string> render = total => $"Total: {total}";
Func<decimal, string> pipeline = total =>
{
    ArgumentOutOfRangeException.ThrowIfNegative(total);       // (3)
    return render(total);
};
🧾 شرح الكود
  1. (1) تفشل startup لو config مش صالحة؛ استهلك IOptions أو IOptionsSnapshot أو IOptionsMonitor حسب lifetime وreload المطلوبين.
  2. (2) log requests مقصود إنها no-op؛ مش تخفي فشل service أساسية زي payment.
  3. (3) الترتيب جزء من العقد؛ الخطوة دي توقف pipeline عند input غلط.

إمتى لأ؟ Options لثابت داخلي مش config، أو Null Object تخبي dependency ناقصة، أو pipeline ديناميكية لخطوتين ثابتتين. Options documentation.

Anti-patternعلامته وعلاجه
Pattern obsessionclasses بتزيد من غير مشكلة تغيير؛ ارجع لأبسط تدفق واضح
Service locatorIServiceProvider جوه business logic تخبي dependencies؛ استخدم constructor injection
God objectManager تعرف الحساب والتخزين وكل workflows؛ افصل أسباب التغيير
Leaky abstractionrepository توعد بعزل EF لكن تعرض تفاصيله من غير قصد؛ اعترف بالعقد الحقيقي أو ضيقه
Singleton mutable stateمعلومات مستخدمين مختلطة بين requests؛ استخدم lifetime مناسبة
Shotgun surgeryإضافة نوع واحد تعدل عشر switch blocks؛ دور على variation point مشتركة

أسئلة الانترفيو

راجع فهمك

ليه الاختيار ده مناسب؟ إمتى ممكن ماينفعش؟ وإيه اللي هيتغير لو الحمل زاد أو ظهر فشل؟

15.1What is a design pattern, and how do you decide whether to use one?Junior

حل مسمى لمشكلة متكررة مع سياق وتكلفة. ابدأ بالمشكلة، مش اسم pattern. Creational إنشاء، Structural تركيب، Behavioral سلوك وتواصل.

الإجابة النموذجية (English)

I treat a pattern as a named design trade-off, not a requirement to add classes. The groups are creational, structural and behavioral: construction, composition and collaboration. I first describe the repeated change or coupling problem, then choose the smallest design that addresses it. A pattern's name helps the team communicate, but I still explain ownership, failure behavior and when the design should be simplified. More patterns do not automatically make a system more maintainable.

15.2How does a DI singleton differ from a classic Singleton?Junior

الكلاسيكية private constructor وstatic access. بتوفر instance مشتركة لكل provider مع dependency صريحة، مش تمنع new عالميًا ولا تعمل distributed singleton.

الإجابة النموذجية (English)

A classic Singleton controls construction and exposes a global access point, commonly through a private constructor and Lazy instance. A singleton is a container lifetime: consumers explicitly request the service, and one provider supplies a shared instance. It is easier to substitute in tests but does not prohibit other construction or create one instance across servers. I still require thread-safe behavior and avoid capturing scoped dependencies. Thread-safe initialization alone does not protect mutable service state.

15.3When is Builder preferable to a constructor?Junior

خيارات كتير مترابطة وخطوات إنشاء وvalidation في النهاية. object بسيطة constructor أنسب. Build ترجع snapshot مستقلة بدل mutable object متسربة.

الإجابة النموذجية (English)

I use a builder when construction has several optional or dependent steps and the result benefits from validation at completion. It can make report definitions or host configuration readable without an unwieldy constructor. I make Build return a valid independent product so later builder changes cannot mutate earlier results. For a simple object with a few required values, a constructor or initializer is usually clearer. Fluent chaining is helpful syntax, but not a reason by itself to introduce a builder.

15.4What problem does Adapter solve?Junior

واجهة خارجية مش مطابقة لعقد التطبيق. لف وترجم الأنواع والوحدات والأخطاء. Facade تبسط subsystem، مش نفس النية.

الإجابة النموذجية (English)

Adapter lets an incompatible interface satisfy the contract a client expects. For example, an adapter can translate a decimal amount into a legacy payment 's minor units and map its response into the application's result type. I keep external types and semantics at that boundary. It can also support an anti-corruption layer when concepts differ. I avoid empty adapters that add no translation or , and I do not confuse adaptation with a facade merely simplifying a subsystem.

15.5How does Strategy reduce repeated branching?Junior

كل algorithm ورا عقد واحد، والاختيار بره الحساب. شحن flat وweight بدل switch بتكبر. ممكن تكفي لو الاختلاف صغير.

الإجابة النموذجية (English)

Strategy separates interchangeable algorithms behind one contract, so a shipping quote can use a flat fee or weight-based fee without every rule in a growing switch. The client or configuration chooses the strategy. I use it when variants change independently or recur across workflows. I keep the contract's inputs and guarantees consistent across implementations. If the variation is a tiny expression or the set is small and stable, a or straightforward switch can be easier to maintain.

15.6How do Factory Method, Abstract Factory and a simple factory differ?Mid

Factory Method تعتمد على override لاختيار منتج. Abstract Factory عيلة متوافقة من منتجات. Simple factory اختيار مركزي غالبًا switch؛ مش لازم نسميه Factory Method.

الإجابة النموذجية (English)

In the classic Factory Method, a base workflow calls an overridable creation method and a subclass chooses the concrete product. Abstract Factory is an object that creates a compatible family, such as database connections and commands. A simple factory centralizes selection, often with a switch, without requiring that inheritance structure. I distinguish them because many called factories are not GoF Factory Method. I prefer direct injection when product creation does not need its own variation mechanism.

15.7What is the main danger of Prototype copying in C#?Mid

record with shallow، list بتفضل مشتركة. انسخ mutable children حسب الحاجة، وماتنسخش identity أو connection ownership بالغلط.

الإجابة النموذجية (English)

I check copy semantics first. A record with expression creates a shallow copy, so a referenced mutable list is still shared unless I copy it separately. Copying that list is not automatically a deep copy of mutable elements either. I define which parts should be shared, which should be independent, and whether identity must be regenerated. values make this easier. I avoid cloning resource owners such as database connections, and I do not assume ICloneable specifies deep-copy behavior.

15.8How do Decorator, Proxy and Adapter differ?Mid

Adapter تغير العقد للتوافق. Decorator نفس العقد وتضيف مسؤولية. Proxy تتحكم في الوصول. ممكن نفس شكل wrapper بس النية مختلفة.

الإجابة النموذجية (English)

Adapter changes the exposed interface to match a client's expectation. Decorator preserves the contract and adds composable responsibilities, such as logging or compression. Proxy generally preserves the contract while controlling access, for example by delaying creation or representing a remote object. Their code can look similar, so I explain intent rather than identifying them from a wrapper alone. I also define lifetime, disposal and failure behavior, because stacking wrappers can change ordering and hide network or storage costs.

15.9How does Facade differ from a God object?Mid

Facade باب بسيط لمجموعة مترابطة، مش تجمع كل السياسات. خليك use-case focused، وسيب domain rules في مكانها. Facade مش تلقائي.

الإجابة النموذجية (English)

A facade simplifies a cohesive subsystem for a particular client need, while a God object accumulates unrelated policies and workflows. I keep the facade focused on orchestration and leave domain with their proper owners. If every feature depends on it and every change edits it, that is a warning sign. I also make its failure contract explicit: wrapping several calls in one method does not make them atomic or provide rollback across external systems.

15.10How do Strategy and State differ?Mid

Strategy اختيار طريقة، عادة من بره. State سلوك context حسب lifecycle وانتقالات. State class ممكن تختار الحالة اللي بعدها؛ Strategy غالبًا مستقلة.

الإجابة النموذجية (English)

Both behavior, but they model different reasons for change. Strategy selects an interchangeable algorithm, usually chosen by a client or configuration, such as a shipping-fee calculation. State changes a context's behavior as its lifecycle changes, and states may choose transitions to other states. I use State when transition rules are otherwise repeated across operations. For a small stable lifecycle, an enum with explicit transition validation can be simpler. Neither pattern automatically handles persistence or concurrent transitions.

15.11How do Observer and Mediator differ?Mid

Observer نشر واحد لكتير من غير publisher تعرف workflow. Mediator تنسيق أو request routing. C# events مش async durable messaging، والوسيط مش لازم library.

الإجابة النموذجية (English)

Observer publishes notifications to interested subscribers without the publisher coordinating their complete workflow. Mediator centralizes collaboration or request routing so components do not directly depend on each other. A C# event is a familiar Observer mechanism, but it is normally synchronous and not durable. MediatR is one mediation implementation, not a requirement. I choose explicit calls when there is one clear collaborator, and use notifications carefully when ordering, failures and subscriber lifetimes need well-defined behavior.

15.12What do Command and Chain of Responsibility each contribute?Mid

Command تمثل الطلب وبياناته للفصل والـ queue. Chain توزع المعالجة بالترتيب وتسمح بالتمرير أو التوقف. Retry وundo وatomicity محتاجين تصميم منفصل.

الإجابة النموذجية (English)

Command packages an operation's intent and inputs separately from the sender, making queuing or recording possible. Chain of Responsibility routes a request through handlers that can handle it, forward it or stop processing. They can work together, but neither guarantees safe retries, undo or atomicity. I define ordering and unhandled-request behavior for the chain, and an policy for replayable commands. For trivial direct calls, those abstractions may add more than value.

15.13When would you use Template Method rather than Strategy?Mid

Template تحافظ على skeleton ثابتة في base مع hooks. Strategy تفصل algorithm بالـ composition. لو steps مستقلة أو inheritance بتطول، Strategy أو pipeline أوضح.

الإجابة النموذجية (English)

I use Template Method when several implementations share an algorithm's fixed sequence and only specific steps should vary. The base class can enforce validation order while subclasses implement parsing or persistence hooks. Strategy uses composition and is easier when whole algorithms or independent steps must be selected dynamically. I avoid deep template hierarchies where hooks interact unpredictably. A simple function or an explicit pipeline can be clearer if there is little stable behavior to inherit.

15.14When are Composite, Bridge and Flyweight worth their complexity?Senior

Composite tree بعمليات مشتركة، Bridge محورين تغيير مستقلين، Flyweight حالة ثابتة متكررة بكثرة. حدد structure وvariation وقياس memory بدل الاختيار بالاسم.

الإجابة النموذجية (English)

I use Composite for a genuine tree where leaves and groups share meaningful operations. Bridge pays off when two independent axes, such as report type and output format, would otherwise create a subclass for every combination. Flyweight helps when many objects repeat substantial state and measurements show a memory benefit. I avoid fake leaf operations, speculative variation axes and unbounded pools. Each pattern solves a specific structural problem; none is a general way to make ordinary object models better.

15.15Why does Visitor favor stable node types but changing operations?Senior

كل operation visitor جديدة، لكن node جديدة تحتاج Visit في كل visitors. Accept تعمل double dispatch. Pattern matching بديل صغير؛ مش وعد exhaustiveness لكل hierarchy.

الإجابة النموذجية (English)

Visitor moves operations into separate visitor objects, with Accept selecting the correct node-specific operation through double dispatch. Adding printing or validation then means another visitor, while the node model stays stable. Adding a node type is expensive because the visitor contract and existing visitors must change. That trade-off suits expression trees better than a rapidly evolving domain hierarchy. I consider C# pattern matching for smaller cases, while remembering that arbitrary class hierarchies are not automatically checked exhaustively.

15.16What are the limits of Iterator, Memento and Interpreter?Senior

Iterator lazy وممكن تعيد شغل عند enumeration. Memento undo محلية مش external rollback. Interpreter grammar صغيرة بحدود، مش code execution لقواعد user.

الإجابة النموذجية (English)

I check the hidden cost each introduces. Iterator hides representation, but lazy traversal can repeat on re-enumeration and may hold resources open. Memento supports local state restoration, not reversal of an external payment, and retained snapshots need memory limits. Interpreter makes a small language explicit, but a large grammar or unbounded input needs stronger parsing and resource controls. I do not treat their names as guarantees of streaming efficiency, transactional undo or safe execution of arbitrary user rules.

15.17Would you add Repository and Unit of Work over EF Core?Senior

مش تلقائي. وDbSet بيقدموا الجزء الكبير. -specific repository ممكن تعزل domain؛ generic wrapper بتخبي projections وميزات ممكن تضر.

الإجابة النموذجية (English)

Not automatically. already coordinates tracked changes and saving, and DbSet provides repository-like access. A generic wrapper can hide projections and useful behavior without creating a meaningful boundary. I add an -specific repository when the domain needs a focused persistence contract or loading policy. I keep its methods aligned with the aggregate and share one unit of work. I also avoid claiming provider independence while exposing or provider-specific assumptions through the abstraction.

15.18How do you keep enterprise patterns from becoming ceremony?Senior

Specification لقاعدة متكررة، Result لفشل متوقع بيتعالج، Options لconfig، Null Object لـ no-op مقصودة، وPipeline لترتيب steps حقيقي. مفيش فايدة لو المشكلة مش موجودة.

الإجابة النموذجية (English)

I ask what concrete contract each pattern improves. A specification should name a reused predicate, a Result should make expected failure handling explicit, Options should group validated configuration, and a Null Object should represent an intentional safe no-op. A pipeline needs meaningful ordering and composition. I remove abstractions that only forward calls, hide missing dependencies or let callers ignore errors. I also keep infrastructure failures observable instead of converting every exception into an ordinary business rejection.

اختبر نفسك

إجابات الاختبار وعلامات إكمال المحاور بتتحفظ على المتصفح ده، ومش بتتنقل لأجهزة تانية.

النتيجة: 0 / 0

Which statement best describes classic GoF Factory Method?

Simple factory مختلفة عن شكل القائم على override.

Which pattern creates a compatible family such as connections and commands?

العيلة المتوافقة هي محور Abstract Factory.

A record with expression copies an object containing a mutable List. What happens by default?

with shallow؛ انسخ mutable children لما تحتاج استقلال.

Which pattern adds compression around an existing Stream while preserving the Stream contract?

مسؤولية إضافية مع نفس العقد.

A report type hierarchy delegates output format to an independent implementation hierarchy. Which pattern?

محورين تغيير مستقلين من غير class لكل توليفة.

Which state belongs outside a shared Flyweight style?

السعر extrinsic ومختلف لكل منتج.

The context's lifecycle chooses transitions among behavior objects. Which pattern is most direct?

State تربط السلوك والانتقال بالحالة.

Which limitation applies to an ordinary C# event?

event عادية مش ولا manager.

Visitor is usually easiest when which part is stable?

node جديدة بتفرض تعديل visitor contract والـ implementations.

What does an in-memory Memento normally provide?

إرجاع state محلية مختلف عن تعويض side effect خارجي.

Why use a Result for an expired coupon?

الـ Result عقد فشل واضح، مش آلية إخفاء errors.

What should happen if pattern usage increases files without solving a change or coupling problem?

الـ pattern وسيلة لحل مشكلة، مش هدف في حد ذاتها.