Ачааллж байна...
Энэ хичээлд шинэ бичлэг байхгүй. Ганц ч шинэ түлхүүр үг, шинэ синтакс сурахгүй.
Энэ бол шударга зөвлөгөөний хичээл.
Өмнөх гурван хичээлд бид inheritance-ийг сурсан бөгөөд тэр нь гайхалтай хүчирхэг зүйл мэт харагдсан. Давхардал арилдаг, шинэ class нэмэхэд хуучин код өөрчлөгддөггүй, polymorphism нь илбэ шиг ажилладаг.
Тэр бүхэн ҮНЭН. Гэхдээ бүтэн үнэн биш.
Бүтэн үнэн нь ийм: inheritance бол программистуудын хамгийн олон ХЭТРҮҮЛЭН ашигладаг хэрэгсэл юм. Шинэ зүйл сурсан хүн бүхэн түүнийг хаа сайгүй ашиглах гэж оролддог — тэр нь маш байгалийн. Гэвч буруу газарт хэрэглэсэн inheritance нь код бичихийг хөнгөвчлөхийн оронд ХҮНДРҮҮЛДЭГ: ойлгомжгүй, хэврэг, засварлахад аймшигтай кодыг үүсгэдэг.
Энэ хичээлийн зорилго бол танд тэр аюулыг харуулж, хэзээ inheritance ашиглах, хэзээ БОЛИХ вэ гэсэн энгийн дүрэм өгөх явдал юм.
Хамгийн энгийн, хамгийн найдвартай дүрэм нь энэ:
Inheritance-ыг зөвхөн "Х БОЛ Y" гэж хэлж болох үед ашигла.
Англи хэлээр үүнийг "is-a test" гэж нэрлэдэг. Монголоор "бол" гэсэн тест.
Жишээ:
"Ном бол зүйл." → class Nom(Zuil) — зөв
"Нохой бол амьтан." → class Nokhoi(Amitan) — зөв
"Аудио ном бол ном." → class AudioNom(Nom) — зөв
"Захирал бол ажилтан." → class Zakhiral(Ajiltan) — зөв
Эдгээр бүгд байгалийн сонсогдож байна. Тэдгээр нь ЖИНХЭНЭ ангиллын харилцаа юм.
Одоо буруу жишээнүүд:
"Машин бол хөдөлгүүр." → БУРУУ. Машин нь хөдөлгүүр БИШ. Машинд хөдөлгүүр БАЙДАГ.
"Хүн бол хаяг." → БУРУУ. Хүн хаяг биш. Хүнд хаяг байдаг.
"Номын сан бол ном." → БУРУУ. Номын сан ном биш. Номын санд номнууд байдаг.
Ялгааг мэдрэв үү? Эхний бүлэгт "бол", хоёр дахь бүлэгт "байдаг".
"Байдаг" гэж хэлж байвал — inheritance БИШ. Тэр бол өөр зүйл, түүнийг доор үзнэ.
Практикт энэ алдаа ямар харагддагийг харъя.
Танд Khodolguur (хөдөлгүүр) class байна:
python
class Khodolguur:
def __init__(self, khuchin_chadal: int) -> None:
self.khuchin_chadal = khuchin_chadal
self.asaasan = False
def asaakh(self) -> None:
self.asaasan = True
print("Хөдөлгүүр асав")
def untraakh(self) -> None:
self.asaasan = False
print("Хөдөлгүүр унтарлаа")Одоо машин class бичих хэрэгтэй. Машинд хөдөлгүүр байдаг тул... inheritance ашиглах уу?
python
class Mashin(Khodolguur): # БУРУУ!
def __init__(self, ner: str, khuchin_chadal: int) -> None:
super().__init__(khuchin_chadal)
self.ner = nerЭнэ АЖИЛЛАНА:
python
mashin = Mashin("Prius", 98)
mashin.asaakh()
print(mashin.khuchin_chadal)Хөдөлгүүр асав
98Гэвч энэ бол буруу дизайн. Хэдэн шалтгаанаар.
Шалтгаан 1: логикгүй. isinstance(mashin, Khodolguur) нь True буцаана. Өөрөөр хэлбэл "машин бол хөдөлгүүр" гэж код хэлж байна. Тэр нь худал.
Шалтгаан 2: буруу зүйлсийг өвлөнө. Mashin нь Khodolguur -ийн БҮХ зүйлийг авсан. Ирээдүйд Khodolguur -т khiiten_shingen_solikh() гэсэн method нэмвэл — машин ч бас түүнийг авна. Тэр нь утгагүй.
Шалтгаан 3: хоёр хөдөлгүүртэй машин бол? Ачааны машин, эрлийз машин — олон хөдөлгүүртэй байж болно. Inheritance-ээр үүнийг илэрхийлэх БОЛОМЖГҮЙ. Class нь өөрөөсөө хоёр удаа өвлөж чадахгүй.
Зөв шийдэл: composition.
Composition гэдэг нь энгийн санаа: нэг объект дотор өөр объектыг ХАДГАЛАХ.
Тусгай синтакс байхгүй. Ямар ч түлхүүр үг байхгүй. Зөвхөн self.something = OtherClass(...).
python
class Khodolguur:
def __init__(self, khuchin_chadal: int) -> None:
self.khuchin_chadal = khuchin_chadal
self.asaasan = False
def asaakh(self) -> None:
self.asaasan = True
print("Хөдөлгүүр асав")
def untraakh(self) -> None:
self.asaasan = False
print("Хөдөлгүүр унтарлаа")
class Mashin:
def __init__(self, ner: str, khuchin_chadal: int) -> None:
self.ner = ner
self.khodolguur = Khodolguur(khuchin_chadal) # ХАДГАЛЖ байна
def khodolgokh(self) -> None:
self.khodolguur.asaakh()
print(f"{self.ner} явж эхлэв")
def zogsookh(self) -> None:
self.khodolguur.untraakh()
print(f"{self.ner} зогслоо")
mashin = Mashin("Prius", 98)
mashin.khodolgokh()
print(f"Хүчин чадал: {mashin.khodolguur.khuchin_chadal}")
mashin.zogsookh()Хөдөлгүүр асав
Prius явж эхлэв
Хүчин чадал: 98
Prius зогслооself.khodolguur = Khodolguur(khuchin_chadal) — энэ бол composition-ы бүх мөн чанар. Mashin нь Khodolguur -ээс өвлөөгүй, харин түүнийг ДОТРОО ХАДГАЛЖ байна.
Одоо дизайн нь ҮНЭНИЙГ хэлж байна:
isinstance(mashin, Khodolguur) → False. Зөв! Машин бол хөдөлгүүр биш.
Mashin нь Khodolguur -ийн зөвхөн ХЭРЭГТЭЙ хэсгийг ашиглаж байна (asaakh, untraakh). Бусад бүхнийг үл тоомсорлож болно.
Хоёр хөдөлгүүр хэрэгтэй бол? Хялбар:
python
class Erliits(Mashin):
def __init__(self, ner: str, benzin_chadal: int, tsahilgaan_chadal: int) -> None:
self.ner = ner
self.benzin = Khodolguur(benzin_chadal)
self.tsahilgaan = Khodolguur(tsahilgaan_chadal)Хоёр хөдөлгүүр, ямар ч асуудалгүй. Inheritance-ээр үүнийг хийх БОЛОМЖГҮЙ байсан.
Одоо composition-ы жинхэнэ давуу талыг харъя.
Inheritance нь БАРИМЖААНЫ ҮЕД тогтдог — class бичих үед. Түүнийг дараа нь өөрчилж болохгүй.
Composition нь АЖИЛЛАХ ҮЕД өөрчлөгдөж чадна.
python
class Khodolguur:
def __init__(self, ner: str, khuchin_chadal: int) -> None:
self.ner = ner
self.khuchin_chadal = khuchin_chadal
class Mashin:
def __init__(self, ner: str, khodolguur: Khodolguur) -> None:
self.ner = ner
self.khodolguur = khodolguur
def medeelel(self) -> str:
return f"{self.ner}: {self.khodolguur.ner} ({self.khodolguur.khuchin_chadal} хүчин чадал)"
def khodolguur_solikh(self, shine: Khodolguur) -> None:
khuuchin = self.khodolguur.ner
self.khodolguur = shine
print(f"Хөдөлгүүр солив: {khuuchin} → {shine.ner}")
benzin = Khodolguur("Бензин", 98)
tsahilgaan = Khodolguur("Цахилгаан", 150)
mashin = Mashin("Prius", benzin)
print(mashin.medeelel())
mashin.khodolguur_solikh(tsahilgaan)
print(mashin.medeelel())Prius: Бензин (98 хүчин чадал)
Хөдөлгүүр солив: Бензин → Цахилгаан
Prius: Цахилгаан (150 хүчин чадал)Машины хөдөлгүүрийг АЖИЛЛАЖ БАЙХАД нь сольлоо.
Inheritance-ээр үүнийг хийх БОЛОМЖГҮЙ. Объект нь өөрийн эцэг class-ыг өөрчилж чадахгүй — тэр нь мөнхөд тогтсон.
Мөн анзаараарай: Mashin.__init__ нь khodolguur: Khodolguur гэсэн ПАРАМЕТР авч байна. Тэгэхээр машин үүсгэх үед ямар хөдөлгүүр өгөхийг ГАДНААС шийдэж байна. Энэ загварыг dependency injection гэж нэрлэдэг (нэр томьёог цээжлэх шаардлагагүй). Тэр нь код тестлэхэд, өөрчлөхөд маш их тусалдаг.
Программын ертөнцөд алдартай зөвлөгөө бий:
"Composition over inheritance" — inheritance-аас composition-ыг илүүд үз.
Энэ нь "inheritance-ыг ХЭЗЭЭ Ч бүү ашигла" гэсэн үг БИШ. Тэр нь: эргэлзвэл composition сонго гэсэн үг.
Яагаад ийм зөвлөгөө байдаг вэ? Учир нь:
1. Inheritance нь ХАТУУ холбоос үүсгэдэг. Хүү class нь эцгийн БҮХ ЗҮЙЛТЭЙ мөнхөд холбогдоно. Эцэг өөрчлөгдөхөд БҮХ хүү өөрчлөгдөнө — заримдаа гэнэтийн байдлаар. Программистууд үүнийг "хэврэг эцэг class-ын асуудал" (fragile base class problem) гэж нэрлэдэг.
2. Composition нь СУЛ холбоос үүсгэдэг. Та зөвхөн хэрэгтэй method-уудыг л ашиглана. Дотоод объект өөрчлөгдвөл — зөвхөн таны ашигладаг хэсэг л нөлөөлнө.
3. Composition нь илүү УЯН ХАТАН. Ажиллах үед солих, хэдийг ч хадгалах, огт хадгалахгүй байх — бүгд боломжтой.
4. Composition нь илүү ОЙЛГОМЖТОЙ. self.khodolguur.asaakh() гэсэн код нь "машин хөдөлгүүрээ асааж байна" гэж ТОДОРХОЙ хэлж байна. self.asaakh() (inheritance-ээр) гэсэн код нь тэр нь хаанаас ирснийг нуудаг.
Тэгвэл inheritance хэзээ зөв вэ? Дөрвөн нөхцөл бүгд биелэх ёстой:
Нөхцөл 1: "Х бол Y" гэж хэлж болно. Жинхэнэ ангиллын харилцаа. Ном бол зүйл. Нохой бол амьтан.
Нөхцөл 2: Хүү class нь эцгийн БҮХ зан төлөвийг АШИГЛАЖ чадна. Хэрэв хүү нь эцгийн зарим method-ыг "энэ надад хамаагүй" гэж үзэж байвал — inheritance буруу.
Нөхцөл 3: Хүү class-ыг эцгийн ОРОНД ашиглаж болно. Хэрэв функц нь Zuil хүлээж байвал, Nom дамжуулахад бүх зүйл ажиллах ёстой. Ямар ч гэнэтийн зүйл гарах ёсгүй.
Нөхцөл 4: Polymorphism нь ХЭРЭГТЭЙ. Та үнэхээр list[Zuil] үүсгэж, тэдгээрийг ижил байдлаар боловсруулах гэж байгаа юу? Хэрэв үгүй бол — inheritance нь илүүдэл.
Хэрэв ДӨРВҮҮЛЭЭ биелж байвал — inheritance зөв. Хэрэв аль нэг нь эргэлзээтэй бол — composition бод.
Гурав дахь нөхцөл нь хамгийн нарийн бөгөөд хамгийн олон алдаа гардаг газар. Түүнийг жишээгээр харъя.
Танд Shuvuu (шувуу) class байна:
python
class Shuvuu:
def __init__(self, ner: str) -> None:
self.ner = ner
def nisekh(self) -> str:
return f"{self.ner} нисч байна"Одоо оцон шувуу нэмье. Оцон шувуу бол шувуу мөн, тийм үү?
python
class OtsonShuvuu(Shuvuu):
def nisekh(self) -> str:
raise Exception("Оцон шувуу нисдэггүй!")(raise -ыг Бүлэг 13-т үзнэ. Одоохондоо "алдаа гаргаж, программыг зогсоох" гэж ойлгоод өнгөрөөрэй.)
Одоо асуудал үүснэ:
python
def nisgekh(shuvuu: Shuvuu) -> None:
print(shuvuu.nisekh())
shuvuunuud: list[Shuvuu] = [
Shuvuu("Бүргэд"),
OtsonShuvuu("Оцон шувуу"),
]
for sh in shuvuunuud:
nisgekh(sh)Бүргэд нисч байна
Traceback (most recent call last):
...
Exception: Оцон шувуу нисдэггүй!Программ унасан.
nisgekh функц нь Shuvuu хүлээж байсан. Оцон шувуу бол шувуу мөн, тиймээс дамжуулагдсан. Гэвч тэр функц ажиллахаа больсон.
Энэ бол Нөхцөл 3-ын зөрчил. Хүү class-ыг эцгийн оронд ашиглах боломжгүй болсон.
(Программын ертөнцөд энэ зарчмыг Liskov Substitution Principle гэж нэрлэдэг. Нэр томьёог мэдэх шаардлагагүй, гэхдээ санааг нь мэдэх нь чухал: хүү class нь эцгийн амлалтыг ЭВДЭХ ЁСГҮЙ.)
Зөв шийдэл: ангиллаа дахин бод.
python
class Shuvuu:
def __init__(self, ner: str) -> None:
self.ner = ner
def khodolokh(self) -> str:
return f"{self.ner} хөдөлж байна"
class NisdegShuvuu(Shuvuu):
def khodolokh(self) -> str:
return f"{self.ner} нисч байна"
class OtsonShuvuu(Shuvuu):
def khodolokh(self) -> str:
return f"{self.ner} усанд сэлж байна"
shuvuunuud: list[Shuvuu] = [
NisdegShuvuu("Бүргэд"),
OtsonShuvuu("Оцон шувуу"),
]
for sh in shuvuunuud:
print(sh.khodolokh())Бүргэд нисч байна
Оцон шувуу усанд сэлж байнаОдоо ажиллаж байна. Эцэг class нь ЗӨВХӨН бүх шувуунд ҮНЭН зүйлсийг л амлаж байна (khodolokh). Нисэх нь бүх шувуунд ХАМААРАХГҮЙ тул эцэгт байх ёсгүй байсан.
Сургамж: эцэг class-д зөвхөн БҮХ хүүд ҮНЭН зүйлсийг л хий. Хэрэв хүү нэг нь "энэ надад хамаагүй" гэж хэлэх юм бол — эцэг буруу баригдсан.
Хичээл 7.2-т би сануулсан: гурваас олон түвшний inheritance нь аюултай. Одоо яагаад гэдгийг тайлбарлая.
python
class A:
def uildel(self) -> str:
return "A"
class B(A):
pass
class C(B):
def uildel(self) -> str:
return "C"
class D(C):
pass
class E(D):
def uildel(self) -> str:
return super().uildel() + "E"
e = E()
print(e.uildel())CEЭнэ үр дүнг таамаглаж чадсан уу?
Ихэнх хүн чадахгүй. Хариултыг олохын тулд та таван class-ыг дээрээс доош дагаж, аль нь uildel -ыг override хийсэн, аль нь хийгээгүйг олох хэрэгтэй болно. E.uildel дотор super() нь D -г заана, D -д байхгүй тул C рүү явна, тэнд олдож "C" буцаана, дараа нь "E" нэмэгдэнэ.
Таван мөрийн үр дүнг ойлгохын тулд таван class уншлаа. Хэрэв тэдгээр нь өөр өөр файлд байвал — бүр ч дор.
Энэ асуудлыг "yo-yo problem" гэж нэрлэдэг: та кодыг ойлгохын тулд гинжээр дээш доош гүйж, толгой эргэдэг.
Практик дүрэм: ХОЁР түвшин хангалттай. Гурав нь хязгаар. Дөрөв бол дизайны асуудал.
Хэрэв та дөрөв дэх түвшин барих гэж байгаа бол — зогсоод, composition-оор дахин зохиох боломжтой эсэхийг бод.
Асуулт | Хариулт |
|---|---|
"Х бол Y" гэж хэлж болох уу? | Тийм → inheritance бодож үз |
"Х-д Y байдаг" гэж хэлэх үү? | Тийм → composition |
Хүү нь эцгийн БҮХ method-ыг ашиглах уу? | Үгүй → composition |
Хүү нь эцгийн амлалтыг эвдэх үү? | Тийм → ангиллаа дахин бод |
Polymorphism ( | Үгүй → inheritance илүүдэл |
Гурваас олон түвшин болох уу? | Тийм → дахин зохио |
Эргэлзэж байна уу? | Composition |
Одоо энэ бүх зарчмыг БОДИТ дизайн дээр харъя. Бүлэг 9-д та номын сангийн систем бичнэ. Түүний бүтэц ямар байх ёстой вэ?
Асуулт 1: Nom, Setguul, DVD — inheritance ашиглах уу?
"Ном бол зүйл" → тийм, байгалийн
Хүү бүр эцгийн zeeldekh(), butsaakh() -ыг ашиглана → тийм
list[Zuil] үүсгэж, ижил байдлаар боловсруулна → тийм
Хоёр түвшин л → тийм
Дүгнэлт: inheritance ЗӨВ.
Асуулт 2: Nomiin_San (номын сан) class — юугаас өвлөх вэ?
"Номын сан бол ном" → ҮГҮЙ! Утгагүй.
Номын санд номнууд БАЙДАГ → composition
python
class NomiinSan:
def __init__(self, ner: str) -> None:
self.ner = ner
self.zuiluud: list[Zuil] = [] # composition!
self.gishuud: list[Gishuun] = [] # composition!
def zuil_nemekh(self, zuil: Zuil) -> None:
self.zuiluud.append(zuil)Дүгнэлт: composition ЗӨВ.
Асуулт 3: Gishuun (гишүүн) — Zuil -ээс өвлөх үү?
"Гишүүн бол зүйл" → ҮГҮЙ. Гишүүн бол ХҮН, зээлж болох эд зүйл биш.
Дүгнэлт: тусдаа class, ямар ч inheritance байхгүй.
Тэгэхээр эцсийн дизайн нь:
Zuil (эцэг)
├── Nom
├── Setguul
└── DVD
Gishuun (тусдаа)
NomiinSan (composition)
├── zuiluud: list[Zuil]
└── gishuud: list[Gishuun]Энэ бол цэвэрхэн, ойлгомжтой, өргөтгөх боломжтой дизайн. Inheritance нь зөвхөн ЗӨВ газартаа, composition нь бусад бүх газарт.
Хэрэв сонирхвол: дараах хосуудад "бол" тест хийж, inheritance эсвэл composition аль нь зөв болохыг шийдээрэй.
Suragch ба Khun
Anги ба Suragch
Tsakhilgaan_Mashin ба Mashin
Zurag ба Fail
Zakhialga ба Baraa
Bagsh ба Ajiltan
Санал болгох хариултууд: 1 → inheritance ("сурагч бол хүн"). 2 → composition ("ангид сурагчид байдаг"). 3 → inheritance ("цахилгаан машин бол машин"). 4 → inheritance ("зураг бол файл"). 5 → composition ("захиалгад бараанууд байдаг"). 6 → inheritance ("багш бол ажилтан").
Хэрэв таны хариулт өөр байвал — тайлбарлаж чадаж байвал зөв. Гол нь ЯАГААД гэдгээ мэдэх явдал.
1. Код дахин ашиглахын ТӨЛӨӨ inheritance ашиглах
Энэ бол ХАМГИЙН түгээмэл алдаа. "Тэр class-д надад хэрэгтэй method байна, тэгэхээр түүнээс өвлөе."
БУРУУ. Inheritance нь код дахин ашиглах хэрэгсэл БИШ. Тэр нь ангиллын харилцааг илэрхийлэх хэрэгсэл. Хэрэв танд зөвхөн код хэрэгтэй бол — composition ашигла, эсвэл зүгээр л функц бич.
2. "Байдаг" -ыг inheritance болгох
python
class Mashin(Khodolguur): # машин бол хөдөлгүүр биш!Composition ашигла: self.khodolguur = Khodolguur(...).
3. Эцэг class-д бүх хүүд хамаарахгүй зүйл хийх
python
class Shuvuu:
def nisekh(self) -> str: ... # оцон шувуу нисдэггүй!Эцэг class-д ЗӨВХӨН бүх хүүд ҮНЭН зүйлсийг л хий.
4. Хэт гүн гинж
Дөрөв, таван түвшин → "yo-yo problem". Хоёр түвшин хангалттай, гурав нь хязгаар.
5. Polymorphism хэрэггүй байхад inheritance ашиглах
Хэрэв та хэзээ ч list[Zuil] үүсгэхгүй, хэзээ ч эцэг төрлөөр hint бичихгүй бол — inheritance нь зүгээр л нэмэлт нарийн төвөгтэй байдал. Тусдаа class бич.
Гол дүрэм: "Х БОЛ Y" гэж хэлж болох үед л inheritance ашигла. "Х-д Y байдаг" гэж хэлэх бол — composition.
Composition гэдэг нь объект дотор өөр объектыг хадгалах: self.khodolguur = Khodolguur(...). Тусгай синтакс байхгүй.
"Composition over inheritance" — эргэлзвэл composition сонго.
Composition-ы давуу талууд: сул холбоос, уян хатан (ажиллах үед солиж болно, хэдийг ч хадгалж болно), ойлгомжтой, зөвхөн хэрэгтэй хэсгийг ашиглана.
Inheritance-ийн ДӨРВӨН нөхцөл (бүгд биелэх ёстой):
"Х бол Y" гэж хэлж болно
Хүү нь эцгийн БҮХ зан төлөвийг ашиглана
Хүүг эцгийн ОРОНД ашиглаж болно (амлалт эвдрэхгүй)
Polymorphism үнэхээр хэрэгтэй
Эцэг class-д зөвхөн БҮХ хүүд ҮНЭН зүйлсийг л хий. Оцон шувуу нисдэггүй → Shuvuu -д nisekh() байх ёсгүй.
Хэт гүн гинж (4+ түвшин) → "yo-yo problem". Хоёр түвшин хангалттай, гурав нь хязгаар.
Inheritance нь код дахин ашиглах хэрэгсэл БИШ — тэр нь ангиллын харилцааг илэрхийлэх хэрэгсэл. Зөвхөн код хэрэгтэй бол composition эсвэл энгийн функц.
Бүлэг 7 дууслаа. Та одоо inheritance-ыг зөв ашиглаж, буруу ашиглахаас зайлсхийж чадна. Тэр нь мэдлэгээс илүү ЧУХАЛ — учир нь буруу ашигласан хүчирхэг хэрэгсэл нь огт хэрэглээгүйгээс дор байдаг.
Бүлэг 8-д бид special methods руу орно. Тэнд эцэст нь print(objekt) яагаад <__main__.Nom object at 0x000001A2...> гэсэн муухай зүйл хэвлэдгийг тайлж, түүнийг зассаны дараа кодоо жинхэнэ мэргэжлийн харагдуулна. Мөн __eq__ -ээр объектуудыг харьцуулах, __len__ -ээр len() -ийг өөрийн class дээр ажиллуулах, property -ээр method-ыг талбар шиг харагдуулах — эдгээрийг үзнэ. Тэдгээр бүгд нь давхар доогуур зураастай нэрс ашигладаг бөгөөд тэр нь Python-ы хамгийн гоё загваруудын нэг юм.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.