8-bo‘lim
Adapter
O'zgartirib bo'lmaydigan kodni o'z interfeysingizga moslash.
Ushbu bo‘lim mundarijasi
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:
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;
}
$n = xabarTarqat(new OzXizmatimiz(), ['+998901112233', '+998935556677'],
'Buyurtmangiz yolga chiqdi');
echo "yuborildi: {$n}", PHP_EOL;
[oz] +998901112233: Buyurtmangiz yolga chiqdi
[oz] +998935556677: Buyurtmangiz yolga chiqdi
yuborildi: 2
Endi tashqi SMS provayderining kutubxonasi keldi. Uning interfeysi butunlay boshqacha:
// 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;
}
}
$gw = new SmsGateway();
$javob = $gw->send(['to' => '+998901112233', 'body' => 'Salom']);
print_r($javob);
$xato = $gw->send(['to' => '77012345678', 'body' => 'Salom']);
print_r($xato);
[gateway] +998901112233 <- Salom
Array
(
[status] => sent
[id] => msg_1
)
Array
(
[status] => failed
[error] => invalid_number
)
| Bizning kutganimiz | Kutubxona berayotgani |
|---|---|
Metod nomi yubor | send |
| Ikkita argument | Bitta assotsiativ massiv |
Kalitlar manzil, matn | to, body |
bool qaytadi | Massiv qaytadi |
xabarTarqat() ga SmsGateway ni uzatib bo'lmaydi - u
Yuboruvchi interfeysini bajarmaydi.
Uch xil yechim bor:
| Yechim | Baho |
|---|---|
xabarTarqat ni o'zgartirish | Ikkinchi provayder kelsa yana o'zgaradi |
| Kutubxonani tahrirlash | Yangilanishda yo'qoladi, ko'pincha imkonsiz |
| Adapter yozish | Ikkalasi ham tegilmaydi |
Obyekt adapteri #
Eng keng tarqalgan shakl - adapter begona obyektni ichida saqlaydi:
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;
}
}
$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;
}
[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:
$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);
}
OzXizmatimiz:
[oz] +998901112233: Test
[oz] +998935556677: Test
natija: 2
SmsGatewayAdapter:
[gateway] +998901112233 <- Test
[gateway] +998935556677 <- Test
natija: 2
Sinf adapteri (meros orqali) #
PHP da adapterni meros bilan ham yozish mumkin:
// 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
15
15
| Kamchilik | Izoh |
|---|---|
Eski sinf final bo'lsa - ishlamaydi | Ko'p kutubxonalar final ishlatadi |
| Eski metodlar ochiq qoladi | hisobla() tashqaridan chaqirilaveradi |
| Bir vaqtda faqat bitta sinfni moslashtirish | Ikki kutubxonani birlashtirib bo'lmaydi |
| Ota-sinf o'zgarsa - adapter buziladi | Meros 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:
// 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);
#42 Malika Ergasheva
royxat: 2025-01-01 00:00
faol: ha
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:
// 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);
}
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:
| Naqsh | Interfeys | Maqsad |
|---|---|---|
| Adapter | O'zgaradi | Mos kelmaydiganni ulash |
| Decorator | O'zgarmaydi | Yangi xulq qo'shish |
| Facade | Yangi, soddaroq | Murakkablikni yashirish |
// 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;
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.
- O'z interfeysingizni va unga mos kelmaydigan "tashqi" sinfni yozing.
- To'rtta nomuvofiqlikni ro'yxat qilib chiqing.
- Obyekt adapteri yozing va mijoz kodi o'zgarmaganini tekshiring.
- Adapterda xatolarni to'plang va oxirida ko'rsating.
- Xuddi shuni meros bilan yozing va kamchiliklarini sanang.
- Tashqi sinfni
finalqiling - meros adapteri nima bo'ladi? - JSON javobini o'z
readonlysinfingizga o'tkazuvchi adapter yozing. - Begona kalit nomlari qayerlarda tarqalganini kodingizdan qidiring.
- Massiv qaytaruvchi sinfni
IteratorAggregatega moslang. - 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)
finalsinflarda 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,IteratorAggregatekabi 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.
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.