Ачааллж байна...
Өмнөх хичээлд бид pattern-ийн ерөнхий санааг ярилцаж, Python-д тэдгээр хөнгөн байдгийн шалтгааныг тодорхойлсон. Энэ хичээлд эхний pattern-аа бүрэн судална: Strategy. Энэ нь хамгийн энгийн, хамгийн түгээмэл pattern-уудын нэг бөгөөд Python-ий "функц бол утга" санаа түүнийг хэр их хөнгөлдгийг хамгийн тод харуулдаг.
Strategy-ийн шийддэг асуудал нь: нэг ажлыг хийх хэд хэдэн арга байгаа, аль аргыг хэрэглэхийг ажиллах явцад сонгох ёстой, мөн шинэ арга хожим нэмэгдэж болно. Pattern-ийн шийдэл нь арга бүрийг тусдаа, сольж болдог хэсэг болгон гаргаж, ажлыг хийдэг кодыг тэдгээрийн аль нэгийг хүлээн авдаг болгох явдал. Хичээл 3.2-ын key= бол яг ийм: sorted() эрэмбэлэх алгоритмаа мэддэг, харин юугаар харьцуулахыг гаднаас хүлээн авдаг.
Бид бодит жишээгээр явна: онлайн дэлгүүрийн хүргэлтийн төлбөр. Стандарт хүргэлт, түргэн хүргэлт, дэлгүүрээс өөрөө авах, тодорхой дүнгээс дээш захиалгад үнэгүй хүргэлт. Эхлээд хамгийн энгийн, if-elif гинжтэй хувилбарыг бичиж, түүний асуудлуудыг харна. Дараа нь Strategy pattern-ыг сонгодог, class-тай хэлбэрээр товч харуулна, ингэснээр та бусдын кодонд түүнийг таних болно. Эцэст нь Python хувилбарыг бүтээнэ: функцүүдийн dict. Өмнөх хичээлд хэлсэнчлэн pattern асуудлаас эхэлдэг тул бид асуудлаас эхэлнэ.
Шуудангийн газрын үйлчилгээний цонхыг төсөөлөөд үзээрэй. Та илгээмж авчирч, ажилтан түүнийг жинлэж, хэмжинэ. Дараа нь танаас "ямар үйлчилгээгээр илгээх вэ?" гэж асууна: энгийн шуудан, түргэн шуудан, бүртгэлтэй шуудан. Ажилтны ажил, илгээмжийг хүлээн авах, жинлэх, баримт бичих, үргэлж ижил. Ялгаатай нь зөвхөн үнэ тооцох дүрэм.
Шинэ ажилтан ирэхэд түүнд бүх үнийн дүрмийг цээжлүүлэх хэрэггүй. Цонхны хажууд үйлчилгээ бүрийн тариф бичсэн картууд өлгөөтэй байна. Ажилтан зөвхөн "харилцагчийн сонгосон картыг ав, түүгээр тооцоол" гэдгийг мэдэхэд хангалттай. Шинэ үйлчилгээ нэмэгдэхэд шинэ карт өлгөнө: ажилтныг дахин сургах шаардлагагүй, бусад картуудыг өөрчлөх шаардлагагүй.
Strategy pattern бол тэр картуудын систем. Карт бүр нэг стратеги: нэг тооцооны арга. Ажилтан бол стратегийг хэрэглэгч, Gang of Four-ын нэр томьёогоор “context”. Картууд сольж болдог, учир нь бүгд ижил асуултад хариулдаг: "энэ илгээмжийг хүргэх үнэ хэд вэ?" Энэ "ижил асуулт" бол стратегиудын хоорондын гэрээ бөгөөд Бүлэг 8-ын протоколын санаатай ижил.
Захиалгыг Хичээл 7.4-ийн frozen dataclass-аар төлөөлье. Мөнгийг Хичээл 8.1-ийн адил бүхэл төгрөгөөр хадгална:
python
import math
from dataclasses import dataclass
@dataclass(frozen=True)
class Order:
total: int
weight_kg: floatОдоо хүргэлтийн төлбөрийг хамгийн шууд аргаар бичье:
python
def shipping_cost(order, method):
if method == "standard":
extra_kg = max(0, math.ceil(order.weight_kg - 1))
return 3000 + extra_kg * 500
elif method == "express":
extra_kg = max(0, math.ceil(order.weight_kg - 1))
return (3000 + extra_kg * 500) * 2 + 2000
elif method == "pickup":
return 0
else:
raise ValueError(f"Танигдаагүй хүргэлт: {method}")
order = Order(total=85000, weight_kg=2.4)
print(shipping_cost(order, "standard"))
print(shipping_cost(order, "express"))
print(shipping_cost(order, "pickup"))4000
10000
0Стандарт хүргэлт: эхний килограмм 3000 төгрөг, түүнээс хэтэрсэн килограмм бүрт 500. math.ceil() дээш нь дугуйлдаг: 1.4 килограмм илүү бол 2 килограммаар тооцно. Түргэн хүргэлт: стандартын хоёр дахин дээр 2000. Дэлгүүрээс авбал үнэгүй.
Энэ код ажилладаг бөгөөд гурван аргатай байхад бүрэн хүлээн зөвшөөрөгдөхүйц. Өмнөх хичээлийн зарчмаар бол бид pattern-ыг одоо биш, асуудал гарч ирэхэд хэрэглэнэ. Тэгвэл асуудал хэзээ гарч ирэх вэ? Удахгүй маркетингийн баг “100,000 төгрөгөөс дээш захиалгад үнэгүй хүргэлт” урамшуулал хүснэ. Дараа нь "хөдөө орон нутаг руу хүргэлт" нэмэгдэнэ. Дараа нь "шөнийн хүргэлт". Салаа бүр shipping_cost функцийг томруулж, функц бүхэлдээ нэг дэлгэцээс хальж эхэлнэ.
Гурван тодорхой асуудал аль хэдийн харагдаж байна. Нэгдүгээрт, стандарт тооцоолол хоёр газар давхардсан: түргэн хүргэлтийн салаа түүнийг хуулбарласан. Стандартын үнэ өөрчлөгдвөл хоёр газарт засах хэрэгтэй, нэгийг нь мартах эрсдэлтэй. Хоёрдугаарт, шинэ арга нэмэх бүрт ажиллаж байгаа функцийг засах хэрэгтэй: шинэ салаа нэмэх нь хуучин салааг санамсаргүй эвдэж болно. Гуравдугаарт, арга бүрийг тусад нь тестлэх боломжгүй: тест бүр бүхэл функцийг дуудаж, зөв салаа руу орох method string өгөх ёстой.
Gang of Four-ын номын хэв маягаар Strategy-г ингэж бичдэг. Эхлээд бүх стратегийн нийтлэг эцэг class, гэрээг тодорхойлно:
python
class ShippingStrategy:
def cost(self, order):
raise NotImplementedError
class StandardShipping(ShippingStrategy):
def cost(self, order):
extra_kg = max(0, math.ceil(order.weight_kg - 1))
return 3000 + extra_kg * 500
class ExpressShipping(ShippingStrategy):
def cost(self, order):
return StandardShipping().cost(order) * 2 + 2000
class PickupShipping(ShippingStrategy):
def cost(self, order):
return 0
class Checkout:
def __init__(self, strategy):
self.strategy = strategy
def total(self, order):
return order.total + self.strategy.cost(order)
print(Checkout(ExpressShipping()).total(order))95000ShippingStrategy бол гэрээ: "стратеги бүр cost(order) method-той байна". Түүний cost нь NotImplementedError шиддэг, Хичээл 8.1-т NotImplemented-тэй ялгасан тэр exception: "энэ method-ыг удамшсан class бичих ёстой". Стратеги бүр тусдаа class, cost-ийг өөрийнхөөрөө хэрэгжүүлсэн. Checkout бол context: тэр стратегийг хүлээн авч, түүний cost-ийг дууддаг, аль стратеги болохыг мэддэггүй.
Энэ хувилбар if-elif-ийн гурван асуудлыг бүгдийг шийдсэн. Давхардал алга: ExpressShipping стандартыг дуудаж байна. Шинэ арга нэмэх нь шинэ class бичих, Checkout-ийг огт засахгүй. Стратеги бүрийг тусад нь тестлэж болно. Гэвч зардлыг харъя: ганц мөр тооцоололд зориулж class, түүнд ганц method, бүгд нийтлэг эцэг class-аас. Өмнөх хичээлийн Түгээмэл алдаанууд хэсгийг санаарай: "ганц method-той class ихэвчлэн функц байх ёстой". Энэ хувилбар C++ ба Java-д зүйн хэрэг байсан, учир нь тэнд функцийг утга болгон дамжуулах боломжгүй байсан. Python-д тэр хязгаарлалт байхгүй.
Стратеги бүр ганц тооцоолол хийдэг бол түүнийг функц болгоно. Бүлэг 3-аас хойш бид функцийг утга мэт map-д, key-д, decorator-т дамжуулж ирсэн. Одоо түүнийг dict-д хадгална:
python
def standard_cost(order):
extra_kg = max(0, math.ceil(order.weight_kg - 1))
return 3000 + extra_kg * 500
def express_cost(order):
return standard_cost(order) * 2 + 2000
def pickup_cost(order):
return 0
SHIPPING_METHODS = {
"standard": standard_cost,
"express": express_cost,
"pickup": pickup_cost,
}
def shipping_cost(order, method):
strategy = SHIPPING_METHODS.get(method)
if strategy is None:
options = ", ".join(sorted(SHIPPING_METHODS))
raise ValueError(f"Танигдаагүй хүргэлт '{method}'. Боломжтой: {options}")
return strategy(order)
print(shipping_cost(order, "standard"))
print(shipping_cost(order, "express"))
shipping_cost(order, "expres")4000
10000
Traceback (most recent call last):
...
ValueError: Танигдаагүй хүргэлт 'expres'. Боломжтой: express, pickup, standardСтратеги бүр энгийн функц бөгөөд бүгд ижил гэрээг дагадаг: нэг Order хүлээн авч, бүхэл тоо буцаадаг. SHIPPING_METHODS dict нь нэрийг функцтэй холбодог: зүйрлэлийн цонхны хажуу дахь картын самбар. Утгууд нь функцийн нэрс, хаалтгүй. Бид функцүүдийг дуудаагүй, өөрсдийг нь хадгалсан. shipping_cost бол context: тэр dict-ээс стратегийг хайж, олдвол дуудна, олдохгүй бол боломжтой сонголтуудыг жагсаасан тодорхой алдаа өгнө. Хичээл 6.4-т бидний сангийн "хэрэглэгчийн алдааг урьдчилан таамаглах" зарчим энд ч хэрэгжиж байна: “expres” гэсэн үсгийн алдааг хэрэглэгч шууд засаж чадна.
Trace: стратеги сонгогдох нь
shipping_cost(order, "express") дуудагдахад:
Алхам 1 (dict-ээс хайх). SHIPPING_METHODS.get("express") нь express_cost функц объектыг буцаана. shipping_cost-ийн frame-д: order, method = “express”, strategy = express_cost функц.
Алхам 2 (стратеги дуудагдах). strategy(order). strategy нэр express_cost функцийг заадаг тул express_cost(order) дуудагдана. shipping_cost түүнийг ямар нэрээр мэддэг нь хамаагүй: Хичээл 8.3-ын дүрмээр ямар ч callable хаалтаар дуудагдана.
Алхам 3 (express_cost дотор). standard_cost(order)-ийг дуудаж 4000 авна, 4000 * 2 + 2000 = 10000 буцаана.
Алхам 4 (буцах). 10000 shipping_cost-оор дамжиж буцна.
Энэ trace-д if-elif огт алга. "Аль аргыг сонгох вэ" гэсэн шийдвэр dict-ийн хайлт болж хувирсан. Мөн class хувилбартай харьцуулбал: StandardShipping() гэх мэт объект үүсгэх шаардлагагүй, .cost гэх method-ын нэр шаардлагагүй. Стратеги бол функц, түүнийг дуудах нь хаалт.
Одоо асуудлууд хэрхэн шийдэгдснийг шалгая. Давхардал: express_cost standard_cost-ийг дууддаг. Стратеги бүрийг тусад нь тестлэх: assert standard_cost(Order(0, 2.4)) == 4000, shipping_cost-оор дамжих шаардлагагүй. Шинэ арга нэмэх: dict-д нэг мөр, shipping_cost-ийг огт засахгүй.
Жишээ 1: тохируулсан стратеги
Одоо маркетингийн багийн хүсэлт: 100,000 төгрөгөөс дээш захиалгад үнэгүй хүргэлт, бусад тохиолдолд стандарт. Энэ стратеги тохиргоотой: босго дүн ба босгоос доош үед хэрэглэх өөр стратеги. Бүлэг 4-ийн function factory яг үүнд зориулагдсан:
python
def free_over(threshold, fallback):
def cost(order):
if order.total >= threshold:
return 0
return fallback(order)
return cost
SHIPPING_METHODS["promo"] = free_over(100_000, standard_cost)
small = Order(total=85_000, weight_kg=2.4)
large = Order(total=120_000, weight_kg=2.4)
print(shipping_cost(small, "promo"))
print(shipping_cost(large, "promo"))4000
0free_over стратеги үйлдвэрлэдэг: босго ба fallback-ийг үүргэвчиндээ хадгалсан cost функцийг буцаана. Хичээл 4.3-ын тохируулсан форматлагчтай яг ижил загвар. Сонирхолтой нь fallback өөрөө стратеги: free_over бол стратегийг ороодог стратеги. Бүлэг 5-ын decorator-ын санаа: нэмэлт зан чанар, "босгоос дээш бол үнэгүй", ороож буй функцийн зан чанарыг өөрчлөхгүйгээр нэмэгдсэн. Мөн Хичээл 8.3-ын AllOf шиг жижиг хэсгүүдээс том хэсэг бүтээх. Тэгэхээр түргэн хүргэлтийн урамшуулал ч бичих шаардлагагүй: free_over(200_000, express_cost).
Хэрэв стратегийн тохиргоо гаднаас харагдах, эсвэл стратеги нэмэлт мэдээлэл агуулах ёстой бол Хичээл 8.3-ын сургамж хүчинтэй: callable объект. Жишээлбэл, урамшууллын стратеги хэдэн захиалгад үнэгүй хүргэлт өгснөө тоолж, маркетингийн тайланд хэрэглэгдэх ёстой бол __call__-тэй class. shipping_cost ялгааг мэдэхгүй, учир нь хоёулаа callable.
Жишээ 2: бүртгэлийн decorator
SHIPPING_METHODS dict-ийг гараар бөглөх нь нэг сул талтай: шинэ стратеги функц бичээд dict-д нэмэхээ мартах. Хичээл 5.2-ын бүртгэлийн систем ба Хичээл 5.3-ын argument-тай decorator-ыг нийлүүлбэл бүртгэл функцийн хажууд, нэг газарт хийгдэнэ:
python
SHIPPING_METHODS = {}
def shipping_method(name):
def register(func):
SHIPPING_METHODS[name] = func
return func
return register
@shipping_method("standard")
def standard_cost(order):
extra_kg = max(0, math.ceil(order.weight_kg - 1))
return 3000 + extra_kg * 500
@shipping_method("express")
def express_cost(order):
return standard_cost(order) * 2 + 2000
@shipping_method("pickup")
def pickup_cost(order):
return 0
print(sorted(SHIPPING_METHODS))['express', 'pickup', 'standard']shipping_method бол гурван давхар бүтэцтэй боловч хамгийн дотоод давхарга wrapper биш, register: тэр функцийг огт ороохгүй, зөвхөн dict-д бүртгээд өөрчлөлтгүй буцаана. Хичээл 5.4-ийн "засаж, бүртгээд, ижлийг буцаах" decorator-ын хэлбэр. Функц өөрчлөгдөөгүй тул standard_cost-ийг express_cost дотроос шууд дуудах нь асуудалгүй. Одоо шинэ хүргэлтийн арга нэмэхэд ганцхан газар өөрчлөгдөнө: шинэ функц, дээр нь нэг decorator мөр. shipping_cost ба бусад стратегиуд хөндөгдөхгүй.
Энэ шинж чанарыг програм хангамжийн дизайнд “open/closed principle” буюу "нээлттэй/хаалттай зарчим" гэж нэрлэдэг: код өргөтгөлд нээлттэй, шинэ зан чанар нэмж болно, өөрчлөлтөд хаалттай, ажиллаж байгаа кодыг засах шаардлагагүй. if-elif хувилбар энэ зарчмыг зөрчиж байсан: шинэ арга бүр shipping_cost-ийг засахыг шаарддаг байсан. Course 3-ын FastAPI-ийн @app.get("/path") нь яг ижил хэлбэр: endpoint бүр өөрийн decorator-оор бүртгэгддэг, FastAPI-ийн дотоод кодыг хэн ч засдаггүй.
Жишээ 3: тестлэгдэх байдал
Strategy-ийн хамгийн чухал ашиг тусын нэг бол тестлэгдэх байдал. Хичээл 6.4-т бид report ба sleep функцүүдийг тохиргоогоор өгч, тестэд хуурамч функцүүдийг оруулсан. Тэр бол үнэндээ Strategy pattern байсан: хэмжигч хэрэгслийн "мэдээллийг хаашаа гаргах" ба "хэрхэн хүлээх" зан чанарыг гаднаас сольж болдог болгосон. Төлбөрийн тооцоонд ижил санааг хэрэглэе:
python
def checkout_total(order, strategy):
return order.total + strategy(order)
def test_checkout_adds_shipping():
order = Order(total=50_000, weight_kg=1.0)
assert checkout_total(order, strategy=lambda o: 1234) == 51_234
def test_standard_cost_counts_extra_kilograms():
assert standard_cost(Order(total=0, weight_kg=1.0)) == 3000
assert standard_cost(Order(total=0, weight_kg=2.4)) == 4000
def test_promo_is_free_above_threshold():
promo = free_over(100_000, standard_cost)
assert promo(Order(total=100_000, weight_kg=5.0)) == 0checkout_total стратегийг argument болгон авна. Эхний тест жинхэнэ стратегийн оронд lambda өгсөн: тэр үргэлж 1234 буцаадаг. Тэгэхээр тест зөвхөн checkout_total-ийн логикийг, "захиалгын дүн дээр хүргэлтийг нэмэх", шалгаж байна, хүргэлтийн тооцооны нарийн дүрмээс хамааралгүй. Үлдсэн хоёр тест стратегиудыг тусад нь шалгана. Стратегийн дүрэм өөрчлөгдвөл зөвхөн түүний тест өөрчлөгдөнө. if-elif хувилбарт ийм тусгаарлалт боломжгүй байсан.
Strategy хэзээ хэрэггүй вэ
Шударга байцгаая. Хэрэв таны програмд хоёр арга бий, тэдгээр нь хэзээ ч өөрчлөгдөхгүй, нэмэгдэхгүй бол ганц if хамгийн тодорхой код: cost = 0 if pickup else standard_cost(order). Strategy дүрэм олширч, өөрчлөгдөж, нэмэгдэж эхлэхэд, эсвэл дүрмүүдийг тусад нь тестлэх шаардлага гарахад л зардлаа нөхдөг. Анхны жишээний if-elif гурван салаатайгаар зүгээр байсан. Асуудал нь дөрөв, тав дахь салаа нэмэгдэж, давхардал үүсэхэд л эхэлсэн. Тэр мөчид бид dict хувилбар руу дахин бүтэцлэсэн: өмнөх хичээлд хэлсэн эрүүл дараалал.
Class хувилбар хэзээ зөв вэ? Стратеги ганц үйлдэл биш, хэд хэдэн холбоотой үйлдэлтэй үед. Жишээлбэл хүргэлтийн арга бүр үнэ тооцохоос гадна хүргэх хугацаа, хэрэглэгчид харуулах нэр, байгаа эсэхийн шалгалттай бол тэдгээрийг нэг объектод нэгтгэх нь утгатай. Тэр үед ч Python-д нийтлэг эцэг class заавал биш: duck typing-ийн ачаар ижил method-уудтай ямар ч объект ажиллана. Эцэг class ихэвчлэн гэрээг баримтжуулах үүрэгтэй. Хичээл 10.3-т бид энэ гэрээг Protocol-оор албан ёсоор, удамшилгүйгээр тодорхойлох аргыг сурна.
Хүсвэл Жишээ 2-ын бүртгэлтэй хувилбарт “rural” буюу хөдөө орон нутгийн хүргэлт нэмж үзээрэй: стандартаас 8000 төгрөгөөр илүү. shipping_cost-ийг огт засалгүйгээр ажиллаж байгааг шалгаарай. Сонирхвол free_over-ийг давхарлаж, free_over(200_000, free_over(100_000, express_cost)) юу хийх вэ гэдгийг урьдчилан таамаглаад шалгаж болно. Мөн маркетингийн баг "урамшуулал хэдэн захиалгад хэрэглэгдсэн бэ" гэж асуувал free_over-ийг Хичээл 8.3-ын загвараар __call__-тэй class болгож, тоолуур нэмж, shipping_cost-ийн кодыг өөрчлөх шаардлагагүй хэвээр байгааг харах нь closure ба callable объектын сонголтыг бодит нөхцөлд туршина. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
TypeError: ‘int’ object is not callable. “int объектыг дуудах боломжгүй” гэсэн утгатай. dict-д функцийн оронд түүний үр дүнг хадгалсан үед гарна: {"pickup": pickup_cost(order)}. Энэ мөр dict үүсэх мөчид pickup_cost-ийг дуудаж, 0-ийг хадгалсан. Дараа нь shipping_cost strategy(order) гэж 0-ийг дуудах гэж оролдоно. Хичээл 5.1, 6.1, 7.4-ийн "функц ба түүний дуудлага" ялгаа энд ч ижил: стратегийн самбарт картыг өлгө, картаар тооцоолсон үнийг биш.
TypeError: pickup_cost() takes 0 positional arguments but 1 was given. “pickup_cost() байрлалын 0 argument авдаг боловч 1 ирлээ” гэсэн утгатай. Стратеги гэрээг зөрчсөн үед гарна. pickup_cost захиалга хэрэглэдэггүй тул түүнийг def pickup_cost(): гэж параметргүй бичих уруу таталт гардаг. Гэвч shipping_cost бүх стратегийг ижил хэлбэрээр, strategy(order) гэж дууддаг. Гэрээ бүх стратегид хамаатай: хэрэглэхгүй байсан ч order хүлээн авна. Энэ бол Strategy pattern-ын гол дүрэм: стратегиуд сольж болдог байх ёстой бөгөөд энэ нь ижил гарын үсэгтэй байхыг шаарддаг.
ValueError: Танигдаагүй хүргэлт ‘expres’. Боломжтой: express, pickup, standard. Энэ бол бидний бичсэн мессеж. Шийдэл нь мессеждээ: зөв нэрийг сонгох. Хэрэв бид SHIPPING_METHODS[method] гэж шууд хандсан бол “KeyError: ‘expres’” гэсэн, юу хийхийг хэлдэггүй алдаа гарах байсан. Стратегийг нэрээр сонгодог газар боломжтой нэрсийг харуулах нь хэрэглэгчид маш их тусалдаг.
NotImplementedError. Энэ нь class хувилбарт гарна. Хэрэв удамшсан стратеги class cost method-ыг бичихээ мартсан, эсвэл нэрийг нь буруу бичсэн бол эцэг class-ийн cost ажиллаж, NotImplementedError шиднэ. Traceback ShippingStrategy.cost-ийг заана. Энэ нь "та энэ method-ыг хэрэгжүүлэх ёстой байсан" гэсэн дохио. Хичээл 8.1-ийн ялгааг санаарай: NotImplementedError бол шидэгддэг exception, NotImplemented бол operator method-ын буцаах утга.
Стратегийн өөрчлөлт бусдад нөлөөлөх. Мессежгүй алдаа. Хэрэв стратегиуд ямар нэг өөрчлөгдөж болох нийтлэг өгөгдлийг хуваалцвал, жишээлбэл модулийн түвшний тарифын dict-ийг өөрчилдөг бол, нэг стратегийг ажиллуулах нь нөгөөгийн үр дүнг өөрчилж болно. Хичээл 7.3-ын урхи стратеги хэлбэрээр. Стратегиуд аль болох цэвэр байх ёстой: зөвхөн өгсөн order болон өөрийн тохиргоогоос хамаарч, юуг ч өөрчлөхгүйгээр үр дүн буцаах. Цэвэр стратеги бүр тестлэхэд хялбар, мөн Хичээл 3.3-ын lru_cache-ээр кэшлэх боломжтой, frozen Order-ийн ачаар.
Strategy pattern нь нэг ажлыг хийх хэд хэдэн аргыг тусдаа, сольж болдог хэсэг болгож, ажлыг хийдэг кодыг тэдгээрийн аль нэгийг хүлээн авдаг болгодог. Энэ нь if-elif гинжийн давхардал, ажиллаж байгаа кодыг засах шаардлага, тусгаарлан тестлэх боломжгүй байдлыг шийддэг. Сонгодог хэлбэр нь нийтлэг эцэг class ба стратеги бүрт тусдаа class, гэвч ганц method-той class-ууд Python-д илүүдэл. Python хэлбэр нь стратеги бүрийг ижил гарын үсэгтэй функц болгож, тэдгээрийг нэрээр dict-д хадгалж, context нь dict-ээс хайж дуудах. Тохиргоотой стратегийг function factory, ил төлөвтэйг __call__-тэй объектоор үүсгэнэ. Стратеги өөр стратегийг ороож болно. Бүртгэлийн decorator шинэ стратегийг ганц газарт нэмэх боломж олгож, кодыг өргөтгөлд нээлттэй, өөрчлөлтөд хаалттай болгодог. Стратегийг argument болгон авдаг код тестэд хуурамч стратеги хүлээн авч, тусгаарлан шалгагдана. Хоёр тогтмол арга байвал энгийн if хангалттай. Стратеги хэд хэдэн холбоотой үйлдэлтэй бол class зөв.
Strategy нь "юу хийх" сонголтыг гадагш гаргасан. Дараагийн хичээлд өөр төрлийн сонголт: “аль объектыг үүсгэх”. Хичээл 7.1-т classmethod туслах хаалганууд нэг class-ийн объектыг өөр өөр хэлбэрийн өгөгдлөөс үүсгэдэг байсан. Хэрэв тохиргооны файлд “csv” эсвэл “json” гэж бичигдсэнээр огт өөр class-ийн объект үүсгэх ёстой бол яах вэ? Энэ бол Factory pattern-ын асуудал бөгөөд энэ хичээлийн функцүүдийн dict-ийн санаа тэнд ч ашиглагдана, гэхдээ dict-ийн утга нь функц биш, class байна.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.