CodeGym /จาวาบล็อก /สุ่ม /รูปแบบต่อต้านคืออะไร? มาดูตัวอย่างกัน (ตอนที่ 1)
John Squirrels
ระดับ
San Francisco

รูปแบบต่อต้านคืออะไร? มาดูตัวอย่างกัน (ตอนที่ 1)

เผยแพร่ในกลุ่ม
สวัสดีทุกคน! วันก่อนฉันไปสัมภาษณ์งาน และฉันถูกถามคำถามเกี่ยวกับรูปแบบการต่อต้าน: มันคืออะไร มีประเภทใดบ้าง และมีตัวอย่างที่ใช้ได้จริงอะไรบ้าง แน่นอน ฉันตอบคำถามนี้แต่ผิวเผินมาก เนื่องจากฉันไม่เคยเจาะลึกในหัวข้อนี้มาก่อน หลังจากการสัมภาษณ์ ฉันเริ่มท่องอินเทอร์เน็ตและหมกมุ่นอยู่กับหัวข้อนี้มากขึ้นเรื่อยๆ รูปแบบต่อต้านคืออะไร?  มาดูตัวอย่างกัน (ตอนที่ 1) - 1 วันนี้ฉันต้องการให้ภาพรวมโดยย่อของรูปแบบการต่อต้านที่เป็นที่นิยมมากที่สุดและทบทวนตัวอย่างบางส่วน ฉันหวังว่าการอ่านสิ่งนี้จะให้ความรู้ที่คุณต้องการในด้านนี้ มาเริ่มกันเลย! ก่อนที่เราจะพูดถึงว่า Anti-Pattern คืออะไร เรามานึกถึง Design Pattern กันก่อนดีกว่า รูปแบบการออกแบบเป็นโซลูชันทางสถาปัตยกรรมที่ทำซ้ำได้สำหรับปัญหาหรือสถานการณ์ทั่วไปที่เกิดขึ้นเมื่อออกแบบแอปพลิเคชัน แต่วันนี้เราไม่ได้พูดถึงพวกเขา แต่พูดถึงสิ่งที่ตรงกันข้าม นั่นคือการต่อต้านแบบแผน การต่อต้านแบบแผนเป็นวิธีการที่แพร่หลายแต่ไม่ได้ผล มีความเสี่ยง และ/หรือไม่เกิดผลในการแก้ปัญหาทั่วไปประเภทหนึ่ง กล่าวอีกนัยหนึ่ง นี่คือรูปแบบของความผิดพลาด (บางครั้งเรียกว่ากับดัก) ตามกฎแล้วรูปแบบการต่อต้านจะแบ่งออกเป็นประเภทต่อไปนี้:
  1. รูปแบบต่อต้านทางสถาปัตยกรรม — รูปแบบต่อต้านเหล่านี้เกิดขึ้นเมื่อโครงสร้างของระบบได้รับการออกแบบ (โดยทั่วไปแล้วโดยสถาปนิก)
  2. รูปแบบการต่อต้านการจัดการ/องค์กร — สิ่งเหล่านี้เป็นการต่อต้านรูปแบบในการจัดการโครงการ ซึ่งมักพบโดยผู้จัดการหลายคน (หรือกลุ่มผู้จัดการ)
  3. รูปแบบต่อต้านการพัฒนา — รูปแบบต่อต้านเหล่านี้เกิดขึ้นเมื่อระบบถูกใช้งานโดยโปรแกรมเมอร์ทั่วไป
รูปแบบต่อต้านที่หลากหลายนั้นแปลกใหม่กว่ามาก แต่เราจะไม่พิจารณาทั้งหมดในวันนี้ สำหรับนักพัฒนาทั่วไปนั่นอาจมากเกินไป สำหรับผู้เริ่มต้น ลองพิจารณารูปแบบการต่อต้านการจัดการเป็นตัวอย่าง

1. การวิเคราะห์อัมพาต

การวิเคราะห์อัมพาตถือเป็นรูปแบบการต่อต้านการบริหารแบบคลาสสิก มันเกี่ยวข้องกับการวิเคราะห์สถานการณ์มากเกินไปในระหว่างการวางแผน เพื่อไม่ให้มีการตัดสินใจหรือดำเนินการใด ๆ โดยพื้นฐานแล้วจะทำให้กระบวนการพัฒนาเป็นอัมพาต สิ่งนี้มักจะเกิดขึ้นเมื่อเป้าหมายคือการบรรลุความสมบูรณ์แบบและพิจารณาทุกอย่างอย่างสมบูรณ์ในช่วงการวิเคราะห์ การต่อต้านรูปแบบนี้มีลักษณะเด่นคือการเดินเป็นวงกลม (วงจรปิดแบบรันออฟเดอะมิล) การแก้ไขและสร้างแบบจำลองโดยละเอียด ซึ่งจะรบกวนขั้นตอนการทำงาน ตัวอย่างเช่น คุณกำลังพยายามทำนายสิ่งต่างๆ ในระดับหนึ่ง แต่จะทำอย่างไรถ้าจู่ๆ ผู้ใช้ต้องการสร้างรายชื่อพนักงานตามตัวอักษรตัวที่สี่และห้าของชื่อ รวมถึงรายชื่อโครงการที่พวกเขาใช้เวลาทำงานมากที่สุด ระหว่างวันปีใหม่และวันสตรีสากลตลอด 4 ปีที่ผ่านมา? ในสาระสำคัญก็คือ ' วิเคราะห์มากเกินไป ต่อไปนี้คือเคล็ดลับบางประการสำหรับการต่อสู้กับอัมพาตจากการวิเคราะห์:
  1. คุณต้องกำหนดเป้าหมายระยะยาวเป็นสัญญาณสำหรับการตัดสินใจ เพื่อให้การตัดสินใจแต่ละครั้งของคุณทำให้คุณเข้าใกล้เป้าหมายมากขึ้นแทนที่จะทำให้คุณหยุดนิ่ง
  2. อย่าจดจ่ออยู่กับเรื่องเล็ก ๆ น้อย ๆ (ทำไมต้องตัดสินใจเกี่ยวกับรายละเอียดเล็กน้อยราวกับว่าเป็นการตัดสินใจที่สำคัญที่สุดในชีวิตของคุณ)
  3. กำหนดเส้นตายสำหรับการตัดสินใจ
  4. อย่าพยายามทำงานให้เสร็จอย่างสมบูรณ์ - ดีกว่าที่จะทำมันให้ดี
ไม่จำเป็นต้องลงลึกเกินไปที่นี่ ดังนั้นเราจะไม่พิจารณารูปแบบการต่อต้านการบริหารอื่นๆ ดังนั้น หากไม่มีการแนะนำใด ๆ เราจะไปยังรูปแบบการต่อต้านสถาปัตยกรรม เนื่องจากบทความนี้มีแนวโน้มที่จะอ่านโดยนักพัฒนาในอนาคตมากกว่าผู้จัดการ

2. วัตถุพระเจ้า

วัตถุพระเจ้าเป็นรูปแบบการต่อต้านที่อธิบายถึงความเข้มข้นมากเกินไปของฟังก์ชั่นทุกประเภทและข้อมูลที่แตกต่างกันจำนวนมาก (วัตถุที่แอปพลิเคชันหมุนรอบ) ยกตัวอย่างเล็กน้อย:

public class SomeUserGodObject {
   private static final String FIND_ALL_USERS_EN = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users;
   private static final String FIND_BY_ID = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users WHERE id = ?";
   private static final String FIND_ALL_CUSTOMERS = "SELECT id, u.email, u.phone, u.first_name_en, u.middle_name_en, u.last_name_en, u.created_date" +
           "  WHERE u.id IN (SELECT up.user_id FROM user_permissions up WHERE up.permission_id = ?)";
   private static final String FIND_BY_EMAIL = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_dateFROM users WHERE email = ?";
   private static final String LIMIT_OFFSET = " LIMIT ? OFFSET ?";
   private static final String ORDER = " ORDER BY ISNULL(last_name_en), last_name_en, ISNULL(first_name_en), first_name_en, ISNULL(last_name_ru), " +
           "last_name_ru, ISNULL(first_name_ru), first_name_ru";
   private static final String CREATE_USER_EN = "INSERT INTO users(id, phone, email, first_name_en, middle_name_en, last_name_en, created_date) " +
           "VALUES (?, ?, ?, ?, ?, ?, ?)";
   private static final String FIND_ID_BY_LANG_CODE = "SELECT id FROM languages WHERE lang_code = ?";
                                  ........
   private final JdbcTemplate jdbcTemplate;
   private Map<String, String> firstName;
   private Map<String, String> middleName;
   private Map<String, String> lastName;
   private List<Long> permission;
                                   ........
   @Override
   public List<User> findAllEnCustomers(Long permissionId) {
       return jdbcTemplate.query( FIND_ALL_CUSTOMERS + ORDER, userRowMapper(), permissionId);
   }
   @Override
   public List<User> findAllEn() {
       return jdbcTemplate.query(FIND_ALL_USERS_EN + ORDER, userRowMapper());
   }
   @Override
   public Optional<List<User>> findAllEnByEmail(String email) {
       var query = FIND_ALL_USERS_EN + FIND_BY_EMAIL + ORDER;
       return Optional.ofNullable(jdbcTemplate.query(query, userRowMapper(), email));
   }
                              .............
   private List<User> findAllWithoutPageEn(Long permissionId, Type type) {
       switch (type) {
           case USERS:
               return findAllEnUsers(permissionId);
           case CUSTOMERS:
               return findAllEnCustomers(permissionId);
           default:
               return findAllEn();
       }
   }
                              ..............…

   private RowMapper<User> userRowMapperEn() {
       return (rs, rowNum) ->
               User.builder()
                       .id(rs.getLong("id"))
                       .email(rs.getString("email"))
                       .accessFailed(rs.getInt("access_counter"))
                       .createdDate(rs.getObject("created_date", LocalDateTime.class))
                       .firstName(rs.getString("first_name_en"))
                       .middleName(rs.getString("middle_name_en"))
                       .lastName(rs.getString("last_name_en"))
                       .phone(rs.getString("phone"))
                       .build();
   }
}
ที่นี่เราเห็นชั้นเรียนขนาดใหญ่ที่ทำทุกอย่าง ประกอบด้วยแบบสอบถามฐานข้อมูลและข้อมูลบางส่วน นอกจากนี้เรายังเห็นเมธอดส่วนหน้าของ findAllWithoutPageEn ซึ่งรวมถึงตรรกะทางธุรกิจด้วย วัตถุระดับเทพเช่นนี้กลายเป็นสิ่งใหญ่โตและน่าอึดอัดใจที่จะดูแลรักษาอย่างเหมาะสม เราต้องยุ่งกับมันในโค้ดทุกชิ้น ส่วนประกอบของระบบจำนวนมากพึ่งพามันและเชื่อมโยงอย่างแน่นหนา การรักษารหัสดังกล่าวจะยากขึ้นเรื่อย ๆ ในกรณีเช่นนี้ ควรแยกโค้ดออกเป็นคลาสต่างๆ โดยแต่ละคลาสจะมีจุดประสงค์เดียวเท่านั้น ในตัวอย่างนี้ เราสามารถแบ่งวัตถุพระเจ้าออกเป็นคลาส Dao:

public class UserDaoImpl {
   private static final String FIND_ALL_USERS_EN = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users;
   private static final String FIND_BY_ID = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users WHERE id = ?";
  
                                   ........
   private final JdbcTemplate jdbcTemplate;
                                                        
                                   ........
   @Override
   public List<User> findAllEnCustomers(Long permissionId) {
       return jdbcTemplate.query(FIND_ALL_CUSTOMERS + ORDER, userRowMapper(), permissionId);
   }
   @Override
   public List<User> findAllEn() {
       return jdbcTemplate.query(FIND_ALL_USERS_EN + ORDER, userRowMapper());
   }
  
                               ........
}
คลาสที่มีข้อมูลและวิธีการเข้าถึงข้อมูล:

public class UserInfo {
   private Map<String, String> firstName;
                     …..
   public Map<String, String> getFirstName() {
       return firstName;
   }
   public void setFirstName(Map<String, String> firstName) {
       this.firstName = firstName;
   }
                    ....
และจะเป็นการเหมาะสมกว่าที่จะย้ายเมธอดด้วยตรรกะทางธุรกิจไปยังบริการ:

private List<User> findAllWithoutPageEn(Long permissionId, Type type) {
   switch (type) {
       case USERS:
           return findAllEnUsers(permissionId);
       case CUSTOMERS:
           return findAllEnCustomers(permissionId);
       default:
           return findAllEn();
   }
}

3. ซิงเกิลตัน

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

  2. singleton ฝ่าฝืนหลักการ SOLID ซึ่งเป็นหลักความรับผิดชอบเดียว: นอกเหนือจากหน้าที่โดยตรงแล้ว singleton class ยังควบคุมจำนวนของอินสแตนซ์อีกด้วย

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

    การมีซิงเกิลตันช่วยลดความสามารถในการทดสอบของแอปพลิเคชันโดยรวมและคลาสที่ใช้ซิงเกิลตันโดยเฉพาะ ก่อนอื่น คุณไม่สามารถแทนที่ singleton ด้วยวัตถุจำลองได้ ประการที่สอง หากซิงเกิลตันมีส่วนต่อประสานสำหรับเปลี่ยนสถานะ การทดสอบจะขึ้นอยู่กับอีกอันหนึ่ง

    กล่าวอีกนัยหนึ่ง singleton เพิ่มการมีเพศสัมพันธ์ และทุกสิ่งที่กล่าวถึงข้างต้นไม่มีอะไรมากไปกว่าผลของการมีเพศสัมพันธ์ที่เพิ่มขึ้น

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

    อันตรายที่สุดคือความพยายามในการสร้างสถาปัตยกรรมแอปพลิเคชันทั้งหมดโดยใช้ซิงเกิลตัน มีทางเลือกที่ยอดเยี่ยมมากมายสำหรับแนวทางนี้ ตัวอย่างที่สำคัญที่สุดคือ Spring ซึ่งก็คือคอนเทนเนอร์ IoC ซึ่งเป็นวิธีแก้ปัญหาตามธรรมชาติในการควบคุมการสร้างบริการ เนื่องจากจริงๆ แล้วเป็น "โรงงานบนสเตียรอยด์"

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

    เราจะไม่รั้งรอมัน เราจะไปยังรูปแบบการออกแบบล่าสุดสำหรับวันนี้แทน นั่นคือ poltergeist

4. โพลเตอร์ไกสต์

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

public class UserManager {
   private UserService service;
   public UserManager(UserService userService) {
       service = userService;
   }
   User createUser(User user) {
       return service.create(user);
   }
   Long findAllUsers(){
       return service.findAll().size();
   }
   String findEmailById(Long id) {
       return service.findById(id).getEmail();}
   User findUserByEmail(String email) {
       return service.findByEmail(email);
   }
   User deleteUserById(Long id) {
       return service.delete(id);
   }
}
ทำไมเราต้องการวัตถุที่เป็นเพียงตัวกลางและมอบหมายงานให้คนอื่น เรากำจัดมันและถ่ายโอนฟังก์ชันการทำงานเล็กๆ น้อยๆ ที่มีให้กับวัตถุที่มีอายุยืนยาว ต่อไป เราไปยังรูปแบบที่เราสนใจมากที่สุด (ในฐานะนักพัฒนาทั่วไป) เช่นรูปแบบต่อต้านการพัฒนา .

5. ฮาร์ดโค้ด

ดังนั้นเราจึงมาถึงคำที่น่ากลัวนี้: การเข้ารหัสอย่างหนัก สาระสำคัญของรูปแบบการป้องกันนี้คือรหัสนั้นเชื่อมโยงอย่างมากกับการกำหนดค่าฮาร์ดแวร์และ/หรือสภาพแวดล้อมของระบบ สิ่งนี้ทำให้การย้ายรหัสไปยังการกำหนดค่าอื่นซับซ้อนอย่างมาก รูปแบบต่อต้านนี้มีความเกี่ยวข้องอย่างใกล้ชิดกับตัวเลขมหัศจรรย์ (รูปแบบต่อต้านเหล่านี้มักจะเกี่ยวพันกัน) ตัวอย่าง:

public Connection buildConnection() throws Exception {
   Class.forName("com.mysql.cj.jdbc.Driver");
   connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8&characterSetResults=UTF-8&serverTimezone=UTC", "user01", "12345qwert");
   return connection;
}
เจ็บใช่ไหม ที่นี่เราฮาร์ดโค้ดการตั้งค่าการเชื่อมต่อของเรา ดังนั้นรหัสจะทำงานได้อย่างถูกต้องกับ MySQL เท่านั้น ในการเปลี่ยนฐานข้อมูล เราจะต้องดำดิ่งลงไปในโค้ดและเปลี่ยนแปลงทุกอย่างด้วยตนเอง ทางออกที่ดีคือการกำหนดค่าในไฟล์แยกต่างหาก:

spring:
  datasource:
    jdbc-url:jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username:  user01
    password:  12345qwert
อีกทางเลือกหนึ่งคือการใช้ค่าคงที่

6. สมอเรือ

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

public User update(Long id, User request) {
   User user = mergeUser(findById(id), request);
   return userDAO.update(user);
}
private User mergeUser(User findUser, User requestUser) {
   return new User(
           findUser.getId(),
           requestUser.getEmail() != null ? requestUser.getEmail() : findUser.getEmail(),
           requestUser.getFirstName() != null ? requestUser.getFirstName() : findUser.getFirstNameRu(),
           requestUser.getMiddleName() != null ? requestUser.getMiddleName() : findUser.getMiddleNameRu(),
           requestUser.getLastName() != null ? requestUser.getLastName() : findUser.getLastNameEn(),
           requestUser.getPhone() != null ? requestUser.getPhone() : findUser.getPhone());
}
เรามีเมธอดการอัพเดทที่ใช้วิธีการแยกเพื่อรวมข้อมูลผู้ใช้จากฐานข้อมูลด้วยข้อมูลผู้ใช้ที่ส่งผ่านไปยังเมธอด (หากผู้ใช้ที่ส่งผ่านไปยังเมธอดการอัพเดทมีฟิลด์ว่าง ค่าฟิลด์เก่าจะถูกนำมาจากฐานข้อมูล) . จากนั้น สมมติว่ามีข้อกำหนดใหม่ที่บันทึกจะต้องไม่ถูกรวมเข้ากับข้อมูลเก่า แต่แม้ว่าจะมีฟิลด์ว่างอยู่ ฟิลด์เหล่านี้จะถูกใช้เพื่อเขียนทับข้อมูลเก่า:

public User update(Long id, User request) {
   return userDAO.update(user);
}
ซึ่งหมายความว่าคุณจะไม่ใช้การผสานผู้ใช้อีกต่อไป แต่น่าเสียดายที่จะลบออก — จะเกิดอะไรขึ้นหากวิธีนี้ (หรือแนวคิดของวิธีนี้) อาจมีประโยชน์ในสักวันหนึ่ง โค้ดดังกล่าวมีแต่จะทำให้ระบบซับซ้อนและทำให้เกิดความสับสนโดยที่ไม่มีประโยชน์ในทางปฏิบัติ เราต้องไม่ลืมว่ารหัสดังกล่าวที่มี "ชิ้นส่วนที่ตายแล้ว" จะส่งต่อให้เพื่อนร่วมงานได้ยากเมื่อคุณออกจากโครงการอื่น วิธีที่ดีที่สุดในการจัดการกับสมอเรือคือการปรับโครงสร้างโค้ดใหม่ เช่น ลบส่วนต่างๆ ของโค้ด (ฉันรู้ ปวดใจ) นอกจากนี้ เมื่อเตรียมกำหนดการพัฒนา จำเป็นต้องคำนึงถึงจุดยึดดังกล่าวด้วย (เพื่อจัดสรรเวลาสำหรับการจัดระเบียบ)

7. ส้วมซึมของวัตถุ

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

class ReusablePool {
   private static ReusablePool pool;
   private List<Resource> list = new LinkedList<>();
   private ReusablePool() {
       for (int i = 0; i < 3; i++)
           list.add(new Resource());
   }
   public static ReusablePool getInstance() {
       if (pool == null) {
           pool = new ReusablePool();
       }
       return pool;
   }
   public Resource acquireResource() {
       if (list.size() == 0) {
           return new Resource();
       } else {
           Resource r = list.get(0);
           list.remove(r);
           return r;
       }
   }
   public void releaseResource(Resource r) {
       list.add(r);
   }
}
คลาสนี้ถูกนำเสนอในรูปแบบของsingleton pattern/anti-pattern ข้างต้น นั่นคือสามารถมีวัตถุประเภทนี้ได้เพียงอันเดียว มันใช้Resourceวัตถุ บางอย่าง ตามค่าเริ่มต้น ตัวสร้างจะเติมพูลด้วย 4 อินสแตนซ์ เมื่อคุณได้รับอ็อบเจกต์ มันจะถูกลบออกจากพูล (หากไม่มีออบเจกต์ที่มีอยู่ ออบเจกต์หนึ่งจะถูกสร้างขึ้นและส่งคืนทันที) และในตอนท้ายเรามีวิธีการใส่วัตถุกลับ วัตถุทรัพยากรมีลักษณะดังนี้:

public class Resource {
   private Map<String, String> patterns;
   public Resource() {
       patterns = new HashMap<>();
       patterns.put("proxy", "https://en.wikipedia.org/wiki/Proxy_pattern");
       patterns.put("bridge", "https://en.wikipedia.org/wiki/Bridge_pattern");
       patterns.put("facade", "https://en.wikipedia.org/wiki/Facade_pattern");
       patterns.put("builder", "https://en.wikipedia.org/wiki/Builder_pattern");
   }
   public Map<String, String> getPatterns() {
       return patterns;
   }
   public void setPatterns(Map<String, String> patterns) {
       this.patterns = patterns;
   }
}
ที่นี่เรามีวัตถุขนาดเล็กที่มีแผนที่ที่มีชื่อรูปแบบการออกแบบเป็นคีย์และลิงก์ Wikipedia ที่สอดคล้องกันเป็นค่า ตลอดจนวิธีการเข้าถึงแผนที่ ลองดูที่หลัก:

class SomeMain {
   public static void main(String[] args) {
       ReusablePool pool = ReusablePool.getInstance();

       Resource firstResource = pool.acquireResource();
       Map<String, String> firstPatterns = firstResource.getPatterns();
       // use our map somehow...
       pool.releaseResource(firstResource);

       Resource secondResource = pool.acquireResource();
       Map<String, String> secondPatterns = firstResource.getPatterns();
       // use our map somehow...
       pool.releaseResource(secondResource);

       Resource thirdResource = pool.acquireResource();
       Map<String, String> thirdPatterns = firstResource.getPatterns();
       // use our map somehow...
       pool.releaseResource(thirdResource);
   }
}
ทุกอย่างที่นี่ชัดเจนเพียงพอ: เราได้รับวัตถุพูล รับวัตถุที่มีทรัพยากรจากพูล รับแผนที่จากวัตถุทรัพยากร ทำบางอย่างกับมัน และนำทั้งหมดนี้มาแทนที่ในพูลเพื่อใช้ซ้ำต่อไป Voila นี่คือรูปแบบการออกแบบกลุ่มวัตถุ แต่เรากำลังพูดถึงรูปแบบต่อต้านใช่ไหม? ลองพิจารณากรณีต่อไปนี้ในวิธีการหลัก:

Resource fourthResource = pool.acquireResource();
   Map<String, String> fourthPatterns = firstResource.getPatterns();
// use our map somehow...
fourthPatterns.clear();
firstPatterns.put("first","blablabla");
firstPatterns.put("second","blablabla");
firstPatterns.put("third","blablabla");
firstPatterns.put("fourth","blablabla");
pool.releaseResource(fourthResource);
อีกครั้ง เราได้รับวัตถุทรัพยากร เราได้รับแผนผังของรูปแบบ และเราทำบางอย่างกับแผนที่ แต่ก่อนที่จะบันทึกแผนที่กลับไปที่กลุ่มวัตถุ แผนที่จะถูกล้างและเติมข้อมูลเสียหาย ทำให้วัตถุทรัพยากรไม่เหมาะสมสำหรับการใช้ซ้ำ หนึ่งในรายละเอียดหลักของกลุ่มออบเจกต์คือเมื่อออบเจ็กต์ถูกส่งคืน จะต้องคืนค่าเป็นสถานะที่เหมาะสมสำหรับการใช้ซ้ำต่อไป หากอ็อบเจกต์ที่ส่งคืนไปยังพูลยังคงอยู่ในสถานะที่ไม่ถูกต้องหรือไม่ได้กำหนด การออกแบบของเราจะเรียกว่าอ็อบเจกต์เซสพูล การจัดเก็บวัตถุที่ไม่เหมาะสำหรับการนำมาใช้ซ้ำนั้นสมเหตุสมผลหรือไม่? ในสถานการณ์นี้ เราสามารถทำให้แผนที่ภายในไม่เปลี่ยนรูปแบบได้ในตัวสร้าง:

public Resource() {
   patterns = new HashMap<>();
   patterns.put("proxy", "https://en.wikipedia.org/wiki/Proxy_pattern");
   patterns.put("bridge", "https://en.wikipedia.org/wiki/Bridge_pattern");
   patterns.put("facade", "https://en.wikipedia.org/wiki/Facade_pattern");
   patterns.put("builder", "https://en.wikipedia.org/wiki/Builder_pattern");
   patterns = Collections.unmodifiableMap(patterns);
}
ความพยายามและความปรารถนาที่จะเปลี่ยนแปลงเนื้อหาของแผนที่จะจางหายไป ต้องขอบคุณ UnsupportedOperationException ที่พวกเขาจะสร้างขึ้น Anti-patternsเป็นกับดักที่นักพัฒนาพบบ่อยครั้งเนื่องจากการไม่มีเวลาอย่างเฉียบพลัน ความประมาท ขาดประสบการณ์ หรือแรงกดดันจากผู้จัดการโครงการ การเร่งรีบซึ่งเป็นเรื่องปกติอาจนำไปสู่ปัญหาใหญ่สำหรับแอปพลิเคชันในอนาคต ดังนั้นคุณจำเป็นต้องรู้เกี่ยวกับข้อผิดพลาดเหล่านี้และหลีกเลี่ยงล่วงหน้า นี่เป็นการสรุปส่วนแรกของบทความ ยังมีต่อ...
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION