🐍 شرح Python Design Patterns

كل مثال فيه الكود + الشرح + مين بينادي مين + الفلو الكامل + نوع الباترن

📚 محتويات الملف

1

Lecture 1 – Python Foundations

Clean Code, Modularity, Type Hints, Context Manager, Decorator

Example 1 — Clean Code Refactoring Foundation
اعمل function اسمها square_positive بتاخد رقم: لو الرقم موجب ترجع مربعه، لو لأ ترجع None. الكود يكون نظيف وواضح مع type hints.
def square_positive(number: float):
    """Return square of number if number > 0, otherwise None."""
    if number <= 0:
        return None
    return number * number
def demo():
    tests = [5, 0, -3, 2.5]
    for t in tests:
        print(f"Input={t:>4} -> Output={square_positive(t)}")
if __name__ == "__main__":
    demo()

الكود ده مثال على Clean Code — يعني كتابة كود واضح وسهل القراءة.

  • square_positive(number): دالة بتاخد رقم. لو الرقم صفر أو أقل → ترجع None. لو أكبر من صفر → ترجع مربعه.
  • demo(): دالة بتجرب الدالة على أرقام مختلفة وتطبع النتيجة.
  • استخدمنا type hint (float) عشان نوضح إن الدالة بتاخد عدد عشري.
  • استخدمنا docstring لشرح الدالة — ده من أساسيات الكود النظيف.
__main__ demo() square_positive(t) print نتيجة كل رقم
1البرنامج بيبدأ من __main__ وينادي demo()
2demo() بتلف على القايمة [5, 0, -3, 2.5]
3كل رقم بيتبعت لـ square_positive()
4لو الرقم > 0 → ترجع number * number | لو لأ → ترجع None
5demo() بتطبع النتيجة لكل رقم
Example 2 — Modular Program Structure Foundation
اكتب برنامج modular بيقرأ نص من ملف، بينظفه، وبيعد الكلمات. استخدم دوال منفصلة: read_text, clean_text, count_words.
from pathlib import Path
def read_text(path: str) -> str:
    """Read all text from a file."""
    return Path(path).read_text(encoding="utf-8")
def clean_text(text: str) -> str:
    """Normalize whitespace and lowercase the text."""
    normalized = " ".join(text.split())
    return normalized.lower()
def count_words(text: str) -> int:
    """Count words in cleaned text."""
    return len(text.split())
def main():
    sample_path = "lecture1_sample.txt"
    Path(sample_path).write_text("  Hello   Python   Students!\nWelcome to   ELC 423.  ", encoding="utf-8")
    raw = read_text(sample_path)
    cleaned = clean_text(raw)
    n_words = count_words(cleaned)
    print("RAW:", raw)
    print("CLEANED:", cleaned)
    print("WORD COUNT:", n_words)
if __name__ == "__main__":
    main()

المثال ده بيطبق مبدأ Separation of Concerns — كل دالة مسؤولة عن حاجة واحدة بس.

  • read_text(path): بتقرأ الملف وترجع محتواه كنص.
  • clean_text(text): بتشيل المسافات الزيادة وتحوّل الكل لـ lowercase.
  • count_words(text): بتعد الكلمات عن طريق split() ثم len().
  • main(): بتنسق الكل — بتبني الملف → تقراه → تنظفه → تعد الكلمات → تطبع.
  • استخدمنا pathlib بدل open() القديمة عشان أوضح وأنظف.
__main__ main() read_text() clean_text() count_words() print النتائج
1main() بتكتب ملف تجريبي على الديسك
2read_text() بتفتح الملف وترجع النص الخام
3clean_text() بتاخد النص → split() لشيل المسافات → join() → lower()
4count_words() بتعمل split() على النص المنظف وترجع len()
5main() بتطبع RAW - CLEANED - WORD COUNT
Example 3 — Type Hints + safe_divide Foundation
اكتب دالة safe_divide(a, b) بتقسم a على b. لو b == 0 ترجع None. استخدم type hints.
from typing import Optional
def safe_divide(a: float, b: float) -> Optional[float]:
    """Return a/b if b != 0, otherwise None."""
    if b == 0:
        return None
    return a / b
def main():
    tests = [(10, 2), (5, 0), (7.5, 2.5)]
    for a, b in tests:
        result = safe_divide(a, b)
        print(f"{a} / {b} = {result}")

Type Hints بتساعدك تعرف الدالة بتاخد إيه وبترجع إيه من غير ما تشغّل الكود.

  • Optional[float]: معناه إن الدالة ممكن ترجع float أو None.
  • الـ guard clause if b == 0 بيمنع ZeroDivisionError قبل ما يحصل.
  • main() بتجرب 3 حالات: قسمة عادية، قسمة على صفر، وعدد عشري.
__main__ main() safe_divide(a, b) print النتيجة
1main() بتعمل loop على قايمة الأزواج
2كل زوج (a,b) بيتبعت لـ safe_divide()
3لو b == 0 → ترجع None فوراً
4لو b != 0 → بتحسب a/b وترجعها
5main() بتطبع كل نتيجة
Example 4 — Context Manager (with) Foundation
اكتب برنامج يكتب أسماء طلاب في ملف ويقراهم تاني باستخدام context manager (with).
def write_students(path: str, students: list[str]) -> None:
    with open(path, "w", encoding="utf-8") as f:
        for name in students:
            f.write(name + "\n")
def read_students(path: str) -> list[str]:
    with open(path, "r", encoding="utf-8") as f:
        return [line.strip() for line in f if line.strip()]
def main():
    file_path = "students.txt"
    students = ["Ahmed", "Mona", "Hany", "Sara"]
    write_students(file_path, students)
    loaded = read_students(file_path)
    print("Saved students:", students)
    print("Loaded students:", loaded)

الـ Context Manager (with) بيضمن إن الملف هيتقفل تلقائياً حتى لو في error.

  • write_students(): بتفتح الملف للكتابة → تكتب كل اسم في سطر → الملف يتقفل تلقائي.
  • read_students(): بتفتح الملف للقراءة → تقرا السطور → بتشيل المسافات الزيادة والسطور الفاضية.
  • الـ list comprehension [line.strip() for line in f if line.strip()] بتقرا وتنظف في خطوة واحدة.
main() write_students() open() + write()| main() read_students() open() + strip()
1main() بتعرّف القايمة والمسار
2write_students() بتفتح الملف بـ with → تكتب كل اسم → يتقفل تلقائي
3read_students() بتفتح الملف بـ with → تقرا السطور وتنظفها
4main() بتطبع القايمة الأصلية والمقروءة من الملف
Example 5 — Decorator (timer) Foundation
اعمل decorator اسمه timer بيقيس وقت تنفيذ أي دالة، وطبقه على دالة بتحسب مجموع من 1 لـ n.
import time
from functools import wraps
def timer(func):
    """Decorator to measure execution time."""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        value = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"[TIMER] {func.__name__} took {(end - start):.6f} seconds")
        return value
    return wrapper
@timer
def sum_to_n(n: int) -> int:
    return sum(range(1, n + 1))
def main():
    result = sum_to_n(5_000_000)
    print("Result:", result)

الـ Decorator ده بيضيف سلوك جديد (قياس الوقت) على أي دالة من غير ما تعدّلها.

  • timer(func): decorator بياخد الدالة الأصلية كـ argument.
  • wrapper(*args, **kwargs): الدالة الغلافة بتسجل الوقت قبل وبعد تنفيذ func.
  • @wraps(func): بيحافظ على اسم الدالة الأصلية بدل ما يبقى "wrapper".
  • @timer قبل sum_to_n: معناه إن sum_to_n = timer(sum_to_n) تلقائياً.
  • perf_counter(): أدق من time.time() لقياس الأداء.
main() sum_to_n(5M)→ [decorator intercepts] → wrapper() func() [الأصلية] print الوقت
1عند تعريف sum_to_n بـ @timer → Python بينفذ: sum_to_n = timer(sum_to_n)
2main() بتنادي sum_to_n(5M) → في الحقيقة بتنادي wrapper(5M)
3wrapper بيسجل start time
4wrapper بينادي الدالة الأصلية sum() وتحسب المجموع
5wrapper بيسجل end time ويطبع الفارق
6main() بتطبع النتيجة

2

Lecture 2 – SOLID Principles

SRP, OCP, LSP, ISP, DIP

Example 1 — SRP: Single Responsibility Principle SOLID – SRP
افصل مسؤولية حساب المتوسط عن مسؤولية الطباعة. اعمل StudentGrades للبيانات، وReportPrinter للطباعة.
class StudentGrades:
    def __init__(self, student_name: str, grades: list[float]):
        self.student_name = student_name
        self.grades = grades
    def average(self) -> float:
        if not self.grades: return 0.0
        return sum(self.grades) / len(self.grades)
class ReportPrinter:
    def print_report(self, student: StudentGrades) -> None:
        avg = student.average()
        print(f"Student: {student.student_name}")
        print(f"Average: {avg:.2f}")
def main():
    student = StudentGrades("Ahmed", [90, 85, 92, 88, 95])
    printer = ReportPrinter()
    printer.print_report(student)

SRP: كل class مسؤول عن حاجة واحدة بس — لو عندك سبب واحد للتغيير = تمام.

  • StudentGrades: مسؤوليتها الوحيدة = تخزين الدرجات وحساب المتوسط. مش بتطبع حاجة.
  • ReportPrinter: مسؤوليتها الوحيدة = طباعة التقرير. مش بتحسب حاجة.
  • الفايدة: لو عايز تغير طريقة العرض → تعدل ReportPrinter بس. لو عايز تضيف GPA → تعدل StudentGrades بس.
main() ReportPrinter.print_report(student) student.average() print النتيجة
1main() بتنشئ StudentGrades بالاسم والدرجات
2main() بتنشئ ReportPrinter وتناديه بالـ student
3print_report() بتنادي student.average() للحساب
4average() بتحسب وترجع المتوسط
5print_report() بتطبع الاسم والمتوسط
Example 2 — OCP: Open/Closed Principle SOLID – OCP
اعمل نظام دفع: PaymentProcessor يشتغل مع أي طريقة دفع جديدة من غير ما تعدّل الـ processor نفسه.
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount: float) -> None: pass
class CreditCardPayment(PaymentMethod):
    def pay(self, amount: float) -> None:
        print(f"[Credit Card] Paid {amount:.2f} EGP")
class CashPayment(PaymentMethod):
    def pay(self, amount: float) -> None:
        print(f"[Cash] Paid {amount:.2f} EGP")
class BankTransferPayment(PaymentMethod):   # مضاف بدون تعديل PaymentProcessor
    def pay(self, amount: float) -> None:
        print(f"[Bank Transfer] Paid {amount:.2f} EGP")
class PaymentProcessor:
    def __init__(self, method: PaymentMethod):
        self.method = method
    def process(self, amount: float) -> None:
        self.method.pay(amount)   # Polymorphism — no if/else!
def main():
    for method in (CreditCardPayment(), CashPayment(), BankTransferPayment()):
        PaymentProcessor(method).process(250.0)

OCP: الكود مفتوح للإضافة (Open for extension) لكن مغلق للتعديل (Closed for modification).

  • PaymentMethod (ABC): abstract class بتحدد العقد — كل طريقة دفع لازم عندها pay().
  • PaymentProcessor: شغّالة مع أي PaymentMethod من غير if/else — ده الـ Polymorphism.
  • عايز تضيف Bitcoin؟ → اعمل class جديد يورث من PaymentMethod بس. مش هتعدل PaymentProcessor خالص.
main() PaymentProcessor(method) process(250) method.pay(250)→ [polymorphism] CreditCard / Cash / BankTransfer
1main() بتعمل loop على الـ 3 طرق دفع
2كل iteration بتعمل PaymentProcessor جديد بالطريقة المختارة
3process() بتنادي method.pay() بدون معرفة النوع
4Python بنفسه بيختار الـ implementation الصح (Polymorphism)
Example 3 — LSP: Liskov Substitution Principle SOLID – LSP
اعمل Bird base class. اعمل Sparrow وPenguin بيرثوا منها. الدالة make_bird_move() لازم تشتغل مع أي bird بدون افتراضات.
class Bird:
    def move(self) -> None:
        raise NotImplementedError
class Sparrow(Bird):
    def move(self) -> None:
        print("Sparrow flies in the sky.")
class Penguin(Bird):
    def move(self) -> None:
        print("Penguin swims in the water.")
def make_bird_move(bird: Bird) -> None:
    bird.move()   # works for ANY Bird subclass
def main():
    for b in [Sparrow(), Penguin()]:
        make_bird_move(b)

LSP: أي subclass لازم تقدر تحل محل الـ base class من غير ما البرنامج يتعطل.

  • الخطأ الكلاسيكي: لو Bird كان فيها fly() → Penguin مش بتطير → هيكسر LSP.
  • الحل: استخدمنا move() بدل fly() — كل bird بتتحرك بطريقتها.
  • make_bird_move(bird: Bird): بتاخد أي Bird وتنادي move() بدون ما تعرف النوع.
  • Sparrow → بتطير | Penguin → بتسبح. كلاهما يحل محل Bird بأمان.
main() make_bird_move(Sparrow) Sparrow.move()| make_bird_move(Penguin) Penguin.move()
1main() بتعمل Sparrow وPenguin
2make_bird_move() بتستقبل Bird → بتنادي move()
3Python بيعرف ينادي التنفيذ الصح تلقائياً (Polymorphism)
4كل bird بتتحرك بطريقتها من غير أخطاء
Example 4 — ISP: Interface Segregation Principle SOLID – ISP
بدل interface واحد كبير (Machine: print, scan, fax) → قسّمه لـ interfaces صغيرة. SimplePrinter يطبع بس، MultiFunctionDevice يطبع ويسكن.
from abc import ABC, abstractmethod
class Printer(ABC):
    @abstractmethod
    def print_doc(self, text: str) -> None: pass
class Scanner(ABC):
    @abstractmethod
    def scan_doc(self) -> str: pass
class SimplePrinter(Printer):
    def print_doc(self, text: str) -> None:
        print(f"[SimplePrinter] Printing: {text}")
class MultiFunctionDevice(Printer, Scanner):
    def print_doc(self, text: str) -> None:
        print(f"[MFD] Printing: {text}")
    def scan_doc(self) -> str:
        print("[MFD] Scanning document...")
        return "Scanned document content"

ISP: متجبرش أي class على implement وظائف مش محتاجها.

  • لو عندنا interface واحد فيه print + scan + fax → SimplePrinter هيضطر يعمل scan وfax وهو مش بيدعمهم!
  • الحل: Printer (interface للطباعة بس) + Scanner (interface للسكان بس).
  • SimplePrinter: بتورث Printer بس → بتعمل print_doc بس.
  • MultiFunctionDevice: بتورث Printer AND Scanner → بتعمل الاتنين.
main() SimplePrinter.print_doc()| main() MFD.print_doc()+ MFD.scan_doc()
1main() بتنشئ SimplePrinter → بتنادي print_doc("Hello ISP!")
2main() بتنشئ MultiFunctionDevice
3MFD.print_doc() → تطبع | MFD.scan_doc() → تسكن وترجع نص
4main() بتطبع نتيجة السكان
Example 5 — DIP: Dependency Inversion Principle SOLID – DIP
AlertService لازم تعتمد على Notifier abstraction مش على EmailNotifier مباشرة. استخدم Dependency Injection.
from abc import ABC, abstractmethod
class Notifier(ABC):
    @abstractmethod
    def send(self, message: str) -> None: pass
class EmailNotifier(Notifier):
    def send(self, message: str) -> None:
        print(f"[EMAIL] {message}")
class SMSNotifier(Notifier):
    def send(self, message: str) -> None:
        print(f"[SMS] {message}")
class FakeNotifier(Notifier):   # For testing
    def __init__(self):
        self.sent_messages: list[str] = []
    def send(self, message: str) -> None:
        self.sent_messages.append(message)
class AlertService:
    def __init__(self, notifier: Notifier):   # Depends on abstraction!
        self.notifier = notifier
    def alert(self, level: str, details: str) -> None:
        self.notifier.send(f"ALERT [{level}]: {details}")

DIP: المستوى العالي (AlertService) لازم يعتمد على abstraction مش على تفاصيل concrete classes.

  • AlertService.__init__(notifier: Notifier): بتاخد أي Notifier — ده الـ Dependency Injection.
  • لو عايز تغير من Email لـ SMS → مش بتعدل AlertService خالص، بس بتبعت notifier مختلف.
  • FakeNotifier: للـ testing — بدل ما تبعت emails حقيقية، بتخزن الرسائل في قايمة وتتأكد منها.
  • ده بيخلي الكود سهل الاختبار (testable) وسهل التوسع (extensible).
main() AlertService(EmailNotifier) alert() notifier.send() EmailNotifier.send()
1main() بتنشئ AlertService وتحقن فيه EmailNotifier
2service.alert() بتبني الرسالة وتبعتها لـ notifier.send()
3نفس الخطوات مع SMSNotifier — AlertService ما اتغيرتش
4Testing: FakeNotifier بتخزن الرسائل بدل إرسالها
5main() بتطبع fake.sent_messages للتحقق

3

Lecture 3 – Creational Design Patterns

Factory, Abstract Factory, Builder, Prototype, Singleton

Example 1 — Factory Method Pattern Creational
اعمل factory function لإنشاء shapes (Circle, Rectangle) بدون ما الـ client ينشئهم مباشرة.
from abc import ABC, abstractmethod
import math
class Shape(ABC):
    @abstractmethod
    def area(self) -> float: pass
class Circle(Shape):
    def __init__(self, radius: float): self.radius = radius
    def area(self) -> float: return math.pi * self.radius ** 2
class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width; self.height = height
    def area(self) -> float: return self.width * self.height
def create_shape(shape_type: str, **kwargs) -> Shape:
    if shape_type == "circle": return Circle(radius=kwargs["radius"])
    if shape_type == "rectangle": return Rectangle(kwargs["width"], kwargs["height"])
    raise ValueError(f"Unknown shape: {shape_type}")
def main():
    s1 = create_shape("circle", radius=5)
    s2 = create_shape("rectangle", width=4, height=6)
    print("Circle area:", round(s1.area(), 3))
    print("Rectangle area:", round(s2.area(), 3))

الـ Factory Method بيفصل عملية الإنشاء عن الاستخدام — الـ client مش محتاج يعرف الـ class الحقيقية.

  • create_shape(): هي الـ Factory — بتاخد string وترجع الـ object المناسب.
  • الـ client (main) بيشتغل مع Shape interface بس.
  • لو أضفت Triangle → بتضيف class + سطر في create_shape. الـ client ما بيتغيرش.
  • **kwargs: بيسمح بتمرير parameters مختلفة لكل نوع shape.
main() create_shape("circle") Circle(radius=5)| create_shape("rectangle") Rectangle(4,6) s.area()
1main() بتنادي create_shape بنوع الشكل والأبعاد
2create_shape بتحدد النوع وتنشئ الـ object المناسب
3main() بتنادي area() على الـ object المُنشأ
4كل shape بتحسب مساحتها بطريقتها (Circle = πr², Rectangle = w×h)
5main() بتطبع النتائج
Example 2 — Abstract Factory Pattern Creational
اعمل UI Factory لإنشاء عائلات من components (Light Theme / Dark Theme) — Button و TextBox لكل theme.
class LightButton(Button):
    def render(self): print("[LightButton] White background")
class DarkButton(Button):
    def render(self): print("[DarkButton] Dark background")
class LightFactory(UIFactory):
    def create_button(self): return LightButton()
    def create_textbox(self): return LightTextBox()
class DarkFactory(UIFactory):
    def create_button(self): return DarkButton()
    def create_textbox(self): return DarkTextBox()
def build_screen(factory: UIFactory) -> None:
    btn = factory.create_button()
    tb  = factory.create_textbox()
    btn.render(); tb.render()
def main():
    build_screen(LightFactory())   # Light theme
    build_screen(DarkFactory())    # Dark theme

الـ Abstract Factory بينشئ عائلة من الـ objects المترابطة — لو اخترت Light كل حاجة Light.

  • UIFactory (ABC): الـ interface العام — create_button() و create_textbox().
  • LightFactory / DarkFactory: كل واحدة بتنشئ عائلتها الكاملة.
  • build_screen(factory): بتشتغل مع أي factory من غير ما تعرف النوع.
  • الفرق عن Factory Method: هنا بننشئ عائلة من الـ objects مش object واحد.
main() build_screen(LightFactory) factory.create_button() LightButton.render()
1main() بتنادي build_screen مع LightFactory
2build_screen بتنادي factory.create_button() → يرجع LightButton
3build_screen بتنادي factory.create_textbox() → يرجع LightTextBox
4btn.render() و tb.render() بتطبع الـ Light components
5نفس الخطوات مع DarkFactory → كل حاجة Dark
Example 3 — Builder Pattern Creational
اعمل Computer builder خطوة بخطوة باستخدام method chaining. اعمل Gaming PC وOffice PC.
from dataclasses import dataclass
@dataclass
class Computer:
    cpu: str | None = None
    ram: str | None = None
    storage: str | None = None
    gpu: str | None = None
class ComputerBuilder:
    def __init__(self): self._computer = Computer()
    def cpu(self, cpu): self._computer.cpu = cpu; return self
    def ram(self, ram): self._computer.ram = ram; return self
    def storage(self, s): self._computer.storage = s; return self
    def gpu(self, gpu): self._computer.gpu = gpu; return self
    def build(self):
        built = self._computer
        self._computer = Computer()   # reset for next build
        return built
def main():
    builder = ComputerBuilder()
    gaming = builder.cpu("Intel i7").ram("32GB").storage("1TB SSD").gpu("RTX 4070").build()
    office = builder.cpu("Intel i5").ram("16GB").storage("512GB SSD").build()
    print("Gaming PC:", gaming)
    print("Office PC:", office)

الـ Builder Pattern بيبني objects معقدة خطوة بخطوة باستخدام method chaining.

  • Computer (@dataclass): الـ object النهائي — بياخد قطع اختيارية.
  • ComputerBuilder: كل method بتضيف جزء وترجع self → يسمح بالـ chaining.
  • Method Chaining: builder.cpu().ram().gpu().build() — واضح وسهل القراءة.
  • build() بيرجع الـ Computer وبيـ reset الـ builder لـ build تاني.
  • الفايدة: ممكن تعمل Computer بدون GPU (Office) أو بـ GPU (Gaming) بنفس الـ builder.
