JavaRush /จาวาบล็อก /Random-TH /AntiPattern คืออะไร? มาดูตัวอย่างกัน (ตอนที่ 1)
Константин
ระดับ

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

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

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

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

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

Divine Objectเป็นรูปแบบต่อต้านที่อธิบายถึงความเข้มข้นที่มากเกินไปของฟังก์ชันที่แตกต่างกันมากเกินไป โดยจัดเก็บข้อมูลที่หลากหลายจำนวนมาก (วัตถุที่แอปพลิเคชันหมุนวนอยู่) ลองยกตัวอย่างเล็กๆ น้อยๆ:
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.ซิงเกิลตัน

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

  2. Singleton ละเมิดหลักการ SOLID ข้อใดข้อหนึ่ง - Single Responsibility Principle - คลาส Singleton นอกเหนือจากการปฏิบัติหน้าที่ในทันทีแล้ว ยังควบคุมจำนวนอินสแตนซ์ด้วย

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

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

    กล่าวอีกนัยหนึ่ง ซิงเกิลตันจะเพิ่มการเชื่อมต่อ และทั้งหมดที่กล่าวมาข้างต้นก็ไม่มีอะไรมากไปกว่าผลที่ตามมาของการเชื่อมต่อที่เพิ่มขึ้น

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

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

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

    และเราจะไม่จมอยู่กับมันและไปยังรูปแบบการออกแบบสุดท้ายสำหรับวันนี้ - โพลเตอร์ไกสต์

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

Poltergeistเป็นคลาสต่อต้านรูปแบบที่ไม่มีประโยชน์ซึ่งใช้ในการเรียกเมธอดของคลาสอื่นหรือเพียงแค่เพิ่มเลเยอร์นามธรรมที่ไม่จำเป็น สารต้านรูปแบบนี้แสดงออกมาในรูปของวัตถุอายุสั้นที่ไม่มีสถานะ วัตถุเหล่านี้มักจะใช้เพื่อเตรียมใช้งานวัตถุอื่นที่มีความคงทนมากกว่า
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. สมอเรือ

สมอเรือในบริบทของการต่อต้านรูปแบบหมายถึงการจัดเก็บส่วนที่ไม่ได้ใช้ของระบบซึ่งเหลืออยู่หลังจากการเพิ่มประสิทธิภาพหรือการปรับโครงสร้างใหม่ นอกจากนี้ โค้ดบางส่วนอาจเหลือไว้ "สำหรับอนาคต" ในกรณีที่คุณต้องใช้อีกครั้ง สิ่งนี้จะเปลี่ยนรหัสให้กลายเป็นถังขยะ AntiPattern คืออะไร?  ลองดูตัวอย่าง (ตอนที่ 1) - 4ตัวอย่าง:
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);
}
เป็นผลให้mergeUserไม่มีการใช้อีกต่อไป และน่าเสียดายที่จะลบมันออกไป: จะเกิดอะไรขึ้นถ้ามัน (หรือแนวคิดของมัน) ยังมีประโยชน์อยู่? รหัสดังกล่าวทำให้ระบบซับซ้อนและสร้างความสับสนเท่านั้น โดยพื้นฐานแล้วไม่ได้ให้คุณค่าเชิงปฏิบัติใดๆ เลย เราต้องไม่ลืมว่าโค้ดที่มี "ชิ้นส่วนที่ตายแล้ว" ดังกล่าวจะยากต่อการถ่ายโอนไปยังเพื่อนร่วมงานเมื่อคุณออกจากโปรเจ็กต์อื่น วิธีที่ดีที่สุดในการจัดการกับจุดยึดเรือคือการปรับโครงสร้างโค้ดใหม่ กล่าวคือ การลบส่วนต่างๆ ของโค้ดเหล่านี้ (อนิจจา) นอกจากนี้เมื่อวางแผนการพัฒนาคุณต้องคำนึงถึงการเกิดขึ้นของพุกดังกล่าว (ให้เวลาในการทำความสะอาดหางแร่)

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/antipattern ที่อธิบายไว้ข้างต้น นั่นคือสามารถมีได้เพียงอ็อบเจ็กต์ประเภทนี้เท่านั้น มันทำงานกับอ็อบเจ็กต์บางตัวResourceโดยค่าเริ่มต้นในตัวสร้างพูลจะเต็มไปด้วย 4 ชุด เมื่อนำวัตถุดังกล่าวออกไปก็จะถูกลบออกจากสระ (หากไม่มีก็สร้างและแจกทันที) และสุดท้ายก็มีวิธีการนำวัตถุกลับคืนมา วัตถุResourceมีลักษณะดังนี้:
public class Resource {
   private Map<String, String> patterns;
   public Resource() {
       patterns = new HashMap<>();
       patterns.put("заместитель", "https://studfile.net/preview/3676297/page:3/");
       patterns.put("мост", "https://studfile.net/preview/3676297/page:4/");
       patterns.put("фасад", "https://studfile.net/preview/3676297/page:5/");
       patterns.put("строитель", "https://studfile.net/preview/3676297/page:6/#16");
   }
   public Map<String, String> getPatterns() {
       return patterns;
   }
   public void setPatterns(Map<String, String> patterns) {
       this.patterns = patterns;
   }
}
ที่นี่เรามีวัตถุขนาดเล็กที่ประกอบด้วยแผนที่ซึ่งมีชื่อของรูปแบบเป็นกุญแจและลิงก์ไปยังรูปแบบเหล่านั้นเป็นค่า ตลอดจนวิธีการเข้าถึงแผนที่ ลองดูmain:
class SomeMain {
   public static void main(String[] args) {
       ReusablePool pool = ReusablePool.getInstance();

       Resource firstResource = pool.acquireResource();
       Map<String, String> firstPatterns = firstResource.getPatterns();
       // ......Howим-то образом используем нашу мапу.....
       pool.releaseResource(firstResource);

       Resource secondResource = pool.acquireResource();
       Map<String, String> secondPatterns = firstResource.getPatterns();
       // ......Howим-то образом используем нашу мапу.....
       pool.releaseResource(secondResource);

       Resource thirdResource = pool.acquireResource();
       Map<String, String> thirdPatterns = firstResource.getPatterns();
       // ......Howим-то образом используем нашу мапу.....
       pool.releaseResource(thirdResource);
   }
}
ทุกอย่างที่นี่ก็ชัดเจนเช่นกัน: เรานำวัตถุพูล ดึงวัตถุที่มีทรัพยากรออกมา นำแผนที่จากนั้น ทำอะไรบางอย่างกับมัน และนำมันทั้งหมดกลับเข้าไปในพูลเพื่อนำกลับมาใช้ใหม่อีกครั้ง Voila: ที่นี่คุณมีรูปแบบพูลของวัตถุ แต่เรากำลังพูดถึงการต่อต้านรูปแบบใช่ไหม? ลองดูกรณีนี้main:
Resource fourthResource = pool.acquireResource();
   Map<String, String> fourthPatterns = firstResource.getPatterns();
// ......Howим-то образом используем нашу мапу.....
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("заместитель", "https://studfile.net/preview/3676297/page:3/");
   patterns.put("мост", "https://studfile.net/preview/3676297/page:4/");
   patterns.put("фасад", "https://studfile.net/preview/3676297/page:5/");
   patterns.put("строитель", "https://studfile.net/preview/3676297/page:6/#16");
   patterns = Collections.unmodifiableMap(patterns);
}
(ความพยายามและความปรารถนาที่จะเปลี่ยนแปลงเนื้อหาจะตกไปพร้อมกับ UnsupportedOperationException) AntiPatternsเป็นกับดักที่นักพัฒนามักตกหลุมพรางเนื่องจากการไม่มีเวลาอย่างเฉียบพลัน การไม่ตั้งใจ ไม่มีประสบการณ์ หรือการถูกเตะจากผู้จัดการ การไม่มีเวลาและความเร่งรีบตามปกติอาจส่งผลให้เกิดปัญหาใหญ่ในการใช้งานในอนาคต ดังนั้นจึงต้องทราบและหลีกเลี่ยงข้อผิดพลาดเหล่านี้ล่วงหน้า AntiPattern คืออะไร?  ลองดูตัวอย่าง (ตอนที่ 1) - 6ด้วยเหตุนี้ส่วนแรกของบทความจึงสิ้นสุดลง: ดำเนินการต่อ
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION