6-bo‘lim
Singleton va uning muammolari
Eng oson yoziladigan va eng ko'p pushaymon bo'linadigan naqsh.
Ushbu bo‘lim mundarijasi
Singleton - sinfning butun dastur davomida faqat bitta nusxasi bo'lishini kafolatlaydi.
Bu eng mashhur naqsh, va shu bilan birga eng ko'p tanqid qilinadigani. Ikkalasini ham ko'ramiz.
Klassik ko'rinish #
final class Sozlamalar
{
private static ?self $nusxa = null;
/** @var array<string, string> */
private array $qiymatlar = [];
// tashqaridan new qilib bo'lmaydi
private function __construct()
{
$this->qiymatlar = ['til' => 'uz', 'mavzu' => 'yorug'];
}
public static function ol(): self
{
// birinchi chaqiruvda yaratiladi, keyin qayta ishlatiladi
self::$nusxa ??= new self();
return self::$nusxa;
}
public function qiymat(string $kalit): ?string
{
return $this->qiymatlar[$kalit] ?? null;
}
public function qoy(string $kalit, string $qiymat): void
{
$this->qiymatlar[$kalit] = $qiymat;
}
// nusxa ko'chirishning oldini olamiz
private function __clone() {}
public function __wakeup(): void
{
throw new LogicException('Singleton ni deserializatsiya qilib bolmaydi');
}
}
$a = Sozlamalar::ol();
$b = Sozlamalar::ol();
echo 'bir xil obyektmi: ', $a === $b ? 'ha' : 'yoq', PHP_EOL;
echo 'obyekt raqami: ', spl_object_id($a), ' va ', spl_object_id($b), PHP_EOL;
echo 'til: ', $a->qiymat('til'), PHP_EOL;
// $a orqali o'zgartirsak, $b da ham ko'rinadi
$a->qoy('til', 'ru');
echo 'b dagi til: ', $b->qiymat('til'), PHP_EOL;
bir xil obyektmi: ha
obyekt raqami: 1 va 1
til: uz
b dagi til: ru
Yaratishning barcha yo'llari yopilgan:
try {
$x = new Sozlamalar();
} catch (Error $e) {
echo 'new: ', $e->getMessage(), PHP_EOL;
}
try {
$x = clone Sozlamalar::ol();
} catch (Error $e) {
echo 'clone: ', $e->getMessage(), PHP_EOL;
}
try {
$x = unserialize(serialize(Sozlamalar::ol()));
} catch (LogicException $e) {
echo 'unserialize: ', $e->getMessage(), PHP_EOL;
}
new: Call to private Sozlamalar::__construct() from global scope
clone: Call to private Sozlamalar::__clone() from global scope
unserialize: Singleton ni deserializatsiya qilib bolmaydi
??= operatoriself::$nusxa ??= new self(); - bu null bo'lsa
o'zlashtirish operatori (PHP 7.4+).
To'liq yozilishi:
if (self::$nusxa === null) {
self::$nusxa = new self();
}
Bu kechiktirilgan yaratish (lazy initialization): obyekt birinchi so'ralguncha yaratilmaydi. Agar sinf hech qachon ishlatilmasa, konstruktor ham chaqirilmaydi.
Endi muammolar #
Singleton ikkita narsani birlashtiradi, va ikkalasi ham alohida-alohida muammo:
| Nima qiladi | Nima uchun yomon |
|---|---|
| Nusxa bitta bo'lishini kafolatlaydi | Bu odatda kerak emas |
| Unga hamma joydan kirish beradi | Bu global o'zgaruvchi |
Ikkinchisi asosiy muammo.
1. Yashirin bog'liqlik #
final class HisobotXizmati
{
public function tayyorla(): string
{
// bog'liqlik konstruktorda ko'rinmaydi
$til = Sozlamalar::ol()->qiymat('til');
return "hisobot tili: {$til}";
}
}
$x = new HisobotXizmati();
echo $x->tayyorla(), PHP_EOL;
hisobot tili: uz
new HisobotXizmati() - konstruktor bo'sh. Sinf hech
narsaga bog'liq emasdek ko'rinadi.
Aslida u Sozlamalar ga bog'liq. Buni bilish uchun butun
sinf kodini o'qish kerak.
Katta loyihada bu quyidagiga olib keladi:
| Oqibat | Izoh |
|---|---|
| Sinfni alohida ishlatib bo'lmaydi | Singleton hamisha kerak |
| Bog'liqliklar grafigi ko'rinmaydi | Kim kimga tayanadi - noma'lum |
| Refaktoring xavfli | Nima buzilishini oldindan bilib bo'lmaydi |
2-bo'limdagi D tamoyili aynan shuni taqiqlaydi.
2. Testlar bir-biriga ta'sir qiladi #
// birinchi "test" tilni o'zgartiradi
function testRuscha(): string
{
Sozlamalar::ol()->qoy('til', 'ru');
return 'testRuscha: ' . Sozlamalar::ol()->qiymat('til');
}
// ikkinchi "test" o'zbekchani kutadi - o'zi hech narsa o'zgartirmaydi
function testOzbekcha(): string
{
return 'testOzbekcha: ' . Sozlamalar::ol()->qiymat('til');
}
echo testOzbekcha(), PHP_EOL; // hali toza
echo testRuscha(), PHP_EOL; // holatni o'zgartirdi
echo testOzbekcha(), PHP_EOL; // endi ifloslangan
testOzbekcha: uz
testRuscha: ru
testOzbekcha: ru
Uchinchi "test" hech narsa o'zgartirmagan, lekin ru oldi -
chunki ikkinchi test global holatni ifloslantirdi.
Real loyihada bu quyidagicha ko'rinadi:
| Belgi | Sabab |
|---|---|
| Test alohida o'tadi, birga o'tmaydi | Oldingi test holatni o'zgartirgan |
| Testlar tartibi ahamiyatli | Global holat ketma-ket to'planadi |
| "Menda ishlaydi" | Boshqa mashinada tartib boshqacha |
Yagona yechim - har testdan oldin singleton ni tozalash.
Buning uchun esa private static maydonga kirish kerak -
ya'ni reflection yoki maxsus qaytaTikla() metodi.
Ikkalasi ham naqshning o'z kafolatini buzadi.
Yaxshiroq yo'l: bir marta yaratib, uzatish #
final readonly class Konfiguratsiya
{
/** @param array<string, string> $qiymatlar */
public function __construct(private array $qiymatlar) {}
public function qiymat(string $kalit): ?string
{
return $this->qiymatlar[$kalit] ?? null;
}
}
final readonly class Hisobot
{
// bog'liqlik ochiq ko'rinadi
public function __construct(private Konfiguratsiya $konf) {}
public function tayyorla(): string
{
return 'hisobot tili: ' . $this->konf->qiymat('til');
}
}
// dasturning kirish nuqtasida BIR MARTA yaratiladi
$konf = new Konfiguratsiya(['til' => 'uz', 'mavzu' => 'yorug']);
echo (new Hisobot($konf))->tayyorla(), PHP_EOL;
// testda esa boshqa nusxa - hech narsa ifloslanmaydi
$testKonf = new Konfiguratsiya(['til' => 'ru']);
echo (new Hisobot($testKonf))->tayyorla(), PHP_EOL;
echo (new Hisobot($konf))->tayyorla(), PHP_EOL;
hisobot tili: uz
hisobot tili: ru
hisobot tili: uz
Diqqat: $konf butun dasturda bitta marta yaratildi.
Ya'ni "bitta nusxa" maqsadiga erishildi.
Farq shundaki, buni sinf o'zi majburlamadi - dasturni yig'uvchi kod hal qildi.
| Singleton | Injeksiya | |
|---|---|---|
| Nusxa soni | Sinf majburlaydi | Yig'uvchi kod hal qiladi |
| Bog'liqlik ko'rinadimi | Yo'q | Ha, konstruktorda |
| Testda almashtirish | Qiyin | Oson |
| Ikkinchi nusxa kerak bo'lsa | Naqshni buzish kerak | Shunchaki new |
Oxirgi qator amalda tez-tez uchraydi: "bitta baza ulanishi" degan qoida bir kun "asosiy va o'qish uchun replika" ga aylanadi.
PHP da Singleton kamroq zarur #
PHP ning ishlash modeli boshqa tillardan farq qiladi:
// Har so'rov o'z jarayonida boshlanadi va tugaydi
echo 'jarayon: har HTTP so\'rovi uchun yangi', PHP_EOL;
echo 'xotira: so\'rov tugagach butunlay tozalanadi', PHP_EOL;
echo 'oqimlar: odatiy PHP-FPM da bitta so\'rov = bitta oqim', PHP_EOL;
$boshlanish = memory_get_usage();
$katta = range(1, 100000);
$oxiri = memory_get_usage();
printf('massiv xotirasi: ~%d KB%s',
intdiv($oxiri - $boshlanish, 1024), PHP_EOL);
unset($katta);
printf('unset dan keyin: ~%d KB%s',
intdiv(memory_get_usage() - $boshlanish, 1024), PHP_EOL);
jarayon: har HTTP so'rovi uchun yangi
xotira: so'rov tugagach butunlay tozalanadi
oqimlar: odatiy PHP-FPM da bitta so'rov = bitta oqim
massiv xotirasi: ~4096 KB
unset dan keyin: ~0 KB
Java yoki C# da singleton butun server ishlash muddati
davomida yashaydi - soatlab, kunlab. Shuning uchun u yerda
oqimlar xavfsizligi (synchronized, volatile) jiddiy
masala.
Odatiy PHP da esa obyekt bitta so'rov davomida yashaydi va so'rov tugagach yo'qoladi. Oqimlar muammosi ham yo'q.
Bu shuni bildiradi:
| Java dagi sabab | PHP dagi holat |
|---|---|
| Qimmat resursni bir marta yaratish | Har so'rovda baribir yangi |
| Oqimlar orasida bo'lishish | Oqim bitta |
| Xotirani tejash | So'rov qisqa |
Ya'ni PHP da Singleton ning texnik sabablari deyarli yo'q. Qolgani - qulaylik, va u qimmatga tushadi.
(Swoole yoki RoadRunner kabi doimiy ishlaydigan muhitlar bundan mustasno - u yerda holat so'rovlar orasida saqlanadi va bu yangi xatolar manbai bo'ladi.)
Singleton haqiqatan mos keladigan holatlar #
Naqsh butunlay yomon emas. U mos keladigan holatlar bor:
| Holat | Nima uchun mos |
|---|---|
| Jurnal yozuvchi (logger) | Holatsiz, almashtirish kerak emas |
| Tasodifiy sonlar generatori urug'i | Global bo'lishi mantiqiy |
| Konstantalar registri | O'zgarmaydi |
php://stdout kabi tashqi resurs | Haqiqatan bitta |
final class Jurnal
{
private static ?self $nusxa = null;
/** @var list<string> */
private array $qatorlar = [];
private function __construct() {}
public static function ol(): self
{
return self::$nusxa ??= new self();
}
public function yoz(string $daraja, string $xabar): void
{
$this->qatorlar[] = strtoupper($daraja) . ': ' . $xabar;
}
/** @return list<string> */
public function hammasi(): array
{
return $this->qatorlar;
}
}
Jurnal::ol()->yoz('info', 'dastur boshlandi');
Jurnal::ol()->yoz('warning', 'sekin sorov: 1.2s');
Jurnal::ol()->yoz('info', 'dastur tugadi');
foreach (Jurnal::ol()->hammasi() as $i => $q) {
printf('%d. %s%s', $i + 1, $q, PHP_EOL);
}
1. INFO: dastur boshlandi
2. WARNING: sekin sorov: 1.2s
3. INFO: dastur tugadi
Jurnal singleton bo'lishi shart emas. Uni ham konstruktordan uzatish mumkin, va shunda testda soxta jurnal qo'yish osonlashadi.
Amaliy qoida:
Singleton ni oxirgi chora sifatida ishlating. Avval injeksiyani sinab ko'ring; u noqulay bo'lib chiqsagina singleton haqida o'ylang.
Ko'p jamoada bu qoida qattiqroq yangraydi: yangi kodda singleton umuman yozilmaydi.
Statik metodlar ham singleton #
final class Matematika
{
public static function ortacha(float ...$sonlar): float
{
if ($sonlar === []) {
throw new InvalidArgumentException('bosh royxat');
}
return array_sum($sonlar) / count($sonlar);
}
}
printf('%.2f%s', Matematika::ortacha(4.0, 5.5, 6.5), PHP_EOL);
5.33
ortacha() - statik metod, lekin u holatsiz: hech narsa
saqlamaydi, tashqi narsaga bog'lanmaydi, bir xil kirishga
doim bir xil javob beradi.
Singleton ning muammosi statik bo'lishida emas, holatda.
| Statik + holatsiz | Xavfsiz |
|---|---|
| Statik + holat | Global o'zgaruvchi, xavfli |
Shuning uchun str_pad, array_map kabi funksiyalar hech
kimga xalaqit bermaydi.
- Klassik singleton yozing va
===bilan bitta ekanini tekshiring. new,clone,unserializeyo'llarini yoping va har birini sinang.- Singleton holatini o'zgartiruvchi ikkita "test" funksiya yozing.
- Ularni turli tartibda chaqirib, natija o'zgarishini ko'ring.
- Xuddi shu sinfni konstruktor injeksiyasiga o'tkazing.
- Injeksiya bilan ikkita turli konfiguratsiya yarating.
- Nusxa baribir bitta bo'lganini tushuntiring.
- PHP va Java dagi obyekt umrini solishtiring.
- Singleton mos keladigan uchta holat yozing.
- Statik metod qachon xavfsiz ekanini tushuntiring.
Xulosa #
- Singleton - sinfning bitta nusxasi bo'lishini kafolatlaydi.
- Amalga oshirish:
private __construct,staticmaydon,ol()metodi. ??=bilan kechiktirilgan yaratish olinadi.__cloneva__wakeupham yopilishi kerak.- Asosiy muammo "bitta nusxa" da emas - hamma joydan kirish da.
- Bog'liqlik konstruktorda ko'rinmaydi, shuning uchun kodni o'qish qiyinlashadi.
- Global holat testlarni bir-biriga bog'laydi va tartibga sezgir qiladi.
- Yechim: obyektni bir marta yaratib, konstruktordan uzatish.
- PHP da obyekt bitta so'rov davomida yashaydi - singleton ning texnik sabablari deyarli yo'q.
- Statik metod holatsiz bo'lsa xavfsiz; muammo statiklikda emas, holatda.
Keyingi bo'limda mavjud obyektdan nusxa olib yangi obyekt yasaydigan Prototype ni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.