JavaRush /Blog Java /Random-VI /Antipattern là gì? Hãy xem các ví dụ (phần 1)

Antipattern là gì? Hãy xem các ví dụ (phần 1)

Xuất bản trong nhóm
Antipattern là gì?  Cùng xem ví dụ (phần 1) - 1Chúc mọi người một ngày tốt lành! Hôm nọ, tôi đã được phỏng vấn và được hỏi một câu hỏi về phản mẫu: đây là loại quái vật gì, loại và ví dụ của chúng trong thực tế là gì. Tất nhiên là tôi trả lời câu hỏi nhưng rất hời hợt, vì tôi chưa đi sâu nghiên cứu vấn đề này. Sau cuộc phỏng vấn, tôi bắt đầu lùng sục trên Internet, ngày càng say mê chủ đề này. Hôm nay tôi muốn đánh giá ngắn gọn về các antipattern phổ biến nhất và các ví dụ của chúng, bài đọc này có thể cung cấp cho bạn những kiến ​​thức cần thiết về vấn đề này. Bắt đầu nào! Vì vậy, trước khi thảo luận về phản mẫu là gì, chúng ta hãy nhớ mẫu là gì. Mẫu là một thiết kế kiến ​​trúc có thể lặp lại để giải quyết các vấn đề hoặc tình huống phổ biến phát sinh khi thiết kế một ứng dụng. Nhưng hôm nay chúng ta không nói về chúng mà nói về những mặt đối lập của chúng - những phản mẫu. Chống mẫu là một cách tiếp cận phổ biến để giải quyết một nhóm các vấn đề thường gặp nhưng không hiệu quả, rủi ro hoặc không hiệu quả. Nói cách khác, đó là một mẫu lỗi (đôi khi còn được gọi là bẫy). Antipattern là gì?  Cùng xem ví dụ (phần 1) - 2Theo quy định, antipotype được chia thành các loại sau:
  1. Phản mẫu kiến ​​trúc - các phản mẫu kiến ​​trúc phát sinh khi thiết kế cấu trúc của một hệ thống (thường do kiến ​​trúc sư thực hiện).
  2. Mô hình chống quản lý - mô hình phản đối trong lĩnh vực quản lý, thường được các nhà quản lý (hoặc nhóm quản lý) khác nhau gặp phải.
  3. Development Anti Pattern - antipotype là những vấn đề phát triển phát sinh khi các lập trình viên bình thường viết một hệ thống.
Chủ nghĩa kỳ lạ của các phản mẫu rộng hơn nhiều, nhưng ngày nay chúng ta sẽ không xem xét chúng, vì đối với các nhà phát triển bình thường, điều này sẽ quá sức. Để bắt đầu, hãy lấy một ví dụ về phản mẫu trong lĩnh vực quản lý.

1. Tê liệt khả năng phân tích

Phân tích tê liệt được coi là một mô hình phản tổ chức cổ điển. Nó liên quan đến việc phân tích tổng thể một tình huống khi lập kế hoạch để không đưa ra quyết định hoặc hành động nào, về cơ bản làm tê liệt sự phát triển. Điều này thường xảy ra khi mục tiêu là đạt được sự hoàn hảo và hoàn thành đầy đủ giai đoạn phân tích. Chống mẫu này được đặc trưng bằng cách đi theo vòng tròn (một loại vòng khép kín), sửa đổi và tạo các mô hình chi tiết, từ đó cản trở quy trình làm việc. Ví dụ: bạn đang cố gắng dự đoán những điều như: điều gì sẽ xảy ra nếu người dùng đột nhiên muốn tạo danh sách nhân viên dựa trên chữ cái thứ tư và thứ năm trong tên của họ, bao gồm các dự án mà họ dành nhiều thời gian làm việc nhất từ ​​Tết đến năm mới. ngày 8 tháng 3 trong bốn năm trước? Về bản chất, đây là sự phân tích quá mức. Một ví dụ thực tế điển hình là tình trạng tê liệt trong phân tích đã khiến Kodak phá sản như thế nào . Dưới đây là một số mẹo nhỏ để chống lại tình trạng tê liệt phân tích:
  1. Bạn cần xác định mục tiêu dài hạn như một ngọn hải đăng cho việc ra quyết định, để mỗi quyết định đưa ra sẽ đưa bạn đến gần hơn với mục tiêu và không buộc bạn phải ấn định thời gian.
  2. Đừng tập trung vào những điều nhỏ nhặt (tại sao lại đưa ra quyết định theo một sắc thái nhỏ như thể đó là quyết định cuối cùng trong cuộc đời bạn?)
  3. Đặt ra thời hạn để đưa ra quyết định.
  4. Đừng cố gắng thực hiện một nhiệm vụ một cách hoàn hảo: tốt hơn hết là bạn nên làm nó thật tốt.
Bây giờ chúng ta sẽ không đi quá sâu và xem xét các mô hình quản lý khác. Do đó, không cần mở đầu, chúng ta hãy chuyển sang một số phản mẫu kiến ​​​​trúc, bởi vì rất có thể, bài viết này được đọc bởi các nhà phát triển tương lai chứ không phải các nhà quản lý.

2. Đối tượng thần

Đối tượng thiêng liêng là một mô hình chống mô tả sự tập trung quá mức của quá nhiều chức năng khác nhau, lưu trữ một lượng lớn dữ liệu đa dạng (đối tượng mà ứng dụng xoay quanh). Hãy lấy một ví dụ nhỏ:
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();
   }
}
Ở đây chúng ta thấy một số loại lớp học lớn có thể thực hiện mọi việc cùng một lúc. Chứa các truy vấn tới Cơ sở dữ liệu, chứa một số dữ liệu, chúng ta cũng thấy một phương thức Facade findAllWithoutPageEncó logic nghiệp vụ. Một vật thể thiêng liêng như vậy trở nên to lớn và cồng kềnh để có thể hỗ trợ đầy đủ. Chúng ta phải mày mò nó trong từng đoạn mã: nhiều nút trong hệ thống dựa vào nó và được liên kết chặt chẽ với nó. Việc duy trì mã như vậy ngày càng trở nên khó khăn hơn. Trong những trường hợp như vậy, nó cần được chia thành các lớp riêng biệt, mỗi lớp sẽ chỉ có một mục đích (mục tiêu). Trong ví dụ này, bạn có thể chia nó thành một lớp 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());
   }

                               ........
}
Một lớp chứa dữ liệu và các phương thức để truy cập nó:
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;
   }
                    ....
Và sẽ phù hợp hơn nếu chuyển phương thức có logic nghiệp vụ vào dịch vụ:
private List<User> findAllWithoutPageEn(Long permissionId, Type type) {
   switch (type) {
       case USERS:
           return findAllEnUsers(permissionId);
       case CUSTOMERS:
           return findAllEnCustomers(permissionId);
       default:
           return findAllEn();
   }
}

3.Singleton

Singleton là mẫu đơn giản nhất đảm bảo rằng sẽ có một phiên bản duy nhất của một lớp nào đó trong một ứng dụng đơn luồng và cung cấp một điểm truy cập toàn cầu tới đối tượng đó. Bạn có thể đọc thêm về nó ở đây . Nhưng đây là một mẫu hình hay một mẫu phản mẫu? Antipattern là gì?  Cùng xem ví dụ (phần 1) - 3Hãy xem xét những nhược điểm của mẫu này:
  1. Nhà nước toàn cầu. Khi chúng ta truy cập vào một thể hiện của một lớp, chúng ta không biết trạng thái hiện tại của lớp đó hoặc ai đã thay đổi nó hoặc khi nào, và trạng thái đó có thể không như những gì chúng ta mong đợi. Nói cách khác, tính đúng đắn khi làm việc với một singleton phụ thuộc vào thứ tự các lệnh gọi đến nó, điều này khiến các hệ thống con phụ thuộc vào nhau và do đó, làm tăng nghiêm trọng độ phức tạp của quá trình phát triển.

  2. Singleton vi phạm một trong các nguyên tắc SOLID - Nguyên tắc trách nhiệm duy nhất - lớp Singleton ngoài việc thực hiện các trách nhiệm trước mắt còn kiểm soát số lượng instance của nó.

  3. Sự phụ thuộc của một lớp thông thường vào một singleton không hiển thị trong giao diện lớp. Vì thông thường một cá thể singleton không được truyền trong các tham số của phương thức mà được lấy trực tiếp, thông qua getInstance(), để xác định sự phụ thuộc của lớp vào singleton, nên bạn cần đi sâu vào cách triển khai từng phương thức - chỉ xem hợp đồng công khai của đối tượng là không đủ .

    Sự hiện diện của một singleton làm giảm khả năng kiểm thử của ứng dụng nói chung và các lớp sử dụng singleton nói riêng. Thứ nhất, bạn không thể đặt một đối tượng Mock thay cho một singleton và thứ hai, nếu một singleton có giao diện để thay đổi trạng thái của nó thì các bài kiểm tra sẽ phụ thuộc vào nhau.

    Nói cách khác, một singleton làm tăng khả năng kết nối và tất cả những điều trên không gì khác hơn là hệ quả của việc tăng cường kết nối.

    Và nếu bạn nghĩ về điều đó, bạn có thể tránh được việc sử dụng singleton. Ví dụ, để kiểm soát số lượng phiên bản của một đối tượng, hoàn toàn có thể (và cần thiết) sử dụng nhiều loại nhà máy khác nhau.

    Mối nguy hiểm lớn nhất nằm ở nỗ lực xây dựng toàn bộ kiến ​​trúc ứng dụng dựa trên các singleton. Có rất nhiều lựa chọn thay thế tuyệt vời cho phương pháp này. Ví dụ quan trọng nhất là Spring, cụ thể là các thùng chứa IoC của nó: ở đó vấn đề kiểm soát việc tạo ra các dịch vụ được giải quyết một cách tự nhiên, vì trên thực tế, chúng là “các nhà máy sản xuất steroid”.

    Bây giờ có rất nhiều holivar về chủ đề này, do đó, tùy bạn quyết định xem một singleton là mẫu hay phản mẫu.

    Và chúng ta sẽ không dừng lại ở đó mà chuyển sang mẫu thiết kế cuối cùng của ngày hôm nay - Poltergeist.

4. Yêu tinh

Poltergeist là một phản mẫu lớp không hữu ích, được sử dụng để gọi các phương thức của lớp khác hoặc đơn giản là thêm một lớp trừu tượng không cần thiết. Antipotype biểu hiện dưới dạng các đối tượng tồn tại trong thời gian ngắn, không có trạng thái. Những đối tượng này thường được sử dụng để khởi tạo các đối tượng khác bền hơn.
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);
   }
}
Tại sao chúng ta cần một đối tượng chỉ là vật trung gian và ủy thác công việc của nó cho người khác? Chúng tôi xóa nó và chuyển chức năng nhỏ mà nó triển khai thành các đối tượng tồn tại lâu dài. Tiếp theo, chúng ta chuyển sang các mẫu mà chúng ta (với tư cách là những nhà phát triển bình thường) quan tâm nhất - các mẫu phản mẫu phát triển .

5. Mã cứng

Vậy là chúng ta đã hiểu được từ khủng khiếp này - mã cứng. Bản chất của phản mẫu này là mã được liên kết chặt chẽ với một cấu hình phần cứng và/hoặc môi trường hệ thống cụ thể, điều này khiến cho việc chuyển nó sang các cấu hình khác rất khó khăn. Phản mẫu này có liên quan chặt chẽ với các con số ma thuật (chúng thường đan xen với nhau). Ví dụ:
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;
}
Bị đóng đinh, phải không? Ở đây chúng tôi trực tiếp thiết lập cấu hình kết nối của mình; do đó, mã sẽ chỉ hoạt động bình thường với MySQL và để thay đổi cơ sở dữ liệu, bạn sẽ cần phải nhập mã và thay đổi mọi thứ theo cách thủ công. Một giải pháp tốt là đặt các cấu hình vào một tệp riêng:
spring:
  datasource:
    jdbc-url:jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username:  user01
    password:  12345qwert
Một lựa chọn khác là di chuyển nó sang hằng số.

6. Neo thuyền

Một chiếc neo thuyền trong bối cảnh phản mẫu có nghĩa là lưu trữ những phần không sử dụng của hệ thống còn sót lại sau một số tối ưu hóa hoặc tái cấu trúc. Ngoài ra, một số phần của mã có thể được để lại "cho tương lai", trong trường hợp bạn phải sử dụng lại chúng. Về cơ bản, điều này biến mã thành thùng rác. Antipattern là gì?  Cùng xem ví dụ (phần 1) - 4Ví dụ:
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());
}
Chúng tôi có một phương thức cập nhật sử dụng một phương thức riêng để hợp nhất dữ liệu của người dùng từ cơ sở dữ liệu và người đến cập nhật (nếu người đến cập nhật có trường trống thì trường đó được viết là trường cũ từ cơ sở dữ liệu). Và ví dụ, có một yêu cầu là các bản ghi không được hợp nhất với các bản ghi cũ mà phải ghi đè, ngay cả khi có các trường trống:
public User update(Long id, User request) {
   return userDAO.update(user);
}
Kết quả là mergeUsernó không còn được sử dụng nữa và thật tiếc khi xóa nó: nếu nó (hoặc ý tưởng của nó) vẫn còn hữu ích thì sao? Mã như vậy chỉ làm phức tạp và gây nhầm lẫn cho các hệ thống, về cơ bản không cung cấp bất kỳ giá trị thực tế nào. Chúng ta không được quên rằng những đoạn mã có “mảnh chết” như vậy sẽ khó chuyển giao cho đồng nghiệp khi bạn chuyển sang dự án khác. Phương pháp tốt nhất để giải quyết vấn đề neo thuyền là tái cấu trúc mã, cụ thể là xóa các phần mã này (than ôi, than ôi). Ngoài ra, khi lập kế hoạch phát triển, bạn cần tính đến sự xuất hiện của các mỏ neo đó (dành thời gian để dọn dẹp chất thải).

7.Bể chứa đối tượng

Để mô tả phản mẫu này, trước tiên bạn cần làm quen với mẫu nhóm đối tượng . Nhóm đối tượng (nhóm tài nguyên) là một mẫu thiết kế tổng quát , một tập hợp các đối tượng được khởi tạo và sẵn sàng để sử dụng. Khi một ứng dụng yêu cầu một đối tượng, nó không được tạo lại mà được lấy từ nhóm này. Khi một đối tượng không còn cần thiết nữa, nó không bị phá hủy mà được trả lại vào nhóm. Thường được sử dụng cho các đối tượng nặng cần nhiều tài nguyên để tạo mọi lúc, chẳng hạn như kết nối cơ sở dữ liệu. Hãy xem xét một ví dụ nhỏ và đơn giản để làm ví dụ. Vì vậy, chúng tôi có một lớp đại diện cho mẫu này:
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);
   }
}
Chúng tôi trình bày lớp này dưới dạng mẫu/phản mẫu đơn được mô tả ở trên , nghĩa là chỉ có thể có một đối tượng thuộc loại này, nó hoạt động trên một số đối tượng nhất định Resource, theo mặc định trong hàm tạo, nhóm chứa đầy 4 bản sao; khi một đối tượng như vậy được lấy đi, nó sẽ bị xóa khỏi nhóm (nếu nó không có ở đó, nó sẽ được tạo và cho đi ngay lập tức), và cuối cùng sẽ có phương pháp để đưa đối tượng trở lại. Các đối tượng Resourcetrông như thế này:
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;
   }
}
Ở đây chúng ta có một đối tượng nhỏ chứa bản đồ với tên của các mẫu làm khóa và liên kết đến chúng làm giá trị cũng như các phương thức truy cập bản đồ. Hãy xem 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);
   }
}
Mọi thứ ở đây cũng rõ ràng: chúng tôi lấy một đối tượng nhóm, lấy ra một đối tượng có tài nguyên từ nó, lấy bản đồ từ nó, làm điều gì đó với nó và đặt tất cả trở lại nhóm để tái sử dụng thêm. Thì đấy: ở đây bạn có mẫu nhóm đối tượng. Nhưng chúng ta đang nói về phản mẫu phải không? Chúng ta hãy xem trường hợp này 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);
Ở đây một lần nữa, một đối tượng tài nguyên được lấy, bản đồ của nó với các mẫu được lấy và một số thứ được thực hiện với nó, nhưng trước khi lưu trở lại nhóm đối tượng, bản đồ được làm sạch và chứa đầy dữ liệu khó hiểu khiến đối tượng Tài nguyên này không phù hợp để sử dụng lại. Một trong những sắc thái chính của nhóm đối tượng là sau khi một đối tượng được trả về, nó phải được trả về trạng thái phù hợp để tái sử dụng thêm. Nếu các đối tượng ở trạng thái không chính xác hoặc không xác định sau khi được trả về nhóm, thì cấu trúc này được gọi là một đối tượng cesspool. Mục đích của việc lưu trữ các đối tượng không thể tái sử dụng là gì? Trong tình huống này, bạn có thể làm cho bản đồ nội bộ trở nên bất biến trong hàm tạo:
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);
}
(những nỗ lực và mong muốn thay đổi nội dung sẽ đi kèm với UnsupportedOperationException). Antipatterns là những cái bẫy mà các nhà phát triển thường rơi vào do thiếu thời gian, thiếu chú ý, thiếu kinh nghiệm hoặc bị quản lý đá. Việc thiếu thời gian và vội vàng thông thường có thể dẫn đến những vấn đề lớn cho ứng dụng trong tương lai, vì vậy những sai sót này cần được biết trước và tránh. Antipattern là gì?  Cùng xem ví dụ (phần 1) - 6Đến đây, phần đầu tiên của bài viết đã kết thúc: còn tiếp tục .
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION