8-bo‘lim

Adapter

O'zgartirib bo'lmaydigan kodni o'z interfeysingizga moslash.

🕑 12 daqiqa o‘qish 📄 700 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: begona interfeys
  2. Obyekt adapteri
  3. Sinf adapteri (meros orqali)
  4. Ma'lumot shaklini moslash
  5. Standart interfeyslarga moslash
  6. Adapter, Decorator va Facade
  7. Xulosa

Adapter - mos kelmaydigan interfeyslarni bir-biriga ulaydigan naqsh. Rozetka o'tkazgichi kabi: qurilma ham, rozetka ham o'zgarmaydi, orada moslashtirgich turadi.

U eng ko'p o'zga kod bilan ishlaganda kerak bo'ladi.

Muammo: begona interfeys #

Bizning tizim Yuboruvchi interfeysi bilan ishlaydi:

PHP
interface Yuboruvchi
{
    public function yubor(string $manzil, string $matn): bool;
}

final class OzXizmatimiz implements Yuboruvchi
{
    public function yubor(string $manzil, string $matn): bool
    {
        echo "  [oz] {$manzil}: {$matn}", PHP_EOL;
        return true;
    }
}

function xabarTarqat(Yuboruvchi $y, array $manzillar, string $matn): int
{
    $soni = 0;
    foreach ($manzillar as $m) {
        if ($y->yubor($m, $matn)) {
            $soni++;
        }
    }
    return $soni;
}
PHP
$n = xabarTarqat(new OzXizmatimiz(), ['+998901112233', '+998935556677'],
    'Buyurtmangiz yolga chiqdi');
echo "yuborildi: {$n}", PHP_EOL;
Natija
  [oz] +998901112233: Buyurtmangiz yolga chiqdi
  [oz] +998935556677: Buyurtmangiz yolga chiqdi
yuborildi: 2

Endi tashqi SMS provayderining kutubxonasi keldi. Uning interfeysi butunlay boshqacha:

PHP
// Tashqi kutubxona - biz uni O'ZGARTIRA OLMAYMIZ
final class SmsGateway
{
    private int $balans = 3;

    /**
     * @param array{to: string, body: string} $payload
     * @return array{status: string, id?: string, error?: string}
     */
    public function send(array $payload): array
    {
        if ($this->balans <= 0) {
            return ['status' => 'failed', 'error' => 'insufficient_balance'];
        }
        if (!str_starts_with($payload['to'], '+998')) {
            return ['status' => 'failed', 'error' => 'invalid_number'];
        }

        $this->balans--;
        echo "  [gateway] {$payload['to']} <- {$payload['body']}", PHP_EOL;

        return ['status' => 'sent', 'id' => 'msg_' . (3 - $this->balans)];
    }

    public function balance(): int
    {
        return $this->balans;
    }
}
PHP
$gw = new SmsGateway();
$javob = $gw->send(['to' => '+998901112233', 'body' => 'Salom']);
print_r($javob);

$xato = $gw->send(['to' => '77012345678', 'body' => 'Salom']);
print_r($xato);
Natija
  [gateway] +998901112233 <- Salom
Array
(
    [status] => sent
    [id] => msg_1
)
Array
(
    [status] => failed
    [error] => invalid_number
)
To'rtta nomuvofiqlik
Bizning kutganimizKutubxona berayotgani
Metod nomi yuborsend
Ikkita argumentBitta assotsiativ massiv
Kalitlar manzil, matnto, body
bool qaytadiMassiv qaytadi

xabarTarqat() ga SmsGateway ni uzatib bo'lmaydi - u Yuboruvchi interfeysini bajarmaydi.

Uch xil yechim bor:

YechimBaho
xabarTarqat ni o'zgartirishIkkinchi provayder kelsa yana o'zgaradi
Kutubxonani tahrirlashYangilanishda yo'qoladi, ko'pincha imkonsiz
Adapter yozishIkkalasi ham tegilmaydi

Obyekt adapteri #

Eng keng tarqalgan shakl - adapter begona obyektni ichida saqlaydi:

PHP
final class SmsGatewayAdapter implements Yuboruvchi
{
    /** @var list<string> */
    private array $xatolar = [];

    // moslashtirilayotgan obyekt ichkarida turadi
    public function __construct(private readonly SmsGateway $gateway) {}

    public function yubor(string $manzil, string $matn): bool
    {
        // 1. argumentlarni begona shaklga o'tkazamiz
        $javob = $this->gateway->send([
            'to' => $manzil,
            'body' => $matn,
        ]);

        // 2. javobni o'z shaklimizga qaytaramiz
        if ($javob['status'] !== 'sent') {
            $this->xatolar[] = $manzil . ': ' . ($javob['error'] ?? 'nomalum');
            return false;
        }

        return true;
    }

    /** @return list<string> */
    public function xatolar(): array
    {
        return $this->xatolar;
    }
}
PHP
$adapter = new SmsGatewayAdapter(new SmsGateway());

// xabarTarqat() UMUMAN o'zgarmadi
$n = xabarTarqat($adapter,
    ['+998901112233', '77012345678', '+998935556677'],
    'Buyurtmangiz yolga chiqdi');

echo "yuborildi: {$n}", PHP_EOL;
foreach ($adapter->xatolar() as $x) {
    echo 'xato: ', $x, PHP_EOL;
}
Natija
  [gateway] +998901112233 <- Buyurtmangiz yolga chiqdi
  [gateway] +998935556677 <- Buyurtmangiz yolga chiqdi
yuborildi: 2
xato: 77012345678: invalid_number

Endi ikkala tomon ham o'z holicha qoladi va ular birga ishlaydi:

PHP
$manzillar = ['+998901112233', '+998935556677'];

/** @var list<Yuboruvchi> $yuboruvchilar */
$yuboruvchilar = [
    new OzXizmatimiz(),
    new SmsGatewayAdapter(new SmsGateway()),
];

foreach ($yuboruvchilar as $y) {
    printf("%s:%s", $y::class, PHP_EOL);
    printf("  natija: %d%s", xabarTarqat($y, $manzillar, 'Test'), PHP_EOL);
}
Natija
OzXizmatimiz:
  [oz] +998901112233: Test
  [oz] +998935556677: Test
  natija: 2
SmsGatewayAdapter:
  [gateway] +998901112233 <- Test
  [gateway] +998935556677 <- Test
  natija: 2
Adapter - ikki tomon ham tegilmaydi xabarTarqat() bizning kodimiz o'zgarmaydi yubor() SmsGatewayAdapter implements Yuboruvchi manzil → to massiv → bool send() SmsGateway tashqi kutubxona o'zgartirib bo'lmaydi provayder serveri Adapter ichida sodir bo'ladigan ish yubor('+998901112233', 'Salom') → send(['to' => '+998901112233', 'body' => 'Salom']) true ← ['status' => 'sent', 'id' => 'msg_1'] Ikkinchi provayder kelganda Yangi adapter sinfi yoziladi. xabarTarqat() ham, mavjud adapter ham tegilmaydi.
Adapter - tarjimon: ikki tomon bir-birining tilini bilmaydi

Sinf adapteri (meros orqali) #

PHP da adapterni meros bilan ham yozish mumkin:

PHP
// diqqat: bu sinf `final` EMAS - aks holda meros ishlamas edi
class Eski
{
    public function hisobla(int $a, int $b): int
    {
        return $a + $b;
    }
}

interface Yangi
{
    public function qosh(array $sonlar): int;
}

// Meros bilan: adapter moslashtirilayotgan sinfning vorisi
final class MerosAdapter extends Eski implements Yangi
{
    public function qosh(array $sonlar): int
    {
        $jami = 0;
        foreach ($sonlar as $s) {
            $jami = $this->hisobla($jami, $s);
        }
        return $jami;
    }
}

$a = new MerosAdapter();
echo $a->qosh([1, 2, 3, 4, 5]), PHP_EOL;
echo $a->hisobla(10, 5), PHP_EOL;   // eski metod ham ochiq
Natija
15
15
Meros adapteri - deyarli har doim yomon tanlov
KamchilikIzoh
Eski sinf final bo'lsa - ishlamaydiKo'p kutubxonalar final ishlatadi
Eski metodlar ochiq qoladihisobla() tashqaridan chaqirilaveradi
Bir vaqtda faqat bitta sinfni moslashtirishIkki kutubxonani birlashtirib bo'lmaydi
Ota-sinf o'zgarsa - adapter buziladiMeros eng qattiq bog'lanish

Obyekt adapteri (kompozitsiya) bu muammolarning hech biriga ega emas.

Umumiy qoida - kompozitsiyani merosdan ustun qo'ying. Bu faqat adapterga emas, deyarli barcha naqshlarga tegishli.

Ma'lumot shaklini moslash #

Adapter faqat metod nomlarini emas, ma'lumot shaklini ham moslashtiradi. Bu amalda ko'proq uchraydi:

PHP
// Tashqi API shunday javob beradi
$apiJavobi = '{"data":{"user_id":42,"full_name":"Malika Ergasheva",'
    . '"reg_ts":1735689600,"is_active":1}}';

// Bizning tizim esa shunaqa obyekt kutadi
final readonly class Foydalanuvchi
{
    public function __construct(
        public int $id,
        public string $ism,
        public DateTimeImmutable $royxatdanOtgan,
        public bool $faol,
    ) {}
}

final class ApiAdapter
{
    public function foydalanuvchi(string $json): Foydalanuvchi
    {
        $x = json_decode($json, true, 512, JSON_THROW_ON_ERROR)['data'];

        return new Foydalanuvchi(
            id: (int) $x['user_id'],
            ism: (string) $x['full_name'],
            royxatdanOtgan: (new DateTimeImmutable())
                ->setTimestamp((int) $x['reg_ts'])
                ->setTimezone(new DateTimeZone('UTC')),
            faol: (bool) $x['is_active'],
        );
    }
}

$f = (new ApiAdapter())->foydalanuvchi($apiJavobi);

printf("#%d %s%s", $f->id, $f->ism, PHP_EOL);
printf("royxat: %s%s", $f->royxatdanOtgan->format('Y-m-d H:i'), PHP_EOL);
printf("faol: %s%s", $f->faol ? 'ha' : 'yoq', PHP_EOL);
Natija
#42 Malika Ergasheva
royxat: 2025-01-01 00:00
faol: ha
Bu qatlam nima uchun muhim

API javobidagi user_id, reg_ts, is_active nomlari - begona tizimning qarori. Ular ertaga o'zgarishi mumkin.

Agar bu nomlar butun loyihada tarqalib ketsa, API o'zgarganda o'nlab faylni tahrirlash kerak bo'ladi.

Adapter esa chegara o'rnatadi: begona shakl faqat shu sinfda mavjud. Undan keyingi hamma kod Foydalanuvchi bilan ishlaydi.

Bu qatlam ko'pincha anti-corruption layer deb ataladi - "buzilishdan himoya qatlami".

Standart interfeyslarga moslash #

PHP ning o'z interfeyslari ham adapter uchun maqsad bo'la oladi:

PHP
// Eski uslubdagi sinf - massiv qaytaradi
final class EskiOmbor
{
    public function hammasi(): array
    {
        return [
            ['id' => 1, 'nom' => 'Klaviatura'],
            ['id' => 2, 'nom' => 'Sichqoncha'],
            ['id' => 3, 'nom' => 'Monitor'],
        ];
    }
}

// Adapter uni foreach bilan ishlatiladigan qilib beradi
final class OmborIterator implements IteratorAggregate, Countable
{
    public function __construct(private readonly EskiOmbor $ombor) {}

    public function getIterator(): Generator
    {
        foreach ($this->ombor->hammasi() as $qator) {
            yield $qator['id'] => $qator['nom'];
        }
    }

    public function count(): int
    {
        return count($this->ombor->hammasi());
    }
}

$mahsulotlar = new OmborIterator(new EskiOmbor());

echo 'jami: ', count($mahsulotlar), PHP_EOL;
foreach ($mahsulotlar as $id => $nom) {
    printf("%d. %s%s", $id, $nom, PHP_EOL);
}
Natija
jami: 3
1. Klaviatura
2. Sichqoncha
3. Monitor

count() va foreach endi ishlaydi, chunki adapter Countable va IteratorAggregate interfeyslarini bajardi.

Adapter, Decorator va Facade #

Uchalasi ham obyektni o'raydi. Farqi maqsadda:

NaqshInterfeysMaqsad
AdapterO'zgaradiMos kelmaydiganni ulash
DecoratorO'zgarmaydiYangi xulq qo'shish
FacadeYangi, soddaroqMurakkablikni yashirish
PHP
// Adapter: interfeys A -> interfeys B
echo 'Adapter:   send(array) -> yubor(string, string)', PHP_EOL;
// Decorator: interfeys A -> o'sha interfeys A, lekin ko'proq ish bilan
echo 'Decorator: yubor() -> yubor() + jurnalga yozish', PHP_EOL;
// Facade: 5 ta sinf -> 1 ta oddiy metod
echo 'Facade:    5 sinf -> buyurtmaniRasmiylashtir()', PHP_EOL;
Natija
Adapter:   send(array) -> yubor(string, string)
Decorator: yubor() -> yubor() + jurnalga yozish
Facade:    5 sinf -> buyurtmaniRasmiylashtir()

Keyingi ikki bo'limda Decorator va Facade ni batafsil ko'ramiz.

Amaliy topshiriq
  1. O'z interfeysingizni va unga mos kelmaydigan "tashqi" sinfni yozing.
  2. To'rtta nomuvofiqlikni ro'yxat qilib chiqing.
  3. Obyekt adapteri yozing va mijoz kodi o'zgarmaganini tekshiring.
  4. Adapterda xatolarni to'plang va oxirida ko'rsating.
  5. Xuddi shuni meros bilan yozing va kamchiliklarini sanang.
  6. Tashqi sinfni final qiling - meros adapteri nima bo'ladi?
  7. JSON javobini o'z readonly sinfingizga o'tkazuvchi adapter yozing.
  8. Begona kalit nomlari qayerlarda tarqalganini kodingizdan qidiring.
  9. Massiv qaytaruvchi sinfni IteratorAggregate ga moslang.
  10. Adapter, Decorator va Facade farqini o'z so'zlaringiz bilan yozing.

Xulosa #

  • Adapter - mos kelmaydigan interfeyslarni ulaydi.
  • Ikkala tomon ham o'zgarmaydi: mijoz kod ham, tashqi kutubxona ham.
  • U metod nomlarini, argument shaklini va javob turini moslashtiradi.
  • Obyekt adapteri (kompozitsiya) - deyarli har doim to'g'ri tanlov.
  • Sinf adapteri (meros) final sinflarda ishlamaydi va eski metodlarni ochiq qoldiradi.
  • Adapter API javobini o'z modelingizga o'tkazadigan chegara qatlami bo'la oladi.
  • Bu qatlam begona nomlarning butun loyihaga tarqalishiga yo'l qo'ymaydi.
  • PHP ning Countable, IteratorAggregate kabi interfeyslari ham maqsad bo'lishi mumkin.
  • Adapter interfeysni o'zgartiradi, Decorator esa saqlaydi.
  • Facade yangi, soddaroq interfeys yaratadi - u ham o'rash, lekin boshqa maqsadda.

Keyingi bo'limda obyektga interfeysini o'zgartirmasdan yangi xulq qo'shadigan Decorator 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.