Strategy design patterni

Strategy design patterni

Strategy design patterni

Design patternlar mavzusini davom etgan holda bugun Behavioral design patternlar oilasiga mansub Strategy Design Pattern haqida gaplashamiz. Qo’llash jihatidan ancha sodda lekin juda ko’p muammolarni birdaniga hal qila oladigan ushbu design patternni hayotda juda ko’p uchratishingiz mumkin. Bugun Observer design patterni haqidagi maqolada misol qilib olganimiz, yuk jo’natish dasturchasiga yangi imkoniyat qo’shamiz.

Vodiy tomonlardan yuk jo’natish uchun koskader taksichilar xizmatidan foydalanishadi. Voha tomonlarda esa taksiga qo’shimcha avtobuslar ham yuk yetqazib berish xizmatini ko’rsatishadi. Maqolamizda Qo’qonlik shumaxer haydovchilarni misol qilib olamiz. Odatda doimiy tarzda yuk jo’natib turuvchilar 3 yoki 4 haydovchiga mijoz bo’lib olishadi va har doim ularning xizmatidan foydalanishadi. Haydovchilar tezkorligiga va keraklik paytda bo’shligiga qarab tanlanadi. Xizmat haqqiyam turlicha bo’ladi. Misol qilib uch haydovchini olaylik:

  1. Alisher aka: 10,000 sum
  2. Sherali aka: 12,000 sum
  3. Turdiali aka: 13,000 sum

Yuk jo’natishdan oldin, keraklik haydovchiga bog’lanib yukni unga topshirasiz. Demak Yuk classimizda Haydovchi haqida ma’lumot bo’lishi lozim. Quyida ushbu biznes mantiq uchun classlarni yaratib olganmiz.

//Haydovchilar ro'yhati
public enum Haydovchilar
{
    AlisherAka,
    SheraliAka,
    TurdialiAka
}
//jo'natilayotgan yuk uchun class
public class Yuk
{
    public string Nomi { get; set; }

    public string Manzil { get; set; }

    //yuk jo'natilayotgan haydovchi
    public Haydovchilar Haydovchi { get; set; }
}

Agar design patternlar bilan tanish bo’lmasangiz ushbu jarayonni quyidagicha tadbiq qilishingiz mumkin.

//Yukning narxini hisoblash uchun service
public class YukJunatishHaydovchiService
{
    public double YukJunatishNarxi(Yuk yuk)
    {
        //haydovchining kim ekanligiga qarab
        switch (yuk.Haydovchi)
        {
            case Haydovchilar.AlisherAka:
                return AlisherAkaniNarxi(yuk);
            case Haydovchilar.SheraliAka:
                return SheraliAkaniNarxi(yuk);
            case Haydovchilar.TurdialiAka:
                return TurdialiAkaNarxi(yuk);
            default:
                    return 0;
        }
     }

    //Alisher akaning narxi
    private double AlisherAkaniNarxi(Yuk yuk)
    {
        return 10000;
    }

    //Sherali akaning narxi
    private double SheraliAkaniNarxi(Yuk yuk)
    {
        return 12000;
    }

    //Turdiali akaning narxi
    private double TurdialiAkaNarxi(Yuk yuk)
    {
        return 13000;
    }
}

