15-bo‘lim
Command
Amalni obyektga aylantirish - bekor qilish, navbatga qo'yish va jurnal uchun.
Ushbu bo‘lim mundarijasi
Command - bajariladigan amalni obyektga aylantirish naqshi. Amal ma'lumotga aylangach, uni saqlash, navbatga qo'yish, takrorlash va bekor qilish mumkin bo'ladi.
Muammo: amal chaqiruv joyida qotib qolgan #
Oddiy matn muharriri:
final class Muharrir
{
private string $matn = '';
public function buyruq(string $amal, string $arg = ''): void
{
if ($amal === 'yoz') {
$this->matn .= $arg;
} elseif ($amal === 'ochir') {
$this->matn = substr($this->matn, 0, -(int) $arg);
} elseif ($amal === 'katta') {
$this->matn = strtoupper($this->matn);
}
}
public function matn(): string { return $this->matn; }
}
$m = new Muharrir();
$m->buyruq('yoz', 'Salom ');
$m->buyruq('yoz', 'dunyo');
$m->buyruq('katta');
echo $m->matn(), PHP_EOL;
$m->buyruq('ochir', '5');
echo $m->matn(), PHP_EOL;
SALOM DUNYO
SALOM
Ctrl+Z qo'shish uchun quyidagilar kerak:
| Kerak | Mavjudmi |
|---|---|
| Qaysi amal bajarilgani | Yo'q, unutilgan |
| Uning argumentlari | Yo'q |
| Amaldan oldingi holat | Yo'q |
| Amallar ketma-ketligi | Yo'q |
Ya'ni butun tarixni qo'lda saqlash kerak. Va har yangi amal
uchun "bekor qilish" mantiqini if zanjiriga qo'shish kerak.
Command naqshi shu ma'lumotlarni amalning o'zi bilan birga saqlaydi.
Yechim: amal - obyekt #
final class Hujjat
{
private string $matn = '';
public function matn(): string { return $this->matn; }
public function matnQoy(string $matn): void { $this->matn = $matn; }
public function qosh(string $qism): void { $this->matn .= $qism; }
}
interface Buyruq
{
public function nomi(): string;
public function bajar(): void;
public function bekor(): void;
}
final class YozBuyrugi implements Buyruq
{
private string $oldingi = '';
public function __construct(
private readonly Hujjat $hujjat,
private readonly string $qism,
) {}
public function nomi(): string
{
return "yoz('{$this->qism}')";
}
public function bajar(): void
{
// bekor qilish uchun oldingi holatni eslab qolamiz
$this->oldingi = $this->hujjat->matn();
$this->hujjat->qosh($this->qism);
}
public function bekor(): void
{
$this->hujjat->matnQoy($this->oldingi);
}
}
final class KattaBuyrugi implements Buyruq
{
private string $oldingi = '';
public function __construct(private readonly Hujjat $hujjat) {}
public function nomi(): string { return 'katta()'; }
public function bajar(): void
{
$this->oldingi = $this->hujjat->matn();
$this->hujjat->matnQoy(strtoupper($this->hujjat->matn()));
}
public function bekor(): void
{
$this->hujjat->matnQoy($this->oldingi);
}
}
final class OchirBuyrugi implements Buyruq
{
private string $oldingi = '';
public function __construct(
private readonly Hujjat $hujjat,
private readonly int $soni,
) {}
public function nomi(): string { return "ochir({$this->soni})"; }
public function bajar(): void
{
$this->oldingi = $this->hujjat->matn();
$this->hujjat->matnQoy(substr($this->oldingi, 0, -$this->soni));
}
public function bekor(): void
{
$this->hujjat->matnQoy($this->oldingi);
}
}
$hujjat = new Hujjat();
$buyruqlar = [
new YozBuyrugi($hujjat, 'Salom '),
new YozBuyrugi($hujjat, 'dunyo'),
new KattaBuyrugi($hujjat),
new OchirBuyrugi($hujjat, 5),
];
foreach ($buyruqlar as $b) {
$b->bajar();
printf('%-16s -> "%s"%s', $b->nomi(), $hujjat->matn(), PHP_EOL);
}
yoz('Salom ') -> "Salom "
yoz('dunyo') -> "Salom dunyo"
katta() -> "SALOM DUNYO"
ochir(5) -> "SALOM "
Bekor qilish tarixi #
final class Boshqaruvchi
{
/** @var list<Buyruq> */
private array $tarix = [];
/** @var list<Buyruq> */
private array $qaytarilgan = [];
public function bajar(Buyruq $b): void
{
$b->bajar();
$this->tarix[] = $b;
// yangi amaldan keyin "qaytarish" tarixi kuchini yo'qotadi
$this->qaytarilgan = [];
}
public function bekor(): ?string
{
$b = array_pop($this->tarix);
if ($b === null) {
return null;
}
$b->bekor();
$this->qaytarilgan[] = $b;
return $b->nomi();
}
public function qaytar(): ?string
{
$b = array_pop($this->qaytarilgan);
if ($b === null) {
return null;
}
$b->bajar();
$this->tarix[] = $b;
return $b->nomi();
}
public function tarix(): string
{
return implode(' | ', array_map(
static fn(Buyruq $b): string => $b->nomi(), $this->tarix));
}
}
$hujjat = new Hujjat();
$boshqaruvchi = new Boshqaruvchi();
foreach ([
new YozBuyrugi($hujjat, 'Salom '),
new YozBuyrugi($hujjat, 'dunyo'),
new KattaBuyrugi($hujjat),
] as $b) {
$boshqaruvchi->bajar($b);
}
printf('holat: "%s"%s', $hujjat->matn(), PHP_EOL);
printf('tarix: %s%s', $boshqaruvchi->tarix(), PHP_EOL);
echo '--- ikki marta Ctrl+Z', PHP_EOL;
printf('bekor: %-16s -> "%s"%s',
$boshqaruvchi->bekor(), $hujjat->matn(), PHP_EOL);
printf('bekor: %-16s -> "%s"%s',
$boshqaruvchi->bekor(), $hujjat->matn(), PHP_EOL);
echo '--- bir marta Ctrl+Y', PHP_EOL;
printf('qaytar: %-15s -> "%s"%s',
$boshqaruvchi->qaytar(), $hujjat->matn(), PHP_EOL);
holat: "SALOM DUNYO"
tarix: yoz('Salom ') | yoz('dunyo') | katta()
--- ikki marta Ctrl+Z
bekor: katta() -> "Salom dunyo"
bekor: yoz('dunyo') -> "Salom "
--- bir marta Ctrl+Y
qaytar: yoz('dunyo') -> "Salom dunyo"
Tarix tugagach null qaytadi:
$hujjat = new Hujjat();
$boshqaruvchi = new Boshqaruvchi();
$boshqaruvchi->bajar(new YozBuyrugi($hujjat, 'bitta'));
var_dump($boshqaruvchi->bekor());
var_dump($boshqaruvchi->bekor());
var_dump($hujjat->matn());
string(12) "yoz('bitta')"
NULL
string(0) ""
Makro buyruq #
Bir nechta buyruqni bittaday ishlatish mumkin - bu 11-bo'limdagi Composite:
final class MakroBuyruq implements Buyruq
{
/** @var list<Buyruq> */
private array $ichi = [];
public function __construct(private readonly string $nom) {}
public function qosh(Buyruq ...$b): self
{
foreach ($b as $bir) { $this->ichi[] = $bir; }
return $this;
}
public function nomi(): string { return $this->nom; }
public function bajar(): void
{
foreach ($this->ichi as $b) {
$b->bajar();
}
}
// bekor qilish - TESKARI tartibda
public function bekor(): void
{
foreach (array_reverse($this->ichi) as $b) {
$b->bekor();
}
}
}
$hujjat = new Hujjat();
$boshqaruvchi = new Boshqaruvchi();
$makro = (new MakroBuyruq('sarlavha yasash'))->qosh(
new YozBuyrugi($hujjat, 'kirish'),
new KattaBuyrugi($hujjat),
new YozBuyrugi($hujjat, ' ---'),
);
$boshqaruvchi->bajar(new YozBuyrugi($hujjat, 'Bob 1: '));
$boshqaruvchi->bajar($makro);
printf('holat: "%s"%s', $hujjat->matn(), PHP_EOL);
echo '--- makroni bir marta bekor qilamiz', PHP_EOL;
$boshqaruvchi->bekor();
printf('holat: "%s"%s', $hujjat->matn(), PHP_EOL);
holat: "BOB 1: KIRISH ---"
--- makroni bir marta bekor qilamiz
holat: "Bob 1: "
Uchta amal bitta Ctrl+Z bilan bekor qilindi - va Bob 1:
qismi ham tiklandi, garchi katta() uni ham katta harfga
o'girgan bo'lsa ham.
bekor() da array_reverse ga e'tibor bering.
Agar buyruqlar to'g'ri tartibda bekor qilinsa, natija noto'g'ri bo'ladi: birinchi buyruq eng eski holatni tiklaydi, keyingilari esa o'zlaridan oldingi holatni ustiga yozadi.
Bu tranzaksiyani qaytarish bilan bir xil qoida: nima oxirgi bo'lgan bo'lsa, birinchi bo'lib qaytariladi.
Navbatga qo'yish #
Buyruq ma'lumot bo'lgani uchun uni keyinroq bajarish mumkin:
final readonly class NavbatYozuvi
{
public function __construct(
public string $tur,
public array $arg,
) {}
}
final class Navbat
{
/** @var list<NavbatYozuvi> */
private array $yozuvlar = [];
public function qoy(string $tur, array $arg = []): void
{
$this->yozuvlar[] = new NavbatYozuvi($tur, $arg);
echo " [navbat] +{$tur}", PHP_EOL;
}
public function ishla(Hujjat $hujjat): void
{
echo ' [navbat] ishlov boshlandi', PHP_EOL;
foreach ($this->yozuvlar as $y) {
$buyruq = match ($y->tur) {
'yoz' => new YozBuyrugi($hujjat, $y->arg[0]),
'katta' => new KattaBuyrugi($hujjat),
'ochir' => new OchirBuyrugi($hujjat, $y->arg[0]),
};
$buyruq->bajar();
printf(' [navbat] %-16s "%s"%s',
$buyruq->nomi(), $hujjat->matn(), PHP_EOL);
}
$this->yozuvlar = [];
}
}
$navbat = new Navbat();
// so'rov davomida - faqat navbatga qo'yamiz, bajarmaymiz
$navbat->qoy('yoz', ['hisobot']);
$navbat->qoy('katta');
$navbat->qoy('yoz', [' tayyor']);
echo 'foydalanuvchiga javob qaytarildi', PHP_EOL;
// keyin fon jarayonida
$navbat->ishla(new Hujjat());
[navbat] +yoz
[navbat] +katta
[navbat] +yoz
foydalanuvchiga javob qaytarildi
[navbat] ishlov boshlandi
[navbat] yoz('hisobot') "hisobot"
[navbat] katta() "HISOBOT"
[navbat] yoz(' tayyor') "HISOBOT tayyor"
Navbatga qo'yiladigan "job" - Command naqshining aynan o'zi:
| Element | Naqshda |
|---|---|
Job sinfi | Buyruq |
handle() metodi | bajar() |
| Konstruktor argumentlari | Buyruq ma'lumotlari |
dispatch() | Navbatga qo'yish |
Muhim cheklov: navbatga qo'yilgan buyruq serializatsiya qilinadi. Demak:
| Mumkin | Mumkin emas |
|---|---|
int, string, massiv | Ochiq fayl yoki ulanish |
| Obyekt identifikatori | Butun obyekt grafi |
| Yopilma emas | Closure |
Shuning uchun jobga butun Buyurtma obyektini emas, uning
id sini uzatish tavsiya qilinadi.
Buyruq va Strategiya farqi #
$jadval = [
['Nima ifodalaydi', 'Algoritm', 'Amal (fe\'l)'],
['Argumentlar', 'Chaqiruvda beriladi', 'Ichiga solingan'],
['Qaytaradimi', 'Odatda ha', 'Odatda yoq'],
['Saqlanadimi', 'Yoq', 'Ha - tarix, navbat'],
['Bekor qilish', 'Yoq', 'Asosiy imkoniyat'],
];
printf("%-16s %-22s %s%s", '', 'Strategy', 'Command', PHP_EOL);
echo str_repeat('-', 60), PHP_EOL;
foreach ($jadval as $q) {
printf("%-16s %-22s %s%s", ...[...$q, PHP_EOL]);
}
Strategy Command
------------------------------------------------------------
Nima ifodalaydi Algoritm Amal (fe'l)
Argumentlar Chaqiruvda beriladi Ichiga solingan
Qaytaradimi Odatda ha Odatda yoq
Saqlanadimi Yoq Ha - tarix, navbat
Bekor qilish Yoq Asosiy imkoniyat
| Narx | Izoh |
|---|---|
| Har amal uchun alohida sinf | 20 ta amal = 20 ta fayl |
| Bekor qilish mantiqi ikki barobar kod | bajar() + bekor() |
| Holatni saqlash xotira talab qiladi | Katta hujjatda tarix og'irlashadi |
| Bekor qilish har doim ham mumkin emas | Yuborilgan xatni qaytarib bo'lmaydi |
Oxirgisi muhim: agar amal tashqi dunyoga ta'sir qilsa (xat yuborish, to'lov, SMS), uni haqiqatda bekor qilib bo'lmaydi. Faqat kompensatsiya qilish mumkin (qaytarish to'lovi, tuzatish xati).
Shuning uchun Ctrl+Z faqat ichki holat o'zgarishlari uchun ishlaydi.
ifzanjirli muharrir yozing va unga Ctrl+Z qo'shishga urinib ko'ring.- Har amalni alohida buyruq sinfiga ajrating.
- Har buyruqda oldingi holatni saqlang.
- Tarix steki bilan
bekor()vaqaytar()yozing. - Yangi amaldan keyin "qaytarish" tarixi tozalanishini tekshiring.
- Makro buyruq yozing va uni bitta Ctrl+Z bilan bekor qiling.
array_reverseni olib tashlang - natija qanday buzildi?- Buyruqlarni navbatga qo'yib keyinroq bajarish kodini yozing.
- Navbatga obyekt emas,
iduzatish nima uchun to'g'riligini yozing. - Bekor qilib bo'lmaydigan uchta amalni sanang.
Xulosa #
- Command - amalni obyektga aylantiradi.
- Obyekt ichida amal, argumentlar va oldingi holat saqlanadi.
- Shu sababli bekor qilish mumkin bo'ladi.
- Tarix - buyruqlar steki;
bekor()oxirgisini oladi. - Yangi amal bajarilgach "qaytarish" tarixi kuchini yo'qotadi.
- Makro buyruq - bir nechta buyruqni bittaday ishlatadi (Composite).
- Makroni bekor qilish teskari tartibda bo'lishi shart.
- Buyruq ma'lumot bo'lgani uchun uni navbatga qo'yish mumkin.
- Navbatdagi buyruqqa butun obyekt emas, identifikator uzatiladi.
- Tashqi dunyoga ta'sir qilgan amalni bekor qilib bo'lmaydi - faqat kompensatsiya.
Keyingi bo'limda algoritm qadamlarini vorislarga qoldiradigan Template Method 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.