Chapterبەش 4 · Lectureوانە 4.3

Dependency Injection: Handing Objects What They Needتێخستنی وابەستەیی: دانی ئەوەی ئۆبجێکتەکان پێویستیانە

Why a class that builds its own parts is hard to test and change, and how to hand them in instead: constructor injection in Python, shapes with typing.Protocol or abc.ABC, fakes in unit tests, setter and method injection, the composition root, inversion of control and the dependency inversion principle, service locators and why many call them an anti-pattern, DI containers (Spring, .NET, Hilt), lifetimes, the same idea in Java 17, and when DI is too much — all with Bazaar Cart's SMS notifier.

بۆچی کە پارچەکانی خۆی دروست دەکات تاقیکردنەوە و گۆڕینی قورسە و چۆن لەبری ئەوە بیدەیتە دەستی: لە Python، شێوە بە ⁦typing.Protocol⁩ یان ⁦abc.ABC⁩، ساختە لە ، تێخستن لە ڕێی و ، ، و ، و بۆچی زۆر کەس بە ناوی دەبەن، (Spring، ⁦.NET⁩، Hilt)، تەمەنی ، هەمان بیرۆکە لە ⁦Java 17⁩ و کەی زیادە — هەمووی لەگەڵ ئاگادارکەرەوەی Bazaar Cart.

23 min read20 خولەک خوێندنەوە · 18 stepsهەنگاو

What you will learnچی فێر دەبیت

  • Explain why a class that creates its own dependency is hard to test and change.
  • Refactor a Python class to constructor injection and describe its dependency with typing.Protocol or abc.ABC.
  • Test a class with injected fakes instead of real outside services.
  • Wire an application in one composition root and choose lifetimes.
  • Tell dependency injection from a service locator and a DI container, and explain inversion of control and dependency inversion.
  • ڕوون بکەیتەوە بۆچی کە خۆی دروست دەکات تاقیکردنەوە و گۆڕینی قورسە.
  • Python بگۆڕیت بۆ و بە ⁦typing.Protocol⁩ یان ⁦abc.ABC⁩ وەسف بکەیت.
  • بە ساختەی تێخراو تاقی بکەیتەوە لەبری ڕاستەقینەی دەرەوە.
  • لە یەک پێکەوە ببەستیت و تەمەنەکان هەڵبژێریت.
  • لە و جیا بکەیتەوە و و ڕوون بکەیتەوە.

A service that builds its own notifierخزمەتگوزارییەک کە ئاگادارکەرەوەکەی خۆی دروست دەکات

Soran wants every customer to get an SMS when an order leaves the shop. Karwan writes the part that sends it, SmsNotifier. It talks to a paid SMS company over the internet and needs a secret API key, read from the environment variable SMS_API_KEY (never commit secrets). The real web request is shown but not run here: it needs a real provider and costs money.

سۆران دەیەوێت هەموو کڕیارێک وەربگرێت کاتێک داواکارییەکەی لە دوکانەکە دەردەچێت. کاروان ئەو بەشە دەنووسێت کە دەینێرێت: SmsNotifier. لە ڕێی قسە لەگەڵ کۆمپانیایەکی پارەداری دەکات و پێویستی بە هەیە کە دەبێت نهێنی بمێنێتەوە و لە گۆڕاوی ژینگەی SMS_API_KEY دەخوێندرێتەوە (هەرگیز نهێنی کۆمیت مەکە). بەشی ناردنە ڕاستەقینەکە لێرە پیشان دراوە بەڵام جێبەجێ ناکرێت: پێویستی بە دابینکەرێکی ڕاستەقینە هەیە و پارەی تێدەچێت.

The real SMS sender (shown, not run)ناردەری ڕاستەقینەی نامەی کورت (پیشان دراوە، جێبەجێ نەکراوە)bazaar_cart/notifiers.pyPYTHON
"""Ways to tell a customer that the order is on its way."""
import json
import urllib.request


class SmsNotifier:
    """The real one for the shop: an SMS through a provider's web API."""

    def __init__(self, api_key, url="https://sms.example.com/v1/messages"):
        if not api_key:
            raise RuntimeError("SMS_API_KEY is not set: cannot send SMS")
        self.api_key = api_key
        self.url = url                      # your provider's address goes here

    def send(self, phone, text):
        body = json.dumps({"to": phone, "text": text}).encode()
        request = urllib.request.Request(
            self.url, data=body, method="POST",
            headers={"Authorization": f"Bearer {self.api_key}",
                     "Content-Type": "application/json"})
        urllib.request.urlopen(request, timeout=10)   # a real SMS: it costs money
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 10-11

Without a key it stops at once with a clear message.

بەبێ کلیل یەکسەر بە پەیامێکی ڕوون ڕادەوەستێت.

Lineدێڕی 16-21

A real POST request to the provider: every call is a paid SMS.

POSTـی ڕاستەقینە بۆ دابینکەرەکە: هەر بانگکردنێک پارەدارە.

Karwan puts it straight into OrderService from the layers lesson: the constructor now creates its own SmsNotifier. The picture shows what that means for everyone who runs the code.

کاروان ڕاستەوخۆ دەیخاتە ناو OrderServiceـی وانەی چینەکان: ئێستا SmsNotifierـی خۆی دروست دەکات. وێنەکە پیشان دەدات ئەمە بۆ هەر کەسێک کە کۆدەکە جێبەجێ دەکات چ واتایەکی هەیە.

Dilan's test, on her laptop, calls OrderService(repo). Inside OrderService the constructor runs SmsNotifier(os.environ['SMS_API_KEY']), a choice made inside the class. An arrow goes on to a paid SMS company on the internet, with three problems: it needs a secret API key, it costs money for every SMS, it needs the internet. Bottom: no key on the laptop leads to RuntimeError before any rule is checked.تاقیکردنەوەکەی دیلان لەسەر لاپتۆپەکەی OrderService(repo) بانگ دەکات. لە ناو OrderService دروستکەرەکەی SmsNotifier(os.environ['SMS_API_KEY']) جێبەجێ دەکات؛ هەڵبژاردنێک کە لە ناو کلاسەکە کراوە. تیرێک دەچێت بۆ کۆمپانیایەکی پارەداری نامەی کورت لەسەر ئینتەرنێت بە سێ کێشەوە: پێویستی بە کلیلی APIـی نهێنی هەیە، هەر نامەیەک پارەی دەوێت، پێویستی بە ئینتەرنێت هەیە. خوارەوە: لەسەر لاپتۆپەکە کلیل نییە ← RuntimeError پێش ئەوەی هیچ یاسایەک بپشکنرێت.
The notifier is chosen inside the class, so every user of the class gets the real SMS company.ئاگادارکەرەوەکە لە ناو هەڵبژێردراوە، بۆیە هەر کەسێک بەکار بهێنێت کۆمپانیا ڕاستەقینەکەی وەردەگرێت.CêIT

A service that builds its own notifierخزمەتگوزارییەک کە ئاگادارکەرەوەکەی خۆی دروست دەکات

Every test fails, before it checks a ruleهەموو تاقیکردنەوەکانی یەکە شکست دەهێنن، پێش ئەوەی یاسایەک بپشکنن

OrderService builds its own notifier (the changed lines)OrderService ئاگادارکەرەوەی خۆی دروست دەکات (دێڕە گۆڕاوەکان)bazaar_cart/services.pyPYTHON
import os

from .notifiers import SmsNotifier

# ... the rules R1-R4 as in the layers lesson ...

class OrderService:
    def __init__(self, repo):
        self.repo = repo  # any object with get() and reduce_stock()
        self.notifier = SmsNotifier(os.environ.get("SMS_API_KEY"))  # builds its own

    # ... price_order() as in the layers lesson ...

    def place_order(self, items, phone):
        """Price the order, take the goods from stock, tell the customer."""
        order = self.price_order(items)
        for code, grams in items:
            self.repo.reduce_stock(code, grams)
        self.notifier.send(phone, f"Bazaar Cart: your order of {order.total:,} IQD"
                                  " is on its way.")
        return order.total

Dilan, the tester, runs the six tests from the layers lesson on her laptop. She has no SMS key, and she should not have one. The output is shortened; the other five errors are the same:

دیلان، ، شەش تاقیکردنەوەکەی وانەی لەسەر لاپتۆپەکەی جێبەجێ دەکات. کلیلی نییە و نابێت هەشیبێت. دەرئەنجامەکە کورت کراوەتەوە؛ پێنج هەڵەکەی تر هەمان شتن:

$ python3.13 -m unittest (shortened)⁦$ python3.13 -m unittest⁩ (کورتکراوە)
EEEEEEEE
======================================================================
ERROR: test_customer_gets_one_message_with_the_total (tests.test_services.OrderServiceTest.test_customer_gets_one_message_with_the_total)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "~/se/bazaar-cart/tests/test_services.py", line 36, in setUp
    self.service = OrderService(self.repo, self.notifier)   # inject both fakes
                   ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: OrderService.__init__() takes 2 positional arguments but 3 were given

[… the same RuntimeError for the other five tests …]

----------------------------------------------------------------------
Ran 8 tests in 0.002s

FAILED (errors=8)

Not one rule was checked: each test stops in setUp(), at the line that builds the notifier. With a real key it would be worse: every test run would send real messages to real phones and cost money. The class is hard to test and hard to change, because it chooses its own partner.

تەنانەت یەک یاساش نەپشکنرا: هەر تاقیکردنەوەیەک لە setUp()ـدا ڕادەوەستێت، لەو دێڕەی کە ئاگادارکەرەوەکە دروست دەکات. بە کلیلێکی ڕاستەقینە خراپتر دەبوو: هەر جارێک جێبەجێ بکرێن نامەی ڕاستەقینە بۆ مۆبایلی ڕاستەقینە دەنێردرێت و پارەی تێدەچێت. تاقیکردنەوەی ئەم قورسە و گۆڕینیشی قورسە، چونکە هاوبەشەکەی خۆی هەڵدەبژێرێت.

Dependencies and couplingوابەستەییەکان و پێکەوەبەستراوی

An object that another object needs in order to work is its dependency. OrderService has two: a repository for the products and a notifier for the messages. Needing other objects is normal. The trouble is how the service gets them. When a class creates a concrete class itself, the two are tightly coupled (coupling): you cannot use one without that exact partner, and changing the partner means editing the class.

ئەو کە تر بۆ کارکردن پێویستیەتی، . OrderService دوو دانەی هەیە: بۆ کاڵاکان و ئاگادارکەرەوەیەک بۆ نامەکان. پێویستبوون بە تر ئاساییە. کێشەکە ئەوەیە چۆن بەدەستیان دەهێنێت. کاتێک خۆی دیاریکراو دروست دەکات، ئەو دووانە بە توندی پێکەوە دەبەسترێن (پێکەوەبەستراوی): ناتوانیت یەکێکیان بەبێ ئەو هاوبەشە دیاریکراوە بەکار بهێنیت و گۆڕینی هاوبەشەکە واتە دەستکاریکردنی .

Dependency injection (DI) is the fix: a class does not make what it needs; something outside hands it in. Android's guide lists three ways for a class to get an object: build it, fetch it from somewhere, or receive it as a parameter. Only the third is dependency injection.

⁦(DI)⁩ چارەسەرەکەیە: ئەوەی پێویستیەتی دروستی ناکات؛ شتێک لە دەرەوە دەیداتە دەستی. ڕێبەری Android سێ ڕێگە بۆ بەدەستهێنانی دەژمێرێت: دروستی بکات، لە شوێنێکەوە بیهێنێت، یان وەک وەریبگرێت. تەنیا سێیەمیان .

A taxi driver and the garageشۆفێری تەکسی و گەراج

Imagine that every Hawler Ride driver had to build his own car each morning before work. He could only ever drive that one car, and no mechanic could test it without him. Real drivers collect a car from the garage: any car that passes the check will do, and the garage can give a learner a training car.

وای دابنێ هەموو شۆفێرێکی Hawler Ride هەموو بەیانییەک پێش کار ئۆتۆمبێلەکەی خۆی دروست بکردایە. تەنیا ئەو یەک ئۆتۆمبێلەی دەتوانی لێبخوڕێت و هیچ وەستایەک نەیدەتوانی بەبێ ئەو تاقی بکاتەوە. شۆفێرە ڕاستەقینەکان ئۆتۆمبێلێک لە گەراجەکە وەردەگرن: هەر ئۆتۆمبێلێک کە پشکنینەکە تێپەڕێنێت دەبێت و گەراجەکە دەتوانێت ئۆتۆمبێلێکی ڕاهێنان بداتە فێرخوازێک.

Where the analogy stops workingسنووری ئەم نموونەیە

The garage hands out a car every morning; with constructor injection an object usually receives its parts once, when it is made.

گەراجەکە هەموو بەیانییەک ئۆتۆمبێلێک دەدات؛ بە زۆرجار یەک جار پارچەکانی وەردەگرێت، کاتی دروستبوونی.

Two panels. Before: OrderService has a thick arrow 'creates' to SmsNotifier, labelled: tied to one class. After, dependency injection: main() creates one of three notifiers (SmsNotifier, ConsoleNotifier, FakeNotifier) and hands it in to OrderService; OrderService uses whichever it is given. Bottom: the class only asks for the part; main() chooses it.دوو بەش. پێشتر: OrderService تیرێکی ئەستووری «دروستی دەکات» بۆ SmsNotifier هەیە؛ تێبینییەکە دەڵێت بە یەک کلاس بەستراوە. دواتر، تێخستنی وابەستەیی: main() یەکێک لە سێ ئاگادارکەرەوە دروست دەکات (SmsNotifier، ConsoleNotifier، FakeNotifier) و دەیداتە دەست OrderService؛ OrderService هەر کامێک بدرێتێ بەکاری دەهێنێت. خوارەوە: کلاسەکە تەنیا داوای پارچەکە دەکات؛ main() هەڵیدەبژێرێت.
Before: the class creates its partner. After: the partner is chosen outside and handed in.پێشتر: هاوبەشەکەی خۆی دروست دەکات. دواتر: هاوبەشەکە لە دەرەوە هەڵدەبژێردرێت و دەدرێتە دەستی.CêIT

Constructor injection: ask for it in __init__تێخستن لە ڕێی دروستکەرەوە: لە __init__ـدا داوای بکە

The most common form is constructor injection: the class lists what it needs as parameters of its constructor and keeps them. You have done it once already: in the layers lesson the service received its repository, so a test could pass a fake one. Now the notifier gets the same treatment. Two lines change in services.py:

باوترین شێوە : ئەوەی پێویستیەتی وەک دەنووسێت و هەڵیاندەگرێت. پێشتر یەک جار کردووتە: لە وانەی ، وەرگرت، بۆیە تاقیکردنەوەیەک دەیتوانی دانەیەکی ساختە بدات. ئێستا ئاگادارکەرەوەکەش هەمان شت وەردەگرێت. دوو دێڕ لە services.py دەگۆڕێن:

The constructor now receives the notifierدروستکەرەکە ئێستا ئاگادارکەرەوەکە وەردەگرێتbazaar_cart/services.pyPYTHON
class OrderService:
    def __init__(self, repo, notifier):
        self.repo = repo            # any object with get() and reduce_stock()
        self.notifier = notifier    # any object with send(phone, text)

Whoever creates the service now chooses the notifier. On a laptop that is a ConsoleNotifier, which only prints the message:

هەر کەسێک دروست بکات، ئێستا ئاگادارکەرەوەکە هەڵدەبژێرێت. لەسەر لاپتۆپ ئەوە ConsoleNotifierـە کە تەنیا نامەکە چاپ دەکات:

A notifier for the laptopئاگادارکەرەوەیەک بۆ لاپتۆپbazaar_cart/notifiers.pyPYTHON
class ConsoleNotifier:
    """For the developer's laptop: prints the message instead of sending it."""

    def send(self, phone, text):
        print(f"[SMS to {phone}] {text}")
Choose the notifier from outsideئاگادارکەرەوەکە لە دەرەوە هەڵبژێرەtry_injection.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
from bazaar_cart.notifiers import ConsoleNotifier
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService

repo = ProductRepository(open_demo_db())
service = OrderService(repo, ConsoleNotifier())     # we choose the notifier here
total = service.place_order([("walnuts", 500), ("sumac", 350)], "0770 000 0001")
print("Returned:", total)
Code box: class OrderService with def __init__(self, repo, notifier), the parameters repo and notifier highlighted. Below, a ProductRepository box (the products) has an arrow into repo, and a ConsoleNotifier box (the messages) has an arrow into notifier; label: handed in from outside. Bottom: it keeps what it is given; it builds nothing itself.سندوقی کۆد: class OrderService لەگەڵ def __init__(self, repo, notifier)؛ پارامیتەرەکانی repo و notifier دیار کراون. لە خوارەوە سندوقی ProductRepository (کاڵاکان) تیرێکی بۆ repo هەیە و سندوقی ConsoleNotifier (نامەکان) تیرێکی بۆ notifier هەیە؛ نووسراوە: لە دەرەوە دەدرێنە دەست. خوارەوە: ئەوەی دەدرێتێ دەیهێڵێتەوە؛ خۆی هیچ دروست ناکات.
The constructor's parameter list says, in one line, everything the class needs.لیستی لە یەک دێڕدا هەموو ئەو شتانە دەڵێت کە پێویستیەتی.CêIT

A forgotten part is now a loud, early error, on the line that creates the service — not deep inside an order: TypeError: OrderService.__init__() missing 1 required positional argument: 'notifier'.

پارچەیەکی لەبیرکراو ئێستا بەدەنگ و زووە، لەسەر ئەو دێڕەی کە دروست دەکات — نەک لە قووڵایی داواکارییەکدا: TypeError: OrderService.__init__() missing 1 required positional argument: 'notifier'.

Depend on a shape, not a class: typing.Protocolپشت بە شێوەیەک ببەستە، نەک بە کلاسێک: ⁦typing.Protocol⁩

OrderService never asks which notifier it has. It only calls send(phone, text). In Python any object with that method works — duck typing. To write that promise down, the business layer describes the shape with typing.Protocol (added in Python 3.8 by PEP 544):

OrderService هەرگیز ناپرسێت کام ئاگادارکەرەوەی هەیە. تەنیا send(phone, text) بانگ دەکات. لە Python هەر ئەو هەبێت کار دەکات — جۆرداریی مراوی. بۆ نووسینەوەی ئەو بەڵێنە، شێوەکە بە typing.Protocol وەسف دەکات (لە ⁦Python 3.8⁩ـدا بە ⁦PEP 544⁩ زیاد کرا):

The shape the business layer needsئەو شێوەیەی چینی لۆجیکی کاروبار پێویستیەتیbazaar_cart/services.pyPYTHON
from typing import Protocol


class Notifier(Protocol):
    """What the business layer needs: something that sends a short text."""

    def send(self, phone: str, text: str) -> None: ...


class OrderService:
    def __init__(self, repo, notifier: Notifier) -> None:
        self.repo = repo            # any object with get() and reduce_stock()
        self.notifier = notifier    # any object that fits Notifier
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 4-7

The ... body means: no code here, only the shape.

جەستەی ... واتە: لێرە کۆد نییە، تەنیا شێوەکە.

Lineدێڕی 11

A type hint: any object that fits Notifier.

: هەر لەگەڵ Notifier بگونجێت.

A class fits when it has the same method; it does not need to inherit from Notifier. Python does not check this while the program runs. A type checker such as mypy (an optional tool you install with python3.13 -m pip install mypy) checks the type hints before it runs. Here it finds a class with the wrong shape:

دەگونجێت کاتێک هەمان هەبێت؛ پێویست ناکات لە Notifierـەوە وەربگرێت. Python ئەمە لە کاتی کارکردنی ناپشکنێت. ئامرازێکی وەک mypy (ئامرازێکی هەڵبژاردەیی کە بە python3.13 -m pip install mypy دایدەمەزرێنیت) پێش جێبەجێکردن دەپشکنێت. لێرە بە شێوەی هەڵە دەدۆزێتەوە:

A notifier with the wrong shapeئاگادارکەرەوەیەک بە شێوەی هەڵەwrong_shape.pyPYTHON
"""A notifier with the wrong shape: send_email() instead of send()."""
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService


class EmailSender:
    def send_email(self, address: str, text: str) -> None:
        print(f"[e-mail to {address}] {text}")


service = OrderService(ProductRepository(open_demo_db()), EmailSender())
service.place_order([("sumac", 350)], "0770 000 0001")
Output of mypy wrong_shape.py (mypy 1.19.1)دەرئەنجامی ⁦mypy wrong_shape.py (mypy 1.19.1)⁩
wrong_shape.py:11: error: Argument 2 to "OrderService" has incompatible type "EmailSender"; expected "Notifier"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Without mypy the mistake waits until an order is placed: AttributeError: 'EmailSender' object has no attribute 'send'.

بەبێ mypy هەڵەکە چاوەڕێ دەکات تا داواکارییەک تۆمار دەکرێت: AttributeError: 'EmailSender' object has no attribute 'send'.

A wall socketپریزی دیوار

A wall socket in Erbil does not care whether you plug in a phone charger, a kettle or a lamp: any plug of the right shape works. Notifier is the socket; SmsNotifier, ConsoleNotifier and a test's FakeNotifier are plugs.

پریزی دیوارێک لە هەولێر گرنگی پێ نادات کە شەحنی مۆبایل، کتری یان گڵۆپێک بخەیتە ناوی: هەر فیشێک بە شێوەی ڕاست کار دەکات. Notifier پریزەکەیە؛ SmsNotifier، ConsoleNotifier و FakeNotifierـی ناو تاقیکردنەوەکان فیشەکانن.

Where the analogy stops workingسنووری ئەم نموونەیە

A plug of the wrong shape cannot go in at all. Python lets a wrong object in, and only mypy — or the crash — tells you.

فیشێکی شێوە هەڵە هەرگیز ناچێتە ناو پریزەکە. Python هەڵە ڕێگە پێدەدات و تەنیا mypy — یان تێکچوونەکە — پێت دەڵێت.

A socket labelled Notifier with two round holes and the text send(phone, text), the shape it promises. On the right four plugs: SmsNotifier (in the shop), ConsoleNotifier (on a laptop) and FakeNotifier (in the tests) have two round pins and a tick; EmailSender (has send_email()) has three flat pins, a cross and the label: does not fit. Bottom: no inheritance needed; having the same method is enough.پریزێک بە ناوی Notifier بە دوو کونی خڕ و دەقی send(phone, text)، ئەو شێوەیەی بەڵێنی پێدەدات. لە لای ڕاست چوار فیش: SmsNotifier (لە دوکانەکە)، ConsoleNotifier (لەسەر لاپتۆپ) و FakeNotifier (لە تاقیکردنەوەکانی یەکەدا) دوو قاچی خڕیان هەیە و نیشانەی ڕاست؛ EmailSender (send_email()ـی هەیە) سێ قاچی پانی هەیە، نیشانەی هەڵە و تێبینی: ناگونجێت. خوارەوە: پێویست بە بۆماوە ناکات؛ هەبوونی هەمان میتۆد بەسە.
Any class with send(phone, text) fits the Notifier socket.هەر send(phone, text)ـی هەبێت لەگەڵ پریزی Notifier دەگونجێت.CêIT

Depend on a shape, not a class: typing.Protocolپشت بە شێوەیەک ببەستە، نەک بە کلاسێک: ⁦typing.Protocol⁩

Protocol or abstract base class?Protocol یان کلاسی ڕووت؟

Python has a second way to write a shape: an abstract base class with @abstractmethod. Java and PHP use the interface keyword for the same job (Java interfaces, PHP interfaces). With an ABC the class must inherit, and Python checks when an object is made:

Python ڕێگەیەکی دووەمی هەیە بۆ نووسینی شێوەیەک: کلاسی ڕووت بە @abstractmethod. Java و PHP بۆ هەمان کار وشەی interface بەکار دەهێنن (نێوبەستەکانی Java، نێوبەستەکانی PHP). بە ABC دەبێت وەربگرێت و Python کاتی دروستکردنی دەیپشکنێت:

The same shape as an abstract base classهەمان شێوە وەک کلاسی ڕووتnotifier_abc.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""The same contract as an abstract base class: checked when an object is made."""
from abc import ABC, abstractmethod


class Notifier(ABC):
    @abstractmethod
    def send(self, phone, text):
        """Send a short text message to a phone."""


class ConsoleNotifier(Notifier):          # must inherit from Notifier
    def send(self, phone, text):
        print(f"[SMS to {phone}] {text}")


class EmailSender(Notifier):              # inherits, but forgot send()
    def send_email(self, address, text):
        print(f"[e-mail to {address}] {text}")


ConsoleNotifier().send("0770 000 0001", "It works.")
EmailSender()
Two ways to describe the shapeدوو ڕێگە بۆ وەسفکردنی شێوەکە
Questionپرسیارtyping.Protocolabc.ABC
Must the class inherit?دەبێت وەربگرێت؟No: the right methods are enoughنەخێر: ئەگەر هەبن بەسەYes: class SmsNotifier(Notifier)بەڵێ: class SmsNotifier(Notifier)
Who checks, and when?کێ دەیپشکنێت و کەی؟a type checker such as mypy, before the program runsئامرازێکی وەک mypy، پێش جێبەجێکردنی Python, when an object is madePython، کاتی دروستکردنی
Good forباشە بۆsmall shapes; classes you did not writeشێوەی بچووک؛ ئەو خۆت نەتنووسیونa family of your own classes that share codeخێزانێک لە خۆت کە کۆدی هاوبەشیان هەیە
A time line with four steps: write the code, mypy checks, object is made, send() is called; a dashed line separates before it runs from while it runs. Row Protocol: a tick at mypy checks with 'mypy error: incompatible type'; without mypy, AttributeError when send() is called. Row abc.ABC: a tick at object is made with 'TypeError: can't instantiate'.هێڵێکی کات بە چوار هەنگاو: نووسینی کۆد، mypy دەپشکنێت، ئۆبجێکت دروست دەکرێت، send() بانگ دەکرێت؛ هێڵێکی پچڕپچڕ پێش جێبەجێکردن لە کاتی جێبەجێکردن جیا دەکاتەوە. ڕیزی Protocol: نیشانەی ڕاست لای mypy دەپشکنێت لەگەڵ «هەڵەی mypy: جۆری نەگونجاو»؛ بەبێ mypy، AttributeError کاتی بانگکردنی send(). ڕیزی abc.ABC: نیشانەی ڕاست لای دروستکردنی ئۆبجێکت لەگەڵ «TypeError: ناتوانرێت دروست بکرێت».
A Protocol is checked by a tool before the program runs; an ABC is checked by Python when an object is made.Protocol لەلایەن ئامرازێکەوە پێش جێبەجێکردنی دەپشکنرێت؛ ABC لەلایەن Pythonـەوە کاتی دروستکردنی .CêIT

One service, three notifiers: the shop, the laptop and the testsیەک خزمەتگوزاری، سێ ئاگادارکەرەوە: دوکان، لاپتۆپ و تاقیکردنەوەکانی یەکە

Because the service accepts any notifier, one class now works in three places. In the shop it gets SmsNotifier; on a laptop, ConsoleNotifier; in the tests, a FakeNotifier that only remembers the messages — a test double.

چونکە هەر ئاگادارکەرەوەیەک وەردەگرێت، ئێستا یەک لە سێ شوێن کار دەکات. لە دوکانەکە SmsNotifier وەردەگرێت؛ لەسەر لاپتۆپ ConsoleNotifier؛ لە FakeNotifier کە تەنیا نامەکان هەڵدەگرێت — ئۆبجێکتێکی ساختە.

OrderService at the top, 'the same class, not changed'. Three arrows go down to three places. In the shop: SmsNotifier, a real SMS on a phone. On a laptop: ConsoleNotifier, printing [SMS to 0770 …] Bazaar Cart: …. In the tests: FakeNotifier, which remembers it in notifier.sent. Bottom: only main() or the test decides which one.OrderService لە سەرەوە، «هەمان کلاس، نەگۆڕدراو». سێ تیر بۆ سێ شوێن دادەبەزن. لە دوکانەکە: SmsNotifier، نامەیەکی کورتی ڕاستەقینە لەسەر مۆبایل. لەسەر لاپتۆپ: ConsoleNotifier کە [SMS to 0770 …] Bazaar Cart: … چاپ دەکات. لە تاقیکردنەوەکانی یەکەدا: FakeNotifier کە لە notifier.sent هەڵیدەگرێت. خوارەوە: تەنیا main() یان تاقیکردنەوەکە بڕیار دەدات کامیان.
The same service with three different notifiers.هەمان بە سێ ئاگادارکەرەوەی جیاواز.CêIT
The tests inject fakes (the new parts)تاقیکردنەوەکانی یەکە ساختەکان تێدەخەن (بەشە نوێیەکان)tests/test_services.pyPYTHON
class FakeNotifier:
    """A fake notifier: it remembers the messages instead of sending them."""

    def __init__(self):
        self.sent = []

    def send(self, phone, text):
        self.sent.append((phone, text))


class OrderServiceTest(unittest.TestCase):
    def setUp(self):
        self.repo = InMemoryProductRepository([
            Product("sumac", "Sumac", 8000, 5000),
            Product("honey", "Honey", 30000, 3000)])
        self.notifier = FakeNotifier()
        self.service = OrderService(self.repo, self.notifier)   # inject both fakes

    # ... the six tests from the layers lesson, unchanged ...

    def test_customer_gets_one_message_with_the_total(self):
        self.service.place_order([("sumac", 350)], "0770 000 0001")
        self.assertEqual(self.notifier.sent, [
            ("0770 000 0001", "Bazaar Cart: your order of 6,000 IQD is on its way.")])

    def test_no_message_when_a_rule_says_no(self):
        with self.assertRaises(ValueError):
            self.service.place_order([("honey", 3500)], "0770 000 0001")   # R3
        self.assertEqual(self.notifier.sent, [])
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 1-8

A fake notifier: a list instead of a phone company.

ئاگادارکەرەوەیەکی ساختە: لیستێک لەبری کۆمپانیای تەلەفۆن.

Lineدێڕی 16-17

Inject both fakes through the constructor.

هەردوو ساختەکە لە ڕێی تێبخە.

Output of python3.13 -m unittest -vدەرئەنجامی ⁦python3.13 -m unittest -v⁩
test_customer_gets_one_message_with_the_total (tests.test_services.OrderServiceTest.test_customer_gets_one_message_with_the_total) ... ok
test_no_message_when_a_rule_says_no (tests.test_services.OrderServiceTest.test_no_message_when_a_rule_says_no) ... ok
test_place_order_takes_goods_from_stock (tests.test_services.OrderServiceTest.test_place_order_takes_goods_from_stock) ... ok
test_r1_line_price_rounds_up_to_250 (tests.test_services.OrderServiceTest.test_r1_line_price_rounds_up_to_250) ... ok
test_r2_less_than_100_g_is_refused (tests.test_services.OrderServiceTest.test_r2_less_than_100_g_is_refused) ... ok
test_r3_not_more_than_the_stock (tests.test_services.OrderServiceTest.test_r3_not_more_than_the_stock) ... ok
test_r4_delivery_costs_3000_below_50000 (tests.test_services.OrderServiceTest.test_r4_delivery_costs_3000_below_50000) ... ok
test_r4_free_delivery_from_50000 (tests.test_services.OrderServiceTest.test_r4_free_delivery_from_50000) ... ok

----------------------------------------------------------------------
Ran 8 tests in 0.000s

OK

The six old tests pass again, and two new tests check the SMS itself: exactly one message with the right total, and none when a rule says no. A unittest.mock.Mock() can be injected in exactly the same way (notifier.send.assert_called_once_with(...)); automated testing compares fakes and mocks.

شەش تاقیکردنەوە کۆنەکە دیسان سەردەکەون و دوو تاقیکردنەوەی نوێ خودی دەپشکنن: ڕێک یەک نامە بە کۆی ڕاست و هیچ نامەیەک کاتێک یاسایەک ڕەتی دەکاتەوە. unittest.mock.Mock()ـیش ڕێک بە هەمان شێوە تێدەخرێت (notifier.send.assert_called_once_with(...))؛ تاقیکردنەوەی خۆکار ساختە و Mock بەراورد دەکات.

Three ways to hand over a dependencyسێ ڕێگە بۆ دانی وابەستەییەک

Martin Fowler's article Inversion of Control Containers and the Dependency Injection pattern (January 2004), which gave the pattern its name, describes constructor injection, setter injection and a third, interface injection, that is rarely used today. In everyday code the third common form is a plain method parameter:

وتارەکەی Martin Fowler بە ناوی Inversion of Control Containers and the Dependency Injection pattern (کانوونی دووەمی ⁦2004⁩)، کە ناوی ئەم دانا، ، تێخستن لە ڕێی و سێیەمێک بە ناوی interface injection وەسف دەکات کە ئەمڕۆ کەم بەکار دێت. لە کۆدی ڕۆژانەدا سێیەم شێوەی باو سادەی :

Constructor, setter and method injectionتێخستن لە ڕێی دروستکەر، میتۆدی دانان و میتۆدthree_forms.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""Three ways to hand an object its notifier."""
from bazaar_cart.notifiers import ConsoleNotifier


class ByConstructor:                       # 1. constructor injection
    def __init__(self, notifier):
        self.notifier = notifier           # cannot exist without one

    def confirm(self, phone, total):
        self.notifier.send(phone, f"Order of {total:,} IQD confirmed.")


class BySetter:                            # 2. setter injection
    def __init__(self):
        self.notifier = None               # must be set before use!

    def set_notifier(self, notifier):
        self.notifier = notifier

    def confirm(self, phone, total):
        self.notifier.send(phone, f"Order of {total:,} IQD confirmed.")


class ByMethod:                            # 3. method (parameter) injection
    def confirm(self, phone, total, notifier):
        notifier.send(phone, f"Order of {total:,} IQD confirmed.")


console = ConsoleNotifier()
ByConstructor(console).confirm("0770 000 0001", 15_000)
ByMethod().confirm("0770 000 0002", 9_000, console)

late = BySetter()
late.set_notifier(console)
late.confirm("0770 000 0003", 6_000)

forgot = BySetter()                        # nobody called set_notifier()
forgot.confirm("0770 000 0004", 3_000)
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 37-38

Nobody called set_notifier(), so self.notifier is still None.

کەس set_notifier()ـی بانگ نەکرد، بۆیە self.notifier هێشتا Noneـە.

Three rows. 1 Constructor: OrderService(repo, notifier), given at birth, always complete, with a tick. 2 Setter: service.set_notifier(notifier), given later, may be forgotten, with a warning sign. 3 Method parameter: confirm(phone, total, notifier), given for each call, with a tick. Bottom: prefer the constructor for what a class must have.سێ ڕیز. 1 دروستکەر: OrderService(repo, notifier)، لە کاتی دروستبووندا دەدرێت، هەمیشە تەواوە، نیشانەی ڕاست. 2 میتۆدی دانان: service.set_notifier(notifier)، دواتر دەدرێت، لەوانەیە لەبیر بچێت، نیشانەی ئاگاداری. 3 پارامیتەری میتۆد: confirm(phone, total, notifier)، بۆ هەر بانگکردنێک دەدرێت، نیشانەی ڕاست. خوارەوە: بۆ ئەوەی کلاس دەبێت هەیبێت، دروستکەر باشترە.
The constructor makes a complete object; a setter can be forgotten. تەواو دروست دەکات؛ لەوانەیە لەبیر بچێت.CêIT
  • Constructor: the object is complete from its first line. Fowler's default, and the advice of the Spring documentation for every required part.
  • Setter: can change a part later, but may be forgotten, as the AttributeError shows. Use it only for optional parts with a sensible default.
  • Method parameter: good when each call may need a different object, such as a different printer for each receipt.
  • : لە یەکەم دێڕییەوە تەواوە. هەڵبژاردەی بنەڕەتیی Fowler و ئامۆژگاریی بەڵگەنامەکانی Spring بۆ هەر پارچەیەکی پێویست.
  • : دەتوانێت پارچەیەک دواتر بگۆڕێت، بەڵام لەوانەیە لەبیر بچێت، وەک AttributeErrorـەکە پیشانی دەدات. تەنیا بۆ پارچەی هەڵبژاردەیی کە گونجاویان هەیە بەکاری بهێنە.
  • : باشە کاتێک هەر بانگکردنێک لەوانەیە جیاوازی پێویست بێت، وەک چاپەرێکی جیاواز بۆ هەر پسوولەیەک.

The composition root: wire everything onceڕەگی پێکهێنان: هەموو شتێک یەک جار پێکەوە ببەستە

If no class builds its own parts, something must build them all. That place is the composition root: one place, as close to the program's start as possible, usually main() (Mark Seemann's name for it, 2011). In the layers lesson cli.py built the service itself; now it only receives it:

ئەگەر هیچ پارچەکانی خۆی دروست نەکات، دەبێت شتێک هەموویان دروست بکات. ئەو شوێنە : یەک شوێن، تا دەکرێت نزیک لە سەرەتای ، زۆرجار main() (ناوێک کە Mark Seemann لە ⁦2011⁩ـدا لێی نا). لە وانەی ، cli.py خۆی دروست دەکرد؛ ئێستا تەنیا وەریدەگرێت:

The user interface receives the serviceنێوبەستەی بەکارهێنەر خزمەتگوزارییەکە وەردەگرێتbazaar_cart/cli.pyPYTHON
def run(service):                   # the service is handed in, not built here
    items = ask_items()
    try:
        show_bill(service.price_order(items))
        if input("Confirm (y/n)? ").strip().lower() == "y":
            phone = input("Phone: ").strip()
            total = service.place_order(items, phone)
            print(f"Order placed: {total:,} IQD. Thank you!")
    except ValueError as error:
        print("Sorry,", error)
The composition rootڕەگی پێکهێنانbazaar_cart/app.pyPYTHON
"""Composition root: the one place where Bazaar Cart's objects are made and connected."""
import os

from . import cli
from .notifiers import ConsoleNotifier, SmsNotifier
from .repository import ProductRepository, open_demo_db
from .services import OrderService


def main():
    repo = ProductRepository(open_demo_db())           # data access layer
    key = os.environ.get("SMS_API_KEY")
    notifier = SmsNotifier(key) if key else ConsoleNotifier()   # real SMS only in the shop
    service = OrderService(repo, notifier)              # business layer gets its parts
    cli.run(service)                                    # presentation gets the service


if __name__ == "__main__":
    main()
Animation in six steps. Left, the code of main() in app.py with the current line highlighted; right, the objects appear. 1: main() runs first, nothing is built yet. 2: repo (ProductRepository) appears. 3: the notifier (ConsoleNotifier here, SmsNotifier in the shop) appears. 4: OrderService appears with arrows to repo and notifier. 5: cli appears with an arrow to OrderService. 6: the whole graph is framed: built once, at start-up; every other part only receives.
Animation in six steps. Left, the code of main() in app.py with the current line highlighted; right, the objects appear. 1: main() runs first, nothing is built yet. 2: repo (ProductRepository) appears. 3: the notifier (ConsoleNotifier here, SmsNotifier in the shop) appears. 4: OrderService appears with arrows to repo and notifier. 5: cli appears with an arrow to OrderService. 6: the whole graph is framed: built once, at start-up; every other part only receives.
main() builds the object graph once, from the bottom up.⁦main()⁩ تۆڕی یەک جار و لە خوارەوە بۆ سەرەوە دروست دەکات.CêIT
$ python3.13 -m bazaar_cart.app (a sample run)⁦$ python3.13 -m bazaar_cart.app⁩ (نموونەیەکی جێبەجێکردن)
Product code (empty to finish): figs
Grams: 750
Product code (empty to finish): tea
Grams: 250
Product code (empty to finish): 
  Dried figs   750 g   9,000 IQD
  Black tea    250 g   3,500 IQD
  Goods 12,500 + delivery 3,000 = 15,500 IQD
Confirm (y/n)? y
Phone: 0770 000 0001
[SMS to 0770 000 0001] Bazaar Cart: your order of 15,500 IQD is on its way.
Order placed: 15,500 IQD. Thank you!

Every other file only receives what it needs. In the shop, Soran sets SMS_API_KEY and the same code sends real messages; nothing else changes. Only the application has a composition root — a library or framework should not have one.

هەموو فایلەکانی تر تەنیا ئەوە وەردەگرن کە پێویستیانە. لە دوکانەکە، سۆران SMS_API_KEY دادەنێت و هەمان کۆد نامەی ڕاستەقینە دەنێرێت؛ هیچ شتێکی تر ناگۆڕێت. تەنیا هەیە — یان نابێت هەیبێت.

Inversion of control and the dependency inversion principleپێچەوانەکردنەوەی کۆنترۆڵ و بنەمای پێچەوانەکردنەوەی وابەستەیی

Two well-known names describe what just happened. Inversion of control (IoC): your code no longer creates and calls everything itself; something outside does, and calls your code. You have used it all along: you never call test_r1_line_price_rounds_up_to_250() yourself — unittest finds it and calls it. Martin Fowler traces its nickname, the Hollywood principle ("Don't call us, we'll call you"), to a 1983 paper. Dependency injection is one kind of inversion of control.

دوو ناوی ناسراو ئەوەی ئێستا ڕوویدا وەسف دەکەن. ⁦(IoC)⁩: کۆدەکەت ئیتر خۆی هەموو شتێک دروست ناکات و بانگ ناکات؛ شتێک لە دەرەوە دەیکات و کۆدەکەت بانگ دەکات. هەمیشە بەکارت هێناوە: هەرگیز خۆت test_r1_line_price_rounds_up_to_250() بانگ ناکەیت — unittest دەیدۆزێتەوە و بانگی دەکات. Martin Fowler نازناوەکەی، بنەمای هۆلیوود («بانگمان مەکە، ئێمە بانگت دەکەین»)، بۆ وتارێکی ⁦1983⁩ دەگەڕێنێتەوە. جۆرێکە لە .

The dependency inversion principle (Robert C. Martin, 1996; the D of SOLID) says: high-level code, the business rules, should not depend on low-level details such as SMS or SQL; both should depend on an abstraction. That is why Notifier lives in services.py, in the business layer: the detail SmsNotifier must fit the business layer's shape, not the other way round. The arrow in the code flips; at run time the call still goes from the service to the SMS sender.

(⁦Robert C. Martin⁩، ⁦1996⁩؛ Dـی SOLID) دەڵێت: کۆدی ئاست بەرز، واتە یاساکانی کاروبار، نابێت پشت بە وردەکاریی ئاست نزم ببەستێت وەک یان ؛ هەردووکیان دەبێت پشت بە ببەستن. لەبەر ئەوە Notifier لە services.pyـدایە، لە : وردەکاریی SmsNotifier دەبێت لەگەڵ شێوەی بگونجێت، نەک بە پێچەوانەوە. تیرەکە لە کۆدەکەدا هەڵدەگەڕێتەوە؛ لە بانگکردنەکە هێشتا لە دەچێت بۆ ناردەری .

Two panels, each with a business layer band on top and a details band (SMS, database) below. Before: OrderService in the business layer has an arrow 'depends on' down to SmsNotifier. After: the business layer holds OrderService and Notifier (Protocol), with an arrow from OrderService to Notifier; SmsNotifier and ConsoleNotifier in the details band have arrows up to Notifier, labelled 'fit the shape'. Bottom: at run time the call still goes from OrderService to the SMS sender.دوو بەش، هەر یەکە بە پشتێنەیەکی چینی لۆجیکی کاروبار لە سەرەوە و پشتێنەی وردەکارییەکان (نامەی کورت، بنکەدراوە) لە خوارەوە. پێشتر: OrderService لە چینی لۆجیکی کاروباردا تیرێکی «پشتی پێ دەبەستێت» بۆ خوارەوە بۆ SmsNotifier هەیە. دواتر: چینی لۆجیکی کاروبار OrderService و Notifier (Protocol) لەخۆ دەگرێت و تیرێک لە OrderServiceـەوە بۆ Notifier هەیە؛ SmsNotifier و ConsoleNotifier لە پشتێنەی وردەکارییەکاندا تیریان بۆ سەرەوە بۆ Notifier هەیە، بە ناونیشانی «لەگەڵ شێوەکە دەگونجێن». خوارەوە: لە کاتی جێبەجێکردندا بانگکردنەکە هێشتا لە OrderServiceـەوە دەچێت بۆ ناردەری نامەی کورت.
After the inversion, the details point up to a shape owned by the business layer.دوای پێچەوانەکردنەوەکە، وردەکارییەکان ڕوو لە شێوەیەک دەکەن کە هی .CêIT

The layers lesson ends with the same idea for whole layers (clean or onion architecture): the business rules at the centre, every detail pointing inward.

وانەی چینەکان بە هەمان بیرۆکە بۆ هەموو کۆتایی دێت (تەلارسازیی پاک یان پیازی): یاساکانی کاروبار لە ناوەڕاست و هەموو وردەکارییەک ڕوو لە ناوەوە.

Service locators: asking instead of receivingدۆزەرەوەی خزمەتگوزاری: داواکردن لەبری وەرگرتن

Another way to keep classes apart is a service locator: one shared registry that code asks for what it needs. Flutter's get_it is one (get_it in the to-do app). Fowler puts the difference in one line: with a locator the class asks; with injection it receives. A locator in 15 lines:

ڕێگەیەکی تر بۆ جیاکردنەوەی : تۆمارگەیەکی هاوبەش کە کۆد داوای ئەوەی لێ دەکات کە پێویستیەتی. ⁦get_it⁩ـی Flutter یەکێکە لەوانە (⁦get_it⁩ لە ئەپی ئەرکەکان). Fowler جیاوازییەکە لە یەک دێڕدا دەڵێت: بە ، داوا دەکات؛ بە تێخستن، وەریدەگرێت. لە ⁦15⁩ دێڕدا:

A tiny service locatorدۆزەرەوەیەکی خزمەتگوزاریی بچووکlocator.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""A tiny service locator, and why it hides what a class needs."""
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService


class Locator:                               # one shared box of objects
    _services = {}

    @classmethod
    def register(cls, name, service):
        cls._services[name] = service

    @classmethod
    def get(cls, name):
        return cls._services[name]


class LocatorOrderService(OrderService):     # the same rules, but ...
    def __init__(self):                      # ... the constructor says "I need nothing"
        pass

    @property
    def repo(self):                          # ... and it ASKS when it needs something
        return Locator.get("repo")

    @property
    def notifier(self):
        return Locator.get("notifier")


repo = ProductRepository(open_demo_db())
Locator.register("repo", repo)               # someone forgot to register "notifier"
service = LocatorOrderService()              # no error: nothing is checked yet
try:
    service.place_order([("sumac", 500)], "0770 000 0001")
except KeyError as error:
    print("KeyError:", error)
print(f"Sumac left in stock: {repo.get('sumac').stock_g:,} g")
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 6-15

One shared dictionary for the whole program.

یەک فەرهەنگی هاوبەش بۆ هەموو .

Lineدێڕی 22-28

Each property asks the locator when it is used.

هەر کاتی بەکارهێنانی داوا لە دۆزەرەوەکە دەکات.

The constructor promised it needed nothing. The order took 500 g of sumac from the stock and then failed, because nobody registered a notifier: a half-done order and no SMS. Mark Seemann (2010) calls the service locator an anti-pattern for this reason: what a class needs is hidden inside its methods, so mistakes appear while the program runs. Fowler is milder: inside your own application a locator is acceptable; for classes that others reuse, prefer injection.

بەڵێنی دابوو پێویستی بە هیچ نییە. داواکارییەکە ⁦500⁩ گرام سوماقی لە کۆگاکە کەم کردەوە و پاشان شکستی هێنا، چونکە کەس ئاگادارکەرەوەیەکی تۆمار نەکردبوو: داواکارییەکی نیوەچڵ و هیچ نەنێردرا. ⁦Mark Seemann (2010)⁩ بەم هۆیەوە بە ناو دەبات: ئەوەی پێویستیەتی لە ناو شاراوەیە، بۆیە هەڵەکان لە کاتی کارکردنی دەردەکەون. Fowler نەرمترە: لە ناو خۆتدا قبووڵکراوە؛ بۆ ئەو کەسانی تر دووپاتە بەکاریان دەهێننەوە، تێخستن باشترە.

Two panels. Service locator: LocatorOrderService() says 'needs: nothing?'; arrows labelled get("repo") and get("notifier") go to a Locator box; a label says KeyError, in the middle of an order; the class asks, when it runs. Dependency injection: main() hands in to OrderService(repo, notifier); its needs are in the constructor; a missing part gives TypeError at once; the class receives, when it is made.دوو بەش. دۆزەرەوەی خزمەتگوزاری: LocatorOrderService() دەڵێت «پێویستی: هیچ؟»؛ تیرەکانی get("repo") و get("notifier") دەچن بۆ سندوقی Locator؛ تێبینی: KeyError، لە ناوەڕاستی داواکارییەکدا؛ کلاسەکە لە کاتی کارکردندا داوا دەکات. تێخستنی وابەستەیی: main() دەیداتە دەست OrderService(repo, notifier)؛ ئەوەی پێویستیەتی لە دروستکەردایە؛ پارچەی ون یەکسەر TypeError دەدات؛ کلاسەکە لە کاتی دروستبووندا وەریدەگرێت.
A locator hides what a class needs until it runs; injection shows it in the constructor. ئەوەی پێویستیەتی تا کاتی کارکردن دەشارێتەوە؛ تێخستن لە پیشانی دەدات.CêIT

Service locators: asking instead of receivingدۆزەرەوەی خزمەتگوزاری: داواکردن لەبری وەرگرتن

DI containersکۆنتەینەری DI

In a big app the composition root may create hundreds of objects. A DI container is a library that does that wiring for you: you register rules once — when someone needs a Notifier, give an SmsNotifier — and the container builds each object with everything its constructor asks for. Examples: Spring (Java), the container built into .NET, Laravel's service container (PHP) and Hilt on Android, which is built on Dagger and is Jetpack's recommended DI library.

لە گەورەدا لەوانەیە سەدان دروست بکات. کە ئەو بەستنەوەیە بۆ تۆ دەکات: یاساکان یەک جار تۆمار دەکەیت — کاتێک کەسێک Notifierـی پێویست بوو، SmsNotifierـی بدەرێ — و کۆنتەینەرەکە هەر لەگەڵ هەموو ئەوەی داوای دەکات دروست دەکات. نموونەکان: ⁦Spring (Java)⁩، ئەو کۆنتەینەرەی لە ⁦.NET⁩ـدا دروست کراوە، service containerـی ⁦Laravel (PHP)⁩ و Hilt لە Android کە لەسەر Dagger دروست کراوە و پێشنیارکراوی Jetpackـە بۆ DI.

The same wiring with the .NET container (an example, not run here)هەمان بەستنەوە بە کۆنتەینەری ⁦.NET⁩ (نموونەیەک، لێرە جێبەجێ نەکراوە)Program.csCSHARP
// Program.cs: the composition root of a .NET app
builder.Services.AddSingleton<INotifier, SmsNotifier>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<OrderService>();

// The container now builds OrderService(repo, notifier) by itself
// whenever a controller's constructor asks for an OrderService.
Left, a box of rules written in main: Notifier -> SmsNotifier, ProductRepository, OrderService; one line per type. An arrow goes into a gear labelled container, and out to a built graph: OrderService with arrows to repo and SmsNotifier. Bottom: Spring (Java), .NET, Hilt (Android) — only inside the composition root.لە چەپ، سندوقێکی یاسا کە لە main نووسراون: Notifier -> SmsNotifier، ProductRepository، OrderService؛ یەک دێڕ بۆ هەر جۆرێک. تیرێک دەچێتە ناو چەرخێک بە ناوی کۆنتەینەر و دەردەچێت بۆ تۆڕێکی دروستکراو: OrderService بە تیرەوە بۆ repo و SmsNotifier. خوارەوە: Spring (Java)، .NET، Hilt (Android) — تەنیا لە ناو ڕەگی پێکهێناندا.
You register the rules once; the container builds the whole graph.یاساکان یەک جار تۆمار دەکەیت؛ کۆنتەینەرەکە هەموو تۆڕەکە دروست دەکات.CêIT

Python's standard library has no container, and Bazaar Cart's main() is short enough without one. Whatever you use, keep the container inside the composition root: a class that calls the container itself has turned it into a service locator again.

Python کۆنتەینەری نییە و main()ـی Bazaar Cart ئەوەندە کورتە کە پێویستی پێی نییە. هەرچی بەکار بهێنیت، کۆنتەینەرەکە لە ناو بهێڵەوە: کە خۆی کۆنتەینەرەکە بانگ بکات، دیسان کردوویەتی بە .

Lifetimes: one object, one per request, or a new one each timeتەمەنەکان: یەک ئۆبجێکت، یەک بۆ هەر داواکارییەک، یان هەر جارێک نوێ

The composition root also decides how long each object lives. Microsoft's .NET documentation names three lifetimes, and the same ideas appear everywhere:

هەروەها بڕیار دەدات هەر چەند دەژی. بەڵگەنامەکانی ⁦.NET⁩ـی Microsoft سێ تەمەن ناو دەبەن و هەمان بیرۆکەکان لە هەموو شوێنێک دەردەکەون:

  • Singleton: one object for the whole program, such as a cache of product prices (singleton).
  • Scoped: one per request — for example one database session for each customer order that reaches a web server.
  • Transient: a new object each time one is asked for, for small helpers that keep nothing.
  • : یەک بۆ هەموو ، وەک کاشی نرخی کاڵاکان.
  • Scoped: یەک دانە بۆ هەر — بۆ نموونە یەک دانیشتنی بۆ هەر داواکارییەکی کڕیار کە دەگاتە .
  • Transient: هەر جارێک داوا بکرێت نوێ، بۆ یاریدەدەری بچووک کە هیچ هەڵناگرن.
Three lifetimes, decided in main()سێ تەمەن، لە ⁦main()⁩ـدا بڕیاریان لەسەر دەدرێتlifetimes.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""Lifetimes: how often the composition root makes each object."""
from collections import Counter

made = Counter()                     # how many of each class were made


class ProductCache:                  # singleton: one for the whole program
    def __init__(self):
        made["ProductCache"] += 1


class DbSession:                     # scoped: one per customer request
    def __init__(self, cache):
        made["DbSession"] += 1
        self.cache = cache


class PriceFormatter:                # transient: a new one each time it is asked for
    def __init__(self):
        made["PriceFormatter"] += 1

    def show(self, amount):
        return f"{amount:,} IQD"


def handle_request(customer, cache, make_formatter):
    session = DbSession(cache)                       # one for this request only
    goods, fee = make_formatter(), make_formatter()  # two parts ask: two new objects
    print(f"{customer}: goods {goods.show(9_000)}, delivery {fee.show(3_000)}")


def main():                          # the composition root decides every lifetime
    cache = ProductCache()
    for customer in ("Hana", "Rebin", "Lana"):
        handle_request(customer, cache, PriceFormatter)
    print(dict(made))


main()
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 27

One session per request.

یەک دانیشتن بۆ هەر .

Lineدێڕی 28

Each call to make_formatter() makes a new object.

هەر بانگکردنێکی make_formatter() نوێ دروست دەکات.

Lineدێڕی 33

Made once: a singleton.

یەک جار دروست دەکرێت: .

A time line from program starts to program ends, with three customer requests (Hana, Rebin, Lana). Singleton ProductCache: one long bar for the whole run. Scoped DbSession: one bar inside each request, three in all. Transient PriceFormatter: two short bars in each request, six in all. Bottom: never keep a short-lived object inside a long-lived one.هێڵێکی کات لە دەستپێکردنی بەرنامە تا کۆتاییەکەی، بە سێ داواکاریی کڕیار (هانا، ڕێبین، لانا). تاکنموونەی ProductCache: یەک هێڵی درێژ بۆ هەموو ماوەکە. DbSessionـی بۆ هەر داواکارییەک: یەک هێڵ لە ناو هەر داواکارییەکدا، کۆی سێ. PriceFormatterـی هەر جارێک نوێ: دوو هێڵی کورت لە هەر داواکارییەکدا، کۆی شەش. خوارەوە: هەرگیز ئۆبجێکتێکی تەمەنکورت لە ناو ئۆبجێکتێکی تەمەندرێژدا هەڵمەگرە.
One cache, three sessions, six formatters: the lifetime decides how many objects exist.یەک کاش، سێ دانیشتن، شەش فۆرماتکەر: تەمەن دیاری دەکات چەند هەن.CêIT

get_it calls the same ideas singleton, lazy singleton and factory (the three moments in get_it). Microsoft adds one warning: never put a scoped object inside a singleton, or it lives too long and one customer's session leaks into the next order.

⁦get_it⁩ هەمان بیرۆکەکان ناو دەنێت singleton، lazy singleton و factory (سێ کاتەکە لە ⁦get_it⁩). Microsoft ئاگادارییەک زیاد دەکات: هەرگیز scoped مەخە ناو ، دەنا زۆر درێژ دەژی و دانیشتنی کڕیارێک دەگاتە داواکاریی دواتر.

The same idea in Java and other languagesهەمان بیرۆکە لە Java و زمانەکانی تر

Dependency injection is not a Python trick. Here is the notifier in Java 17: an interface (interfaces in Java), two implementations and constructor injection into a final field. main() is the composition root:

فێڵێکی Python نییە. ئەمە ئاگادارکەرەوەکەیە لە ⁦Java 17⁩: (نێوبەستەکان لە Java)، دوو جێبەجێکردن و بۆ ناو خانەیەکی final. main() :

Constructor injection in Java 17تێخستن لە ڕێی دروستکەرەوە لە ⁦Java 17⁩BazaarCart.javaJAVAType in a terminalلە تێرمیناڵدا بینووسە
import java.util.ArrayList;
import java.util.List;

// The contract: anything that can send a short text to a phone.
interface Notifier {
    void send(String phone, String text);
}

class ConsoleNotifier implements Notifier {
    public void send(String phone, String text) {
        System.out.println("[SMS to " + phone + "] " + text);
    }
}

class FakeNotifier implements Notifier {   // for tests: remembers, sends nothing
    final List<String> sent = new ArrayList<>();

    public void send(String phone, String text) {
        sent.add(phone + ": " + text);
    }
}

class OrderService {
    private final Notifier notifier;       // final: set once, never null

    OrderService(Notifier notifier) {      // constructor injection
        this.notifier = notifier;
    }

    int placeOrder(String phone, int goodsTotal) {
        int total = goodsTotal + (goodsTotal >= 50_000 ? 0 : 3_000);  // R4
        notifier.send(phone, "Bazaar Cart: your order of " + total + " IQD is on its way.");
        return total;
    }
}

public class BazaarCart {
    public static void main(String[] args) {          // the composition root
        OrderService shop = new OrderService(new ConsoleNotifier());
        System.out.println("Returned: " + shop.placeOrder("0770 000 0001", 12_000));

        FakeNotifier fake = new FakeNotifier();
        OrderService test = new OrderService(fake);
        test.placeOrder("0770 000 0002", 60_000);
        System.out.println("Fake recorded: " + fake.sent);
    }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 5-7

The shape, as a Java interface.

شێوەکە، وەک Java.

Lineدێڕی 24-28

final: set once in the constructor, never null afterwards.

final: یەک جار لە دادەنرێت و دواتر هەرگیز null نابێت.

Lineدێڕی 38-45

The composition root: the shop gets a ConsoleNotifier, the test check gets a fake.

: دوکانەکە ConsoleNotifier وەردەگرێت و پشکنینەکە ساختەیەک.

Dependency injection in five languages لە پێنج زماندا
LanguageزمانThe shapeشێوەکەInjectionتێخستنContainers and locatorsکۆنتەینەر و دۆزەرەوە
Pythontyping.Protocol or abc.ABCtyping.Protocol یان abc.ABC__init__(self, notifier)none built in; main() is enoughهیچ لە ناو زمانەکەدا نییە؛ main() بەسە
Javainterface NotifierconstructorSpring; Hilt and Dagger on AndroidSpring؛ Hilt و Dagger لە Android
Dart / Flutterabstract class; every class is also an implicit interfaceabstract class؛ هەر ناڕاستەوخۆیشەconstructorProvider; get_it (a locator)Provider؛ ⁦get_it⁩ ()
PHPinterface Notifier__construct(Notifier $notifier)Laravel's service containerservice containerـی Laravel
C#interface INotifierconstructorbuilt into .NETلە ⁦.NET⁩ـدا دروست کراوە

When DI is too muchکەی تێخستنی وابەستەیی زیادە

DI is a tool, not a rule for every line. A 20-line script that prints today's price list needs no injection. And when a constructor asks for seven things, DI is not the problem: the class does too much. The Spring documentation calls a large number of constructor arguments "a bad code smell": split the class.

ئامرازێکە، نەک یاسایەک بۆ هەموو دێڕێک. سکریپتێکی ⁦20⁩ دێڕی کە لیستی نرخی ئەمڕۆ چاپ دەکات پێویستی بە تێخستن نییە. کاتێکیش داوای حەوت شت دەکات، کێشەکە نییە: کاری زۆر دەکات. بەڵگەنامەکانی Spring ژمارەیەکی زۆری بە «بۆنی خراپی کۆد» ناو دەبەن: دابەش بکە.

Left, OrderService( repo, notifier, printer, mailer, clock, logger, payment), labelled: prices, sends, prints, pays. An arrow labelled split leads to two smaller classes: OrderService(repo, notifier), which prices and places orders, and ReceiptService(printer, mailer, clock), which prints and e-mails receipts. Bottom: DI did not cause the problem; it made it visible.لە چەپ، OrderService( repo, notifier, printer, mailer, clock, logger, payment) لەگەڵ تێبینی: نرخ دادەنێت، دەنێرێت، چاپ دەکات، پارە دەدات. تیرێک بە ناوی «دابەشی بکە» دەچێت بۆ دوو کلاسی بچووکتر: OrderService(repo, notifier) کە نرخی داواکاری دادەنێت و تۆماری دەکات و ReceiptService(printer, mailer, clock) کە پسوولە چاپ دەکات و بە ئیمەیڵ دەینێرێت. خوارەوە: تێخستنی وابەستەیی کێشەکەی دروست نەکرد؛ دیاری کرد.
Seven parameters are a message: split the class into smaller ones.حەوت پەیامێکە: بۆ چەند بچووکتر دابەش بکە.CêIT
  • Inject what talks to the outside world (SMS, database, network, the clock, random numbers) and anything you want to swap in tests.
  • Do not inject plain values and pure functions such as round_up_250(), small data classes such as Product, or standard-library helpers such as math.ceil().
  • تێبخە ئەوەی قسە لەگەڵ جیهانی دەرەوە دەکات (، ، ، کاتژمێر، ) و هەر شتێک کە دەتەوێت لە بیگۆڕیت.
  • تێمەخە نرخی سادە، پاکی وەک round_up_250()، بچووکی دراوە وەک Product، یان یاریدەدەرەکانی وەک math.ceil().

Common mistakesهەڵە باوەکان

Exercisesڕاهێنانەکان

ExerciseڕاهێنانInject the clockکاتژمێرەکە تێبخە

Soran's shop is open from 09:00 to 21:00. This class cannot be tested at 23:00 without waiting until the next morning. Change it to constructor injection: it should receive a function that returns the current time, and a test should check 08:59, 09:00 and 21:00.

دوکانەکەی سۆران لە ⁦09:00⁩ تا ⁦21:00⁩ کراوەیە. ئەم ناتوانرێت لە کاتژمێر ⁦23:00⁩ تاقی بکرێتەوە بەبێ چاوەڕوانی تا بەیانی. بیگۆڕە بۆ : دەبێت وەربگرێت کە کاتی ئێستا دەگەڕێنێتەوە و تاقیکردنەوەیەک ⁦08:59⁩، ⁦09:00⁩ و ⁦21:00⁩ بپشکنێت.

The starting codeکۆدی سەرەتاopening_hours.pyPYTHON
from datetime import datetime


class OpeningHours:
    def is_open(self):
        now = datetime.now()             # builds its own clock
        return 9 <= now.hour < 21
Hintئاماژە

A clock can simply be a function: datetime.now in the shop, lambda: datetime(2026, 11, 10, 8, 59) in a test.

کاتژمێر دەتوانێت تەنیا بێت: لە دوکانەکە datetime.now، لە تاقیکردنەوەیەکدا lambda: datetime(2026, 11, 10, 8, 59).

Show answerوەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوopening_hours.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""Soran's opening hours, with the clock injected (and its tests)."""
import unittest
from datetime import datetime


class OpeningHours:
    def __init__(self, now=datetime.now):    # a function that returns the time
        self.now = now

    def is_open(self):
        return 9 <= self.now().hour < 21


def at(hour, minute):                      # a fake clock for one moment
    return lambda: datetime(2026, 11, 10, hour, minute)


class OpeningHoursTest(unittest.TestCase):
    def test_closed_at_08_59(self):
        self.assertFalse(OpeningHours(at(8, 59)).is_open())

    def test_open_at_09_00(self):
        self.assertTrue(OpeningHours(at(9, 0)).is_open())

    def test_closed_at_21_00(self):
        self.assertFalse(OpeningHours(at(21, 0)).is_open())
ExerciseڕاهێنانA fake distance finder for Hawler Rideدۆزەرەوەیەکی ساختەی دووری بۆ Hawler Ride

Hana and Rebin's taxi app charges 2,000 IQD plus 750 IQD per kilometre (made-up prices). The real distance comes from a maps service on the internet. Write a DistanceFinder Protocol with km(start, end), a FareService that receives one, and a test with a fake finder that always answers 4 km.

ئەپی تەکسیی هانا و ڕێبین ⁦2,000⁩ دینار و ⁦750⁩ دینار بۆ هەر کیلۆمەترێک وەردەگرێت (نرخی دروستکراو). دووریی ڕاستەقینە لە نەخشە لەسەر دێت. DistanceFinderـێک وەک Protocol بە km(start, end) بنووسە، FareServiceـێک کە دانەیەکی وەردەگرێت و تاقیکردنەوەیەک بە دۆزەرەوەیەکی ساختە کە هەمیشە 4 کیلۆمەتر وەڵام دەداتەوە.

Show answerوەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوride.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""Hawler Ride fares with an injected distance finder (made-up prices in IQD)."""
import unittest
from typing import Protocol


class DistanceFinder(Protocol):
    def km(self, start: str, end: str) -> float: ...


class FareService:
    BASE, PER_KM = 2_000, 750

    def __init__(self, finder: DistanceFinder) -> None:
        self.finder = finder                 # the real maps service, or a fake

    def fare(self, start: str, end: str) -> int:
        return round(self.BASE + self.PER_KM * self.finder.km(start, end))


class FakeFinder:
    def __init__(self, km):
        self.distance = km
        self.asked = []

    def km(self, start, end):
        self.asked.append((start, end))
        return self.distance


class FareServiceTest(unittest.TestCase):
    def test_four_km_costs_5000(self):
        finder = FakeFinder(4)
        self.assertEqual(FareService(finder).fare("Citadel", "Sami Abdulrahman Park"), 5_000)
        self.assertEqual(finder.asked, [("Citadel", "Sami Abdulrahman Park")])
ExerciseڕاهێنانAsks or receives?داوا دەکات یان وەردەگرێت؟

For each line say: constructor injection, setter injection, method injection, service locator, or none (the class builds it). (a) def __init__(self, repo): self.repo = repo (b) self.mailer = Mailer() (c) printer = Locator.get('printer') (d) def print_receipt(self, order, printer): (e) service.set_clock(clock)

بۆ هەر دێڕێک بڵێ: ، تێخستن لە ڕێی ، تێخستن لە ڕێی ، ، یان هیچیان ( خۆی دروستی دەکات). ⁦(a)⁩ def __init__(self, repo): self.repo = repo ⁦(b)⁩ self.mailer = Mailer() ⁦(c)⁩ printer = Locator.get('printer') ⁦(d)⁩ def print_receipt(self, order, printer): ⁦(e)⁩ service.set_clock(clock)

Show answerوەڵامەکە پیشان بدە

(a) constructor injection; (b) none: the class builds its own mailer; (c) service locator: it asks; (d) method injection: a parameter of one call; (e) setter injection.

⁦(a)⁩ ؛ ⁦(b)⁩ هیچیان: ئیمەیڵنێرەکەی خۆی دروست دەکات؛ ⁦(c)⁩ : داوا دەکات؛ ⁦(d)⁩ تێخستن لە ڕێی : یەک بانگکردن؛ ⁦(e)⁩ تێخستن لە ڕێی .

ExerciseڕاهێنانChoose the lifetimeتەمەنەکە هەڵبژێرە

Choose singleton, scoped or transient for each, and say why: (a) the shop's settings read once from a file; (b) a shopping cart object for one web request; (c) a small object that formats one price as text.

بۆ هەر یەکێک ، scoped یان transient هەڵبژێرە و بڵێ بۆچی: ⁦(a)⁩ ڕێکخستنەکانی دوکان کە یەک جار لە فایلێک دەخوێندرێنەوە؛ ⁦(b)⁩ سەبەتەی کڕین بۆ یەک وێب؛ ⁦(c)⁩ بچووک کە یەک نرخ وەک دەق ڕێک دەخات.

Show answerوەڵامەکە پیشان بدە

(a) singleton: one copy is enough and it never changes while the program runs; (b) scoped: it belongs to one request and must not leak into another customer's request; (c) transient: it keeps nothing, so a new one each time is cheap and safe.

⁦(a)⁩ : یەک دانە بەسە و لە کاتی کارکردنی ناگۆڕێت؛ ⁦(b) scoped⁩: هی یەک و نابێت بچێتە ناو کڕیارێکی تر؛ ⁦(c) transient⁩: هیچ هەڵناگرێت، بۆیە دانەیەکی نوێ هەر جارێک هەرزان و سەلامەتە.

Summaryپوختە

  • A dependency is an object another object needs. A class that creates its own concrete dependency is tightly coupled: hard to test and hard to change.
  • Dependency injection: the class receives what it needs. Prefer constructor injection; setters only for optional parts; method parameters for per-call objects.
  • Depend on a shape: typing.Protocol (checked by mypy before running) or abc.ABC (checked when an object is made). Then the shop, the laptop and the tests can each use their own notifier.
  • The composition root (main()) makes and wires every object once, and decides their lifetimes: singleton, scoped (per request) or transient.
  • Inversion of control: something outside calls your code. Dependency inversion: business rules own the shape; details fit it.
  • A DI container (Spring, .NET, Hilt) automates the wiring — only inside the composition root. A service locator is asked and hides needs; many call it an anti-pattern.
  • Next: the course project puts layers, MVP and DI together in one release.
  • ئەو کە تر پێویستیەتی. کە دیاریکراوەکەی خۆی دروست بکات بە توندی پێکەوە بەستراوە: تاقیکردنەوە و گۆڕینی قورسە.
  • : ئەوەی پێویستیەتی وەریدەگرێت. باشترە؛ تەنیا بۆ پارچەی هەڵبژاردەیی؛ بۆ هەر بانگکردنێک.
  • پشت بە شێوە ببەستە: typing.Protocol (پێش جێبەجێکردن بە mypy دەپشکنرێت) یان abc.ABC (کاتی دروستکردنی دەپشکنرێت). پاشان دوکان، لاپتۆپ و هەر یەکە ئاگادارکەرەوەی خۆی بەکار دەهێنێت.
  • (main()) هەموو یەک جار دروست دەکات و پێکەوەیان دەبەستێت و تەمەنەکانیان دیاری دەکات: ، scoped (بۆ هەر ) یان transient.
  • : شتێک لە دەرەوە کۆدەکەت بانگ دەکات. : یاساکانی کاروبار خاوەنی شێوەکەن؛ وردەکارییەکان لەگەڵی دەگونجێن.
  • (Spring، ⁦.NET⁩، Hilt) بەستنەوەکە خۆکار دەکات — تەنیا لە ناو . داوای لێ دەکرێت و ئەوەی پێویستە دەشارێتەوە؛ زۆر کەس بە ناوی دەبەن.
  • دواتر: پڕۆژەی خولەکە ، MVP و لە یەک کۆ دەکاتەوە.

Lesson complete!وانەکە تەواو بوو!

Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.

Key termsزاراوە سەرەکییەکان

Next lessonوانەی داهاتوو

Case Study: ArtCraft's AI-Built Open-Source Appsنموونەیەکی ڕاستەقینە: ئەپە کۆدکراوەکانی ArtCraft کە بە ژیریی دەستکرد دروستکراون

References and further readingسەرچاوەکان و خوێندنەوەی زیاتر

This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.

Appearanceڕووکار

Modeدۆخ
Styleشێواز
Coloursڕەنگەکان

Saved on this device only.تەنیا لەسەر ئەم ئامێرە پاشەکەوت دەکرێت.