Yuqoridagi kodni xato deb bo’lmaydi lekin kamchiliklari bor, ko’rinishidan standard yechimga o’xshaydi. Ushbu yechimning kamchiliklari:

  1. Tasavvur qiling yangi haydovchiga mijoz bo’ldik. Qudratali akaning narxini tizimga qo’shish uchun YukJunatishHaydovchiService classni o’zgartirishimiz lozim. Avval switch ni o’zgartirib keyin shu classga yangi metod qo’shishga to’gri keladi. Ya’ni, Ochiq/Yopiq prinsipiga teskari holat. Yaratayotgan classlarimizni iloji boricha o’zgartirishlarga yopiq, kengaytmalarga ochiq qilib yaratishimiz lozim.
  2. 3 ta emas 20 ta haydovchi bilan ishlashga to’g’ri kelsa nima bo’ladi. 20 ta qismdan iborat switch, va 20 ta turli metod bir classning ichida joylashgan bo’ladi, aniqki bu yaxshi emas.
  3. Bizning misol juda sodda ko’rinishda berilgan. YukJunatishHaydovchiServicening tizimning boshqa servislariga murojaat qilish ehtimolligi yuqori bu esa, bir classning sifat darajasi(code metrics) yomon holatga kelishi va maintance muammolari paydo bo’lishini anglatadi. Coupling darajasi o’sib, cohesion darajasi pasayadi.
  4. Codeni yozish jarayonini yanada murakkablashtiradi. Masalan, har bir haydovchining biznes mantig’i ustida alohida dasturchi ishlayapti. Endi o’ylab ko’ring, bir faylning ustida bir vaqtning o’zida bir necha odam ishlayapti, bu narsa source code control(SVN,TFS,Git) tizimlaridan foydalanishni qiyinlashtiradi. Agar SVN kabi tizimlardan foydalanib ko’rgan bo’lsangiz, bir faylni «o’zgartirishlar uchun yopish(lock)» ba’zida qanchalik bosh og’riq bo’lishi bilasiz.
  5. Yana bir muhim prinsip SRP ga hilofdir.

Bu kabi muammolar uchun eng yaxshi yechim Strategy Design Patterni bo’la oladi.

Maqsadi.

  • Bir maqsadda yaratilgan har xil algoritmlar oilasini bir to’plam sifatida ifodalaydi.
  • Har bir algoritmni enkapsulatsiya qiladi. Tayyor yaratilib qo’yilgan classlardan foydalanayotgan dasturchi uning ichida nima borligi bilan umuman ishi bo’lmaydi, u faqat ishlatishni biladi holos.
  • Runtime vaqtida bir alogiritmni boshqasiga almashtiradi.
  • P2I prinsipini amalga oshiradi. Ya’ni foydalanuvchi konkret class bilan emas interface bilan ishlaydi
  • Eng asosiy ustunligi sifatida Ochiq/Yopiq prinsipini mukammal tarzda ifodalashidir.

UML.

Quyidagi rasmda Strategyning standard ko’rinishi berilgan.

strategy design patterni 65e4b7dd3617b

Design patternning tarkibi

  • Strategy — barcha algoritmlar uchun umumiy bo’lgan qoidalar beriladi. Barcha ConcreteStrategy lar ushbu interfacedan meros oladi. P2I prinsipga ko’ra ushbu interface bilan ishlanadi.
  • ConcreteStrategy — bir xil vazifa uchun mo’ljallangan algoritmlarning konkret implementatsiyasi.
  • ContextConcreteStrategyni o’z ichida saqlaydi va chaqiradi, lekin ConcreteStrategy ning ichidagi mantiqdan umuman bexabar bo’ladi. Yuqorida bergan misolimizda shu jihatdan yutqazganmiz, ya’ni YukJunatishHaydovchiService barcha haydovchilar haqida ma’lumotga ega.

Kamchiliklarni to’g’irlaymiz.

Endi yuqorida berilgan masala uchun Strategyni DPni qo’llashga urinib ko’raylik. YukJunatishHaydovchiService ga Context sifatida qarasak bo’ladi. YukJunatishHaydovchiService ning ichidagi metodlardan qutulamiz, ularni class darajasiga olib chiqamiz. Ko’rinib turibdiki ular bir xil vazifa bajaruvchi 3 xil algoritm. Undan tashqari Quvvatali akaniyam qo’shish muammosini ko’tardik. Quyida o’zgarishlar uchun UML berilgan:

strategy design patterni 65e4b7de13de0

Quyida ushbu UMLning kod ko’rinishda berganmiz.

IHaydovchi interface.

//IStrategyga mos interface,
//barcha algoritmlar uchun umumiy qoidalarni belgilab beradi
//Context shu interface bilan ishlaydi
public interface IHaydovchi
{
    //barcha algoritmlar ushbu metodni implementatsiya qilishi lozim
    double YukJunatishNarxi(Yuk yuk);
}

Har bir algoritmni alohida class/fayl sifatida yozib olamiz.

//IHaydovchi ning Alisher aka algoritmi
public class AlisherAka : IHaydovchi
{
    public double YukJunatishNarxi(Yuk yuk)
    {
        Console.WriteLine("Uka, sal charchab turibman dam ovolishim kere yukiz sal kechro boradi.")

        //murakkab mantiqlar ham shu yerda qo'shilishi mumkin

        return 10000;
    }
}

//IHaydovchi ning Sherali aka algoritmi
public class SheraliAka : IHaydovchi
{
    public double YukJunatishNarxi(Yuk yuk)
    {
        Console.WriteLine("Ukam, iloji boricha vaqtida yetqazib oborib beraman, xavotir oma.")
        
        //murakkab mantiqlar ham shu yerda qo'shilishi mumkin

        return 12000;
    }
}

//IHaydovchi ning Turdiali aka algoritmi
public class TurdaliAka : IHaydovchi
{
    public double YukJunatishNarxi(Yuk yuk)
    {
        Console.WriteLine("Ukajon, mani bilasizu, birinchi bo'lib sizi yukiz manziliga yetadi.")

        //murakkab mantiqlar ham shu yerda qo'shilishi mumkin

        return 13000;
    }
}

YukJunatishHaydovchiService class.

// Staregy dagi Contextga mos class.
public class YukJunatishHaydovchiService
{

    //Programming to interface(P2I) prinsipiga binoan
    //ushbu class aynan qaysi algoritm ishlatilayotganlidan behabar
    //faqat chaqiradi holos.
    private readonly IHaydovchi _haydovchi;

    //IHayovchidan meros olingan classning 
    //obyektida shu yerda keladi, lekin bu class uchun 
    //bularning IHaydovchi
    public YukJunatishHaydovchiService(IHaydovchi haydovchi)
    {
        _haydovchi = haydovchi;
    }

    //Ihaydovchi darajasidaga metodni chaqirish orqali 
    //konkret algoritm ichidagi mantiqni ishlatadi
    public double YukJunatishNarxi(Yuk yuk)
    {
        return _haydovchi.YukJunatishNarxi(yuk);
    }
}

Strategy DP uchun test.

static void StrategyPatternTest()
{

    //yukni yaratamiz
    StrategyPattern.GOF.Yuk yuk = new StrategyPattern.GOF.Yuk();
    yuk.Nomi = "Iphone 5";
    yuk.Manzil = "Manzil";

    //Alisher aka orqali jo'natmoqchimiz
    AlisherAka alisherAka = new AlisherAka();
    YukJunatishHaydovchiService yukJunatishHaydovchiService = new YukJunatishHaydovchiService(alisherAka);
            
    //servisdan foydalanib narxni hisoblaymiz
    double narx = yukJunatishHaydovchiService.YukJunatishNarxi(yuk);
    Console.WriteLine(narx);
}

Nimalar o’zgardi?

  • YukJunatishHaydovchiService konkret algoritm haqida hechnima bilmaydi, u faqat IHaydovchi ni ishlatyapman deb o’ylaydi. Ya’ni, P2I amalda qo’llanildi.
  • Yangi haydovchi qo’shish uchun YukJunatishHaydovchiService o’zgarmaydi, ya’ni Open/Closed prinsip qoidalari buzilmagan.
  • Har bir algoritmni mustaqil tarzda test qilib olishimiz mumkin.
  • Coupling darajasi pasaydi.
  • Alohida algoritmlarni alohida dasturchilarga yuklash mumkin, ular bir-biriga halaqit qilmaydi.

To’liq kod bilan link orqali tanishishingiz mumkin.

Manba:

Algoritm
Strategy design patterni