main() builder.cpu() .ram() .storage() .gpu() .build() Computer object
1ComputerBuilder.__init__() بينشئ Computer فاضي
2كل method (cpu, ram...) بتضيف الجزء ثم ترجع self
3build() بيرجع الـ Computer المبني ويـ reset الـ builder
4نفس الـ builder بيُستخدم لبناء Office PC بدون GPU
Example 4 — Prototype Pattern Creational
اعمل TrainingConfig بيقدر يتكلون (clone) باستخدام deep copy. عمل variant منه بـ learning rate مختلف وآخر بـ batch size مختلف.
import copy
from dataclasses import dataclass
@dataclass
class TrainingConfig:
    model_name: str
    learning_rate: float
    batch_size: int
    epochs: int
    def clone(self) -> "TrainingConfig":
        return copy.deepcopy(self)
def main():
    base = TrainingConfig("CNN", learning_rate=0.001, batch_size=32, epochs=20)
    variant_lr = base.clone(); variant_lr.learning_rate = 0.0005
    variant_bs = base.clone(); variant_bs.batch_size = 64
    print("Base      :", base)
    print("Variant LR:", variant_lr)
    print("Variant BS:", variant_bs)

الـ Prototype Pattern بيسمح بإنشاء نسخة من object موجود بدل إنشاء واحد جديد من الصفر.

  • clone(): بتستخدم copy.deepcopy() عشان كل attribute يتنسخ مستقل تماماً.
  • deepcopy مهم جداً: لو استخدمت shallow copy، التعديل على الـ clone هيأثر على الأصل!
  • الفايدة في ML: عندك base config → تعمل 10 experiments بتغيير parameter واحد كل مرة.
  • النسخ الثلاثة مستقلة تماماً — أي تغيير في واحدة ما بيأثرش على التانية.
main() base.clone() copy.deepcopy(self) variant جديد مستقل تعديل variant
1main() بتنشئ base TrainingConfig
2base.clone() بتعمل deepcopy → نسخة مستقلة تماماً
3variant_lr.learning_rate تتغير — base ما اتأثرش
4base.clone() تاني → variant_bs.batch_size تتغير
5طباعة الثلاث configs — كل واحدة مختلفة ومستقلة
Example 5 — Singleton Pattern Creational
اعمل LoggerSingleton بيضمن وجود instance واحد بس في البرنامج كله. برهن إن logger1 و logger2 نفس الـ object.
class LoggerSingleton:
    """Singleton logger — use carefully."""
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._logs = []
        return cls._instance
    def log(self, message: str) -> None:
        self._logs.append(message)
    def show_logs(self) -> None:
        for i, msg in enumerate(self._logs, 1):
            print(f"{i}. {msg}")
def main():
    logger1 = LoggerSingleton()
    logger2 = LoggerSingleton()
    print("Same object?", logger1 is logger2)  # True
    logger1.log("System started")
    logger2.log("User logged in")
    logger1.show_logs()

الـ Singleton Pattern بيضمن إن class معينة عندها instance واحد بس في البرنامج كله.

  • __new__(cls): ده اللي بيتنادى قبل __init__ لإنشاء الـ object. هنا بنتحكم فيه.
  • لو _instance = None → اعمل instance جديد وخزّنه. لو لأ → رجّع نفس الـ instance القديم.
  • logger1 is logger2 → True — نفس الـ object في الذاكرة.
  • الـ logs اللي logger2 بيضيفها بتظهر مع logger1 لأنهم نفس الـ object.
  • ⚠️ استخدمه بحذر: بيصعّب الـ testing وممكن يسبب مشاكل في multi-threading.
main() LoggerSingleton() → __new__() نفس الـ instance دايماً log() + show_logs()
1LoggerSingleton() → Python ينادي __new__ → _instance = None → ينشئ instance جديد
2LoggerSingleton() تاني → __new__ → _instance موجود → يرجع نفس الـ instance
3logger1 is logger2 → True (نفس العنوان في الذاكرة)
4logger1.log() و logger2.log() كلاهما بيضيفوا لنفس القايمة _logs
5show_logs() بتطبع كل الـ logs من الـ instance الواحد