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.
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 دەخوێندرێتەوە (هەرگیز نهێنی کۆمیت مەکە). بەشی ناردنە ڕاستەقینەکە لێرە پیشان دراوە بەڵام جێبەجێ ناکرێت: پێویستی بە دابینکەرێکی ڕاستەقینە هەیە و پارەی تێدەچێت.
"""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 moneyExplain 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ـی خۆی دروست دەکات. وێنەکە پیشان دەدات ئەمە بۆ هەر کەسێک کە کۆدەکە جێبەجێ دەکات چ واتایەکی هەیە.
A service that builds its own notifierخزمەتگوزارییەک کە ئاگادارکەرەوەکەی خۆی دروست دەکات
Every test fails, before it checks a ruleهەموو تاقیکردنەوەکانی یەکە شکست دەهێنن، پێش ئەوەی یاسایەک بپشکنن
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.totalDilan, 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:
دیلان، ، شەش تاقیکردنەوەکەی وانەی لەسەر لاپتۆپەکەی جێبەجێ دەکات. کلیلی نییە و نابێت هەشیبێت. دەرئەنجامەکە کورت کراوەتەوە؛ پێنج هەڵەکەی تر هەمان شتن:
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 سێ ڕێگە بۆ بەدەستهێنانی دەژمێرێت: دروستی بکات، لە شوێنێکەوە بیهێنێت، یان وەک وەریبگرێت. تەنیا سێیەمیان .
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.
گەراجەکە هەموو بەیانییەک ئۆتۆمبێلێک دەدات؛ بە زۆرجار یەک جار پارچەکانی وەردەگرێت، کاتی دروستبوونی.
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 دەگۆڕێن:
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ـە کە تەنیا نامەکە چاپ دەکات:
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}")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)[SMS to 0770 000 0001] Bazaar Cart: your order of 15,000 IQD is on its way. Returned: 15000
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 زیاد کرا):
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 NotifierExplain 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: 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")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 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 — یان تێکچوونەکە — پێت دەڵێت.
send(phone, text) fits the Notifier socket.هەر send(phone, text)ـی هەبێت لەگەڵ پریزی Notifier دەگونجێت.CêITDepend 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 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()[SMS to 0770 000 0001] It works.
Traceback (most recent call last):
File "~/se/bazaar-cart/notifier_abc.py", line 22, in <module>
EmailSender()
~~~~~~~~~~~^^
TypeError: Can't instantiate abstract class EmailSender without an implementation for abstract method 'send'| Questionپرسیار | typing.Protocol | abc.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خێزانێک لە خۆت کە کۆدی هاوبەشیان هەیە |
Protocol لەلایەن ئامرازێکەوە پێش جێبەجێکردنی دەپشکنرێت؛ ABC لەلایەن Pythonـەوە کاتی دروستکردنی .CêITOne 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 کە تەنیا نامەکان هەڵدەگرێت — ئۆبجێکتێکی ساختە.
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.
هەردوو ساختەکە لە ڕێی تێبخە.
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 وەسف دەکات کە ئەمڕۆ کەم بەکار دێت. لە کۆدی ڕۆژانەدا سێیەم شێوەی باو سادەی :
"""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)[SMS to 0770 000 0001] Order of 15,000 IQD confirmed.
[SMS to 0770 000 0002] Order of 9,000 IQD confirmed.
[SMS to 0770 000 0003] Order of 6,000 IQD confirmed.
Traceback (most recent call last):
File "~/se/bazaar-cart/three_forms.py", line 38, in <module>
forgot.confirm("0770 000 0004", 3_000)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "~/se/bazaar-cart/three_forms.py", line 21, in confirm
self.notifier.send(phone, f"Order of {total:,} IQD confirmed.")
^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'send'Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 37-38
Nobody called
set_notifier(), soself.notifieris stillNone.کەس
set_notifier()ـی بانگ نەکرد، بۆیەself.notifierهێشتاNoneـە.
- 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
AttributeErrorshows. 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 خۆی دروست دەکرد؛ ئێستا تەنیا وەریدەگرێت:
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)"""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()

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 دەبێت لەگەڵ شێوەی بگونجێت، نەک بە پێچەوانەوە. تیرەکە لە کۆدەکەدا هەڵدەگەڕێتەوە؛ لە بانگکردنەکە هێشتا لە دەچێت بۆ ناردەری .
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, 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")KeyError: 'notifier' Sumac left in stock: 4,500 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 نەرمترە: لە ناو خۆتدا قبووڵکراوە؛ بۆ ئەو کەسانی تر دووپاتە بەکاریان دەهێننەوە، تێخستن باشترە.
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.
// 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.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: هەر جارێک داوا بکرێت نوێ، بۆ یاریدەدەری بچووک کە هیچ هەڵناگرن.
"""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()Hana: goods 9,000 IQD, delivery 3,000 IQD
Rebin: goods 9,000 IQD, delivery 3,000 IQD
Lana: goods 9,000 IQD, delivery 3,000 IQD
{'ProductCache': 1, 'DbSession': 3, 'PriceFormatter': 6}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.
یەک جار دروست دەکرێت: .
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() :
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);
}
}[SMS to 0770 000 0001] Bazaar Cart: your order of 15000 IQD is on its way. Returned: 15000 Fake recorded: [0770 000 0002: Bazaar Cart: your order of 60000 IQD is on its way.]
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 وەردەگرێت و پشکنینەکە ساختەیەک.
| Languageزمان | The shapeشێوەکە | Injectionتێخستن | Containers and locatorsکۆنتەینەر و دۆزەرەوە |
|---|---|---|---|
| Python | typing.Protocol or abc.ABCtyping.Protocol یان abc.ABC | __init__(self, notifier) | none built in; main() is enoughهیچ لە ناو زمانەکەدا نییە؛ main() بەسە |
| Java | interface Notifier | constructor | Spring; Hilt and Dagger on AndroidSpring؛ Hilt و Dagger لە Android |
| Dart / Flutter | abstract class; every class is also an implicit interfaceabstract class؛ هەر ناڕاستەوخۆیشە | constructor | Provider; get_it (a locator)Provider؛ get_it () |
| PHP | interface Notifier | __construct(Notifier $notifier) | Laravel's service containerservice containerـی Laravel |
| C# | interface INotifier | constructor | built 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 ژمارەیەکی زۆری بە «بۆنی خراپی کۆد» ناو دەبەن: دابەش بکە.
- 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 asProduct, or standard-library helpers such asmath.ceil().
- تێبخە ئەوەی قسە لەگەڵ جیهانی دەرەوە دەکات (، ، ، کاتژمێر، ) و هەر شتێک کە دەتەوێت لە بیگۆڕیت.
- تێمەخە نرخی سادە، پاکی وەک
round_up_250()، بچووکی دراوە وەکProduct، یان یاریدەدەرەکانی وەکmath.ceil().
Common mistakesهەڵە باوەکان
Exercisesڕاهێنانەکان
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 بپشکنێت.
from datetime import datetime
class OpeningHours:
def is_open(self):
now = datetime.now() # builds its own clock
return 9 <= now.hour < 21Hintئاماژە
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وەڵامەکە پیشان بدە
"""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())test_closed_at_08_59 (ex_opening_hours.OpeningHoursTest.test_closed_at_08_59) ... ok test_closed_at_21_00 (ex_opening_hours.OpeningHoursTest.test_closed_at_21_00) ... ok test_open_at_09_00 (ex_opening_hours.OpeningHoursTest.test_open_at_09_00) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.000s OK
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وەڵامەکە پیشان بدە
"""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")])test_four_km_costs_5000 (ex_ride.FareServiceTest.test_four_km_costs_5000) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
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) تێخستن لە ڕێی .
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) orabc.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زاراوە سەرەکییەکان
Case Study: ArtCraft's AI-Built Open-Source Appsنموونەیەکی ڕاستەقینە: ئەپە کۆدکراوەکانی ArtCraft کە بە ژیریی دەستکرد دروستکراون
Related lecturesوانە پەیوەندیدارەکان
- Automated Testing: Code That Checks Your Codeتاقیکردنەوەی خۆکار: کۆدێک کە کۆدەکەت دەپشکنێت
- Course Project: Ship Bazaar Cart 1.0.0پڕۆژەی خولەکە: Bazaar Cart 1.0.0 بڵاو بکەرەوە
- Requirements and Design: Deciding What to Buildپێداویستییەکان و دیزاین: بڕیاردان لەسەر ئەوەی چی دروست بکەین
- State Management with Providerبەڕێوەبردنی دۆخ بە Provider
- Organising an MVVM Project: Folders, Services and get_itڕێکخستنی پڕۆژەیەکی MVVM: فۆڵدەرەکان، خزمەتگوزارییەکان و get_it
- MVVM in Flutter with ProviderMVVM لە Flutterـدا بە Provider
- Abstract Classes, Interfaces, Inner Classes and Enumsکلاسە ڕووتەکان، نێوبەستەکان، کلاسە ناوەکییەکان و ئینەمەکان
- Inheritance, Interfaces and Traitsبۆماوە، نێوبەستەکان و ترەیتەکان
- Classes and Objectsکلاسەکان و ئۆبجێکتەکان
References and further readingسەرچاوەکان و خوێندنەوەی زیاتر
- Martin Fowler: Inversion of Control Containers and the Dependency Injection pattern (23 January 2004)
- Martin Fowler: InversionOfControl (bliki, 2005; the Hollywood principle)
- Python documentation: typing.Protocol and runtime_checkable (PEP 544)
- Android Developers: Dependency injection in Android (three ways to get an object, service locator, Hilt)
- Microsoft Learn: Dependency injection in .NET
- Microsoft Learn: Service lifetimes (transient, scoped, singleton)
- Microsoft Learn: Architectural principles (dependency inversion, explicit dependencies)
- Mark Seemann: Composition Root (2011)
- Mark Seemann: Service Locator is an Anti-Pattern (2010)
- Spring Framework reference: Dependency injection (constructor-based or setter-based DI?)
- Laravel documentation: Service Container
- Dart: Classes (implicit interfaces)
- Wikipedia: Dependency inversion principle (Robert C. Martin, 1996)
This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.