CodeGym /จาวาบล็อก /สุ่ม /รูปแบบการออกแบบอะแด็ปเตอร์ช่วยแก้ปัญหาอะไรได้บ้าง
John Squirrels
ระดับ
San Francisco

รูปแบบการออกแบบอะแด็ปเตอร์ช่วยแก้ปัญหาอะไรได้บ้าง

เผยแพร่ในกลุ่ม
การพัฒนาซอฟต์แวร์ทำได้ยากขึ้นเนื่องจากส่วนประกอบที่เข้ากันไม่ได้ซึ่งจำเป็นต้องทำงานร่วมกัน ตัวอย่างเช่น หากคุณต้องการรวมไลบรารีใหม่เข้ากับแพลตฟอร์มเก่าที่เขียนด้วย Java เวอร์ชันก่อนหน้า คุณอาจพบอ็อบเจ็กต์ที่เข้ากันไม่ได้หรืออินเทอร์เฟซที่เข้ากันไม่ได้ รูปแบบการออกแบบอะแด็ปเตอร์ช่วยแก้ปัญหาอะไรได้บ้าง  - 1จะทำอย่างไรในกรณีนี้? เขียนรหัสใหม่? เราไม่สามารถทำเช่นนั้นได้ เนื่องจากการวิเคราะห์ระบบจะใช้เวลามาก มิฉะนั้น ตรรกะภายในของแอปพลิเคชันจะถูกละเมิด เพื่อแก้ปัญหานี้ รูปแบบอะแด็ปเตอร์ถูกสร้างขึ้น ช่วยให้วัตถุที่มีอินเทอร์เฟซที่เข้ากันไม่ได้ทำงานร่วมกันได้ มาดูวิธีใช้กัน!

เพิ่มเติมเกี่ยวกับปัญหา

ก่อนอื่น เราจะจำลองพฤติกรรมของระบบเก่า สมมติว่ามันสร้างข้อแก้ตัวสำหรับการไปทำงานหรือไปโรงเรียนสาย ในการทำเช่น นี้มีExcuseอินเทอร์เฟซที่มีgenerateExcuse()และเมธอด likeExcuse()dislikeExcuse()

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
คลาสWorkExcuseใช้อินเทอร์เฟซนี้:

public class WorkExcuse implements Excuse {
   private String[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
   "the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
   "my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
   private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};

   @Override
   public String generateExcuse() { // Randomly select an excuse from the array
       String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
               apologies[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Duplicate the element in the array so that its chances of being chosen are higher
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Remove the item from the array
   }
}
ลองทดสอบตัวอย่างของเรา:

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
เอาท์พุต:

"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
ลองจินตนาการว่าคุณได้เปิดบริการสร้างข้อแก้ตัว รวบรวมสถิติ และสังเกตเห็นว่าผู้ใช้ส่วนใหญ่ของคุณเป็นนักศึกษามหาวิทยาลัย เพื่อให้บริการกลุ่มนี้ได้ดียิ่งขึ้น คุณได้ขอให้นักพัฒนารายอื่นสร้างระบบที่สร้างข้อแก้ตัวสำหรับนักศึกษามหาวิทยาลัยโดยเฉพาะ ทีมพัฒนาได้ทำการวิจัยตลาด จัดอันดับข้อแก้ตัว ติดตั้งปัญญาประดิษฐ์ และรวมบริการเข้ากับรายงานการจราจร รายงานสภาพอากาศ และอื่นๆ ตอนนี้ คุณมีไลบรารีสำหรับสร้างข้อแก้ตัวสำหรับนักศึกษามหาวิทยาลัย แต่มีอินเทอร์เฟซที่แตกต่างกันStudentExcuse:

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
อินเทอร์เฟซนี้มีสองวิธี: generateExcuseซึ่งสร้างข้อแก้ตัว และdislikeExcuseซึ่งจะป้องกันไม่ให้ข้อแก้ตัวปรากฏขึ้นอีกในอนาคต ไม่สามารถแก้ไขไลบรารีของบุคคลที่สามได้ นั่นคือคุณไม่สามารถเปลี่ยนซอร์สโค้ดได้ สิ่งที่เรามีตอนนี้คือระบบที่มีสองคลาสที่ใช้Excuseอินเทอร์เฟซ และไลบรารีที่มีSuperStudentExcuseคลาสที่ใช้StudentExcuseอินเทอร์เฟซ:

public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Logic for the new functionality
       return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Adds the reason to a blacklist
   }
}
ไม่สามารถเปลี่ยนรหัสได้ ลำดับชั้นของคลาสปัจจุบันมีลักษณะดังนี้: รูปแบบการออกแบบอะแด็ปเตอร์ช่วยแก้ปัญหาอะไรได้บ้าง  - 2ระบบเวอร์ชันนี้ใช้งานได้กับอินเทอร์เฟซ Excuse เท่านั้น คุณไม่สามารถเขียนโค้ดใหม่ได้: ในแอปพลิเคชันขนาดใหญ่ การเปลี่ยนแปลงดังกล่าวอาจกลายเป็นกระบวนการที่ใช้เวลานานหรือทำลายตรรกะของแอปพลิเคชัน เราสามารถแนะนำอินเทอร์เฟซพื้นฐานและขยายลำดับชั้น: รูปแบบการออกแบบอะแด็ปเตอร์ช่วยแก้ปัญหาอะไรได้บ้าง  - 3ในการทำเช่นนี้ เราต้องเปลี่ยนชื่อExcuseอินเทอร์เฟซ แต่ลำดับชั้นพิเศษเป็นสิ่งที่ไม่พึงปรารถนาในการใช้งานที่ร้ายแรง: การแนะนำองค์ประกอบรูททั่วไปทำให้สถาปัตยกรรมเสียหาย คุณควรใช้คลาสระดับกลางที่จะให้เราใช้ทั้งฟังก์ชันใหม่และเก่าโดยสูญเสียน้อยที่สุด กล่าวโดยย่อคุณต้องมีอะแดปเตอร์

หลักการเบื้องหลังรูปแบบอะแดปเตอร์

อะแด็ปเตอร์เป็นออบเจกต์ระดับกลางที่ช่วยให้การเรียกเมธอดของอ็อบเจ็กต์หนึ่งเข้าใจได้โดยอีกอ็อบเจ็กต์หนึ่ง ลองใช้อะแดปเตอร์สำหรับตัวอย่างของเราและเรียกมันMiddlewareว่า อะแดปเตอร์ของเราต้องใช้อินเทอร์เฟซที่เข้ากันได้กับหนึ่งในวัตถุ ปล่อยให้มันExcuseเป็น สิ่งนี้ทำให้สามารถMiddlewareเรียกใช้เมธอดของวัตถุตัวแรกได้ Middlewareรับสายและส่งต่อด้วยวิธีที่เข้ากันได้ไปยังวัตถุที่สอง นี่คือMiddlewareการใช้งานกับgenerateExcuseและdislikeExcuseวิธีการ:

public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // The method first adds the excuse to the blacklist,
        // Then passes it to the dislikeExcuse method of the superStudentExcuse object.
    }
   // The likeExcuse method will appear later
}
การทดสอบ (ในรหัสลูกค้า):

public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // We create objects of the classes
       StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
       System.out.println("An ordinary excuse for an employee:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
       System.out.println("Using new functionality with the adapter:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
   }
}
เอาท์พุต:

An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position. Using new functionality with the adapter:
ข้อแก้ตัวที่เหลือเชื่อที่ปรับให้เข้ากับสภาพอากาศในปัจจุบัน การจราจรติดขัด หรือความล่าช้าของตารางการขนส่งสาธารณะ เมธอดgenerateExcuseเพียงแค่ส่งการเรียกไปยังอ็อบเจกต์อื่น โดยไม่มีการเปลี่ยนแปลงเพิ่มเติมใดๆ วิธีการ นี้dislikeExcuseกำหนดให้เราต้องขึ้นบัญชีดำเพื่อแก้ตัวก่อน ความสามารถในการประมวลผลข้อมูลระดับกลางคือเหตุผลที่ผู้คนชื่นชอบรูปแบบอะแดปเตอร์ แล้วเมธอดล่ะlikeExcuseซึ่งเป็นส่วนหนึ่งของExcuseอินเทอร์เฟซ แต่ไม่ใช่ส่วนหนึ่งของStudentExcuseอินเทอร์เฟซ ฟังก์ชันใหม่ไม่รองรับการทำงานนี้ ถูกUnsupportedOperationExceptionประดิษฐ์ขึ้นสำหรับสถานการณ์นี้ จะถูกส่งออกหากการดำเนินการที่ร้องขอไม่ได้รับการสนับสนุน มาใช้กันเถอะ นี่คือลักษณะของMiddlewareการใช้งานใหม่ของคลาส:

public class Middleware implements Excuse {

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) {
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse();
   }

   @Override
   public void likeExcuse(String excuse) {
       throw new UnsupportedOperationException("The likeExcuse method is not supported by the new functionality");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // The method accesses a database to fetch additional information,
       // and then passes it to the superStudentExcuse object's dislikeExcuse method.
   }
}
เมื่อมองแวบแรก โซลูชันนี้ดูไม่ค่อยดีนัก แต่การเลียนแบบฟังก์ชันการทำงานอาจทำให้สถานการณ์ซับซ้อนขึ้นได้ หากไคลเอนต์ให้ความสนใจ และอะแดปเตอร์ได้รับการจัดทำเป็นเอกสารอย่างดี โซลูชันดังกล่าวก็เป็นที่ยอมรับได้

เมื่อใดควรใช้อะแดปเตอร์

  1. เมื่อคุณต้องการใช้คลาสของบุคคลที่สาม แต่อินเทอร์เฟซนั้นเข้ากันไม่ได้กับแอปพลิเคชันหลัก ตัวอย่างด้านบนแสดงวิธีสร้างออบเจกต์อะแดปเตอร์ที่รวมการโทรในรูปแบบที่ออบเจ็กต์เป้าหมายสามารถเข้าใจได้

  2. เมื่อคลาสย่อยที่มีอยู่หลายคลาสต้องการฟังก์ชันทั่วไปบางอย่าง แทนที่จะสร้างคลาสย่อยเพิ่มเติม (ซึ่งจะนำไปสู่การทำซ้ำโค้ด) ควรใช้อะแดปเตอร์

ข้อดีและข้อเสีย

ข้อได้เปรียบ:อะแด็ปเตอร์จะซ่อนรายละเอียดการประมวลผลคำขอจากอ็อบเจ็กต์หนึ่งไปยังอีกอ็อบเจ็กต์หนึ่งจากไคลเอ็นต์ รหัสลูกค้าไม่คิดเกี่ยวกับการจัดรูปแบบข้อมูลหรือจัดการการเรียกไปยังวิธีการเป้าหมาย มันซับซ้อนเกินไป และโปรแกรมเมอร์ก็ขี้เกียจ :) ข้อเสีย:โค้ดเบสของโปรเจ็กต์ซับซ้อนด้วยคลาสเพิ่มเติม หากคุณมีอินเทอร์เฟซที่เข้ากันไม่ได้จำนวนมาก จำนวนคลาสเพิ่มเติมอาจไม่สามารถจัดการได้

อย่าสับสนระหว่างอะแดปเตอร์กับส่วนหน้าหรือตัวตกแต่ง

ด้วยการตรวจสอบเพียงผิวเผิน อะแดปเตอร์อาจสับสนกับรูปแบบด้านหน้าอาคารและตัวตกแต่ง ความแตกต่างระหว่างอะแด็ปเตอร์และส่วนหน้าคือส่วนหน้าจะแนะนำอินเทอร์เฟซใหม่และรวมระบบย่อยทั้งหมด และมัณฑนากรซึ่งแตกต่างจากอะแดปเตอร์คือเปลี่ยนวัตถุเองแทนที่จะเป็นอินเทอร์เฟซ

อัลกอริทึมทีละขั้นตอน

  1. ก่อนอื่น ต้องแน่ใจว่าคุณมีปัญหาที่รูปแบบนี้สามารถแก้ไขได้

  2. กำหนดอินเทอร์เฟซไคลเอ็นต์ที่จะใช้โต้ตอบทางอ้อมกับวัตถุที่เข้ากันไม่ได้

  3. ทำให้คลาสอะแด็ปเตอร์สืบทอดอินเตอร์เฟสที่กำหนดไว้ในขั้นตอนก่อนหน้า

  4. ในคลาสอะแด็ปเตอร์ ให้สร้างฟิลด์เพื่อจัดเก็บการอ้างอิงไปยังอ็อบเจกต์อะแด็ปเตอร์ การอ้างอิงนี้จะถูกส่งผ่านไปยังตัวสร้าง

  5. ใช้วิธีอินเทอร์เฟซไคลเอนต์ทั้งหมดในอะแด็ปเตอร์ วิธีการอาจ:

    • ส่งต่อสายโดยไม่ต้องทำการเปลี่ยนแปลงใดๆ

    • แก้ไขหรือเสริมข้อมูล เพิ่ม/ลด จำนวนการโทรไปยังเมธอดเป้าหมาย เป็นต้น

    • ในกรณีที่รุนแรง หากเมธอดใดยังคงใช้ร่วมกันไม่ได้ ให้โยน UnsupportedOperationException การดำเนินการที่ไม่ได้รับการสนับสนุนจะต้องมีการจัดทำเป็นเอกสารอย่างเคร่งครัด

  6. หากแอ็พพลิเคชันใช้เฉพาะคลาสของอะแด็ปเตอร์ผ่านไคลเอ็นต์อินเทอร์เฟซ (ตามตัวอย่างด้านบน) อะแด็ปเตอร์จะขยายได้โดยไม่ลำบากในอนาคต

แน่นอนว่ารูปแบบการออกแบบนี้ไม่ใช่ยาครอบจักรวาลสำหรับความเจ็บป่วยทั้งหมด แต่มันสามารถช่วยคุณแก้ปัญหาความไม่ลงรอยกันระหว่างวัตถุที่มีอินเทอร์เฟซต่างกันได้อย่างสง่างาม นักพัฒนาที่รู้รูปแบบพื้นฐานนั้นนำหน้าผู้ที่รู้เพียงวิธีเขียนอัลกอริทึมไปหลายก้าว เนื่องจากรูปแบบการออกแบบจำเป็นต่อการสร้างแอปพลิเคชันที่จริงจัง การใช้โค้ดซ้ำไม่ใช่เรื่องยาก และการบำรุงรักษาก็เป็นเรื่องสนุก นั่นคือทั้งหมดสำหรับวันนี้! แต่เดี๋ยวเราจะมาทำความรู้จักรูปแบบการออกแบบต่างๆ กันต่อไปครับ :)
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION