CodeGym /Java Blog /Random /Pattern ng Disenyo ng Diskarte
John Squirrels
Antas
San Francisco

Pattern ng Disenyo ng Diskarte

Nai-publish sa grupo
Hi! Sa aralin ngayon, pag-uusapan natin ang pattern ng Strategy. Sa nakaraang mga aralin, sa madaling sabi ay nakilala na natin ang konsepto ng mana. Kung sakaling nakalimutan mo, ipapaalala ko sa iyo na ang terminong ito ay tumutukoy sa isang karaniwang solusyon sa isang karaniwang gawain sa programming. Sa CodeGym, madalas naming sinasabi na maaari mong i-google ang sagot sa halos anumang tanong. Ito ay dahil ang iyong gawain, anuman ito, ay malamang na matagumpay na nalutas ng ibang tao. Ang mga pattern ay sinubukan-at-totoong solusyon sa mga pinakakaraniwang gawain, o mga pamamaraan para sa paglutas ng mga problemadong sitwasyon. Ang mga ito ay tulad ng "mga gulong" na hindi mo kailangang muling mag-imbento sa iyong sarili, ngunit kailangan mong malaman kung paano at kailan ito gagamitin :) Ang isa pang layunin para sa mga pattern ay upang i-promote ang pare-parehong arkitektura. Ang pagbabasa ng code ng ibang tao ay hindi madaling gawain! Ang bawat isa ay nagsusulat ng iba't ibang code, dahil ang parehong gawain ay maaaring malutas sa maraming paraan. Ngunit ang paggamit ng mga pattern ay nakakatulong sa iba't ibang programmer na maunawaan ang programming logic nang hindi sinisiyasat ang bawat linya ng code (kahit na nakita ito sa unang pagkakataon!) Ngayon ay tinitingnan natin ang isa sa mga pinakakaraniwang pattern ng disenyo na tinatawag na "Diskarte". Pattern ng disenyo: Diskarte - 2Isipin na nagsusulat kami ng isang programa na aktibong gagana sa mga object ng Conveyance. Hindi mahalaga kung ano ang eksaktong ginagawa ng aming programa. Gumawa kami ng hierarchy ng klase na may isang Conveyance parent class at tatlong child class: Sedan , Truck at F1Car .

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {
}

public class Truck extends Conveyance {
}

public class F1Car extends Conveyance {
}
Ang lahat ng tatlong klase ng bata ay nagmamana ng dalawang karaniwang pamamaraan mula sa magulang: go() at stop() . Ang aming programa ay napaka-simple: ang aming mga sasakyan ay maaari lamang sumulong at ilapat ang preno. Sa pagpapatuloy ng aming trabaho, nagpasya kaming bigyan ang mga kotse ng isang bagong paraan: fill() (ibig sabihin, "punan ang tangke ng gas"). Idinagdag namin ito sa klase ng magulang ng Conveyance :

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
  
   public void fill() {
       System.out.println("Refueling!");
   }
}
May mga problema ba talaga na bumangon sa ganitong simpleng sitwasyon? Sa katunayan, mayroon na silang... Pattern ng disenyo: Diskarte - 3

public class Stroller extends Conveyance {

   public void fill() {
      
       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
Ang aming programa ay mayroon na ngayong conveyance (isang baby stroller) na hindi angkop sa pangkalahatang konsepto. Maaari itong magkaroon ng mga pedal o kontrolado ng radyo, ngunit isang bagay ang tiyak - wala itong anumang lugar na ibubuhos ng gas. Ang aming hierarchy ng klase ay nagdulot ng mga karaniwang pamamaraan na mamana ng mga klase na hindi nangangailangan ng mga ito. Ano ang dapat nating gawin sa ganitong sitwasyon? Well, maaari naming i-override ang fill() na paraan sa klase ng Stroller para walang mangyari kapag sinubukan mong lagyan ng gatong ang stroller:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
Ngunit hindi ito matatawag na matagumpay na solusyon kung walang ibang dahilan kundi ang duplicate na code. Halimbawa, karamihan sa mga klase ay gagamit ng paraan ng parent class, ngunit ang iba ay mapipilitang i-override ito. Kung mayroon tayong 15 klase at dapat nating i-override ang pag-uugali sa 5-6 sa mga ito, magiging malawak ang pagdoble ng code. Maaaring makatulong sa amin ang mga interface? Halimbawa, tulad nito:

public interface Fillable {
  
   public void fill();
}
Gagawa kami ng isang Fillable na interface na may isang fill() na paraan. Pagkatapos, ipapatupad ng mga conveyance na iyon na kailangang mag-refuel ang interface na ito, habang ang ibang conveyance (halimbawa, ang aming baby stroller) ay hindi. Ngunit ang pagpipiliang ito ay hindi angkop sa amin. Sa hinaharap, ang aming class hierarchy ay maaaring lumaki at maging napakalaki (imagine na lang kung gaano karaming iba't ibang uri ng conveyances ang mayroon sa mundo). Inabandona namin ang nakaraang bersyon na kinasasangkutan ng mana, dahil ayaw naming i-override ang fill()pamamaraan ng maraming, maraming beses. Ngayon ay kailangan nating ipatupad ito sa bawat klase! At paano kung mayroon tayong 50? At kung ang mga madalas na pagbabago ay gagawin sa aming programa (at ito ay halos palaging totoo para sa mga tunay na programa!), kailangan naming magmadali sa lahat ng 50 mga klase at manu-manong baguhin ang pag-uugali ng bawat isa sa kanila. Kaya ano, sa huli, ang dapat nating gawin sa sitwasyong ito? Upang malutas ang aming problema, pipili kami ng ibang paraan. Ibig sabihin, ihihiwalay natin ang pag-uugali ng ating klase mula sa klase mismo. Anong ibig sabihin niyan? Tulad ng alam mo, ang bawat bagay ay may estado (isang set ng data) at pag-uugali (isang hanay ng mga pamamaraan). Ang pag-uugali ng aming klase ng conveyance ay binubuo ng tatlong paraan: go() , stop() at fill() . Ang unang dalawang pamamaraan ay maayos tulad ng mga ito. Ngunit ililipat namin ang ikatlong paraan sa labas ngConveyance class. Ihihiwalay nito ang pag-uugali mula sa klase (mas tumpak, ihihiwalay lamang nito ang bahagi ng pag-uugali, dahil mananatili ang unang dalawang pamamaraan kung nasaan sila). Kaya't saan natin dapat ilagay ang ating fill() na pamamaraan? Walang pumapasok sa isip :/ Parang sakto lang sa dapat. Ililipat namin ito sa isang hiwalay na interface: FillStrategy !

public interface FillStrategy {

   public void fill();
}
Bakit kailangan natin ng ganoong interface? Diretso lang lahat. Ngayon ay maaari tayong lumikha ng ilang mga klase na nagpapatupad ng interface na ito:

public class HybridFillStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas or electricity — your choice!");
   }
}

public class F1PitstopStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas only after all other pit stop procedures are complete!");
   }
}

public class StandardFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Just refuel with gas!");
   }
}
Gumawa kami ng tatlong diskarte sa pag-uugali: isa para sa mga ordinaryong kotse, isa para sa mga hybrid, at isa para sa Formula 1 na mga race car. Ang bawat diskarte ay nagpapatupad ng ibang refueling algorithm. Sa aming kaso, ipinapakita lang namin ang isang string sa console, ngunit ang bawat pamamaraan ay maaaring maglaman ng ilang kumplikadong lohika. Anong susunod nating gagawin?

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
  
}
Ginagamit namin ang aming FillStrategy interface bilang field sa Conveyance parent class. Tandaan na hindi kami nagsasaad ng partikular na pagpapatupad — gumagamit kami ng interface. Ang mga klase ng kotse ay mangangailangan ng mga partikular na pagpapatupad ng FillStrategy interface:

public class F1Car extends Conveyance {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridCar extends Conveyance {

   public HybridCar() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       this.fillStrategy = new StandardFillStrategy();
   }
}

Tingnan natin kung ano ang nakuha natin!

public class Main {

   public static void main(String[] args) {

       Conveyance sedan = new Sedan();
       Conveyance hybrid = new HybridCar();
       Conveyance f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
Output ng console:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Malaki! Ang proseso ng refueling ay gumagana ayon sa nararapat! Sa pamamagitan ng paraan, walang pumipigil sa amin mula sa paggamit ng diskarte bilang isang parameter sa constructor! Halimbawa, tulad nito:

public class Conveyance {

   private FillStrategy fillStrategy;

   public Conveyance(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       super(new StandardFillStrategy());
   }
}



public class HybridCar extends Conveyance {

   public HybridCar() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Conveyance {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
Patakbuhin natin ang aming pangunahing() na pamamaraan (na nananatiling hindi nagbabago). Nakukuha namin ang parehong resulta! Output ng console:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Tinutukoy ng pattern ng disenyo ng diskarte ang isang pamilya ng mga algorithm, isinasama ang bawat isa sa kanila, at tinitiyak na ang mga ito ay maaaring palitan. Hinahayaan ka nitong baguhin ang mga algorithm anuman ang paggamit ng mga ito ng kliyente (ang kahulugang ito, na kinuha mula sa aklat na "Head First Design Patterns", ay tila napakahusay para sa akin). Pattern ng disenyo: Diskarte - 4Tinukoy na namin ang pamilya ng mga algorithm kung saan kami interesado (mga paraan ng pag-refuel ng mga kotse) sa magkakahiwalay na interface na may iba't ibang pagpapatupad. Pinaghiwalay namin sila sa mismong sasakyan. Ngayon kung kailangan naming gumawa ng anumang mga pagbabago sa isang partikular na refueling algorithm, hindi ito makakaapekto sa aming mga klase ng kotse sa anumang paraan. At para makamit ang pagpapalitan, kailangan lang naming magdagdag ng isang paraan ng setter sa aming klase ng Conveyance :

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
Ngayon ay maaari na nating baguhin ang mga diskarte sa mabilisang:

public class Main {

   public static void main(String[] args) {

       Stroller stroller= new Stroller();
       stroller.setFillStrategy(new StandardFillStrategy());

       stroller.fill();
   }
}
Kung ang mga baby stroller ay biglang nagsimulang tumakbo sa gasolina, ang aming programa ay magiging handa na pangasiwaan ang sitwasyong ito :) At iyon lang! Natutunan mo ang isa pang pattern ng disenyo na walang alinlangan na mahalaga at kapaki-pakinabang kapag gumagawa ng mga tunay na proyekto :) Hanggang sa susunod!
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION