15-bo‘lim

Command

Amalni obyektga aylantirish - bekor qilish, navbatga qo'yish va jurnal uchun.

🕑 11 daqiqa o‘qish 📄 692 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: amal chaqiruv joyida qotib qolgan
  2. Yechim: amal - obyekt
  3. Bekor qilish tarixi
  4. Makro buyruq
  5. Navbatga qo'yish
  6. Buyruq va Strategiya farqi
  7. Xulosa

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:

PHP
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;
Natija
SALOM DUNYO
SALOM
"Bekor qilish" ni bu yerga qanday qo'shasiz?

Ctrl+Z qo'shish uchun quyidagilar kerak:

KerakMavjudmi
Qaysi amal bajarilganiYo'q, unutilgan
Uning argumentlariYo'q
Amaldan oldingi holatYo'q
Amallar ketma-ketligiYo'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 #

PHP
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);
    }
}
PHP
$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);
}
Natija
yoz('Salom ')    -> "Salom "
yoz('dunyo')     -> "Salom dunyo"
katta()          -> "SALOM DUNYO"
ochir(5)         -> "SALOM "

Bekor qilish tarixi #

PHP
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));
    }
}
PHP
$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);
Natija
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:

PHP
$hujjat = new Hujjat();
$boshqaruvchi = new Boshqaruvchi();
$boshqaruvchi->bajar(new YozBuyrugi($hujjat, 'bitta'));

var_dump($boshqaruvchi->bekor());
var_dump($boshqaruvchi->bekor());
var_dump($hujjat->matn());
Natija
string(12) "yoz('bitta')"
NULL
string(0) ""
Command - amal ma'lumotga aylanadi Chaqiruvchi tugma, menyu, klaviatura Buyruq bajar() · bekor() + argumentlar + oldingi holat Hujjat qabul qiluvchi Boshqaruvchi tarixni saqlaydi bekor / qaytar Tarix - buyruqlar steki yoz('Salom ') yoz('dunyo') katta() bekor() → oxirgisini oladi va uning bekor() ini chaqiradi Amal obyektga aylangach nima mumkin bo'ladi • Bekor qilish va qaytarish (Ctrl+Z / Ctrl+Y) • Navbatga qo'yib keyinroq bajarish • Jurnalga yozish va takrorlash • Bir nechtasini birlashtirish (makro) • Tarmoq orqali uzatish • Tranzaksiya sifatida qaytarish
Bekor qilish uchun buyruq oldingi holatni o'zi eslab qoladi

Makro buyruq #

Bir nechta buyruqni bittaday ishlatish mumkin - bu 11-bo'limdagi Composite:

PHP
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);
Natija
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.

Teskari tartib majburiy

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.

Buyruq ma'lumot bo'lgani uchun uni keyinroq bajarish mumkin:

PHP
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());
Natija
  [navbat] +yoz
  [navbat] +katta
  [navbat] +yoz
foydalanuvchiga javob qaytarildi
  [navbat] ishlov boshlandi
  [navbat] yoz('hisobot')   "hisobot"
  [navbat] katta()          "HISOBOT"
  [navbat] yoz(' tayyor')   "HISOBOT tayyor"
Bu Laravel Queue va Symfony Messenger ning asosi

Navbatga qo'yiladigan "job" - Command naqshining aynan o'zi:

ElementNaqshda
Job sinfiBuyruq
handle() metodibajar()
Konstruktor argumentlariBuyruq ma'lumotlari
dispatch()Navbatga qo'yish

Muhim cheklov: navbatga qo'yilgan buyruq serializatsiya qilinadi. Demak:

MumkinMumkin emas
int, string, massivOchiq fayl yoki ulanish
Obyekt identifikatoriButun obyekt grafi
Yopilma emasClosure

Shuning uchun jobga butun Buyurtma obyektini emas, uning id sini uzatish tavsiya qilinadi.

Buyruq va Strategiya farqi #

PHP
$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]);
}
Natija
                 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
Command ning narxi
NarxIzoh
Har amal uchun alohida sinf20 ta amal = 20 ta fayl
Bekor qilish mantiqi ikki barobar kodbajar() + bekor()
Holatni saqlash xotira talab qiladiKatta hujjatda tarix og'irlashadi
Bekor qilish har doim ham mumkin emasYuborilgan 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.

Amaliy topshiriq
  1. if zanjirli muharrir yozing va unga Ctrl+Z qo'shishga urinib ko'ring.
  2. Har amalni alohida buyruq sinfiga ajrating.
  3. Har buyruqda oldingi holatni saqlang.
  4. Tarix steki bilan bekor() va qaytar() yozing.
  5. Yangi amaldan keyin "qaytarish" tarixi tozalanishini tekshiring.
  6. Makro buyruq yozing va uni bitta Ctrl+Z bilan bekor qiling.
  7. array_reverse ni olib tashlang - natija qanday buzildi?
  8. Buyruqlarni navbatga qo'yib keyinroq bajarish kodini yozing.
  9. Navbatga obyekt emas, id uzatish nima uchun to'g'riligini yozing.
  10. 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.

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.