JavaRush /مدونة جافا /Random-AR /ما هي الأنماط المضادة؟ دعونا نلقي نظرة على الأمثلة (الجزء...

ما هي الأنماط المضادة؟ دعونا نلقي نظرة على الأمثلة (الجزء الأول)

نشرت في المجموعة
ما هي الأنماط المضادة؟  دعونا نلقي نظرة على الأمثلة (الجزء 1) - 1يوم جيد للجميع! في اليوم الآخر، تمت مقابلتي، وتم طرح سؤال حول الأنماط المضادة: أي نوع من الوحش هو، ما هي أنواعها وأمثلةها في الممارسة العملية. طبعا أجبت على السؤال ولكن بشكل سطحي جدا، حيث أنني لم أتعمق كثيرا في دراسة هذا الموضوع. بعد المقابلة، بدأت في البحث على الإنترنت، وأصبحت منغمسًا أكثر فأكثر في هذا الموضوع. أود اليوم أن أقوم بمراجعة قصيرة للأنماط المضادة الأكثر شيوعًا وأمثلة عليها، والتي قد تمنحك قراءتها المعرفة اللازمة حول هذه المشكلة. هيا بنا نبدأ! لذا، قبل مناقشة ما هو النمط المضاد، دعونا نتذكر ما هو النمط. النمط هو تصميم معماري قابل للتكرار لحل المشكلات أو المواقف الشائعة التي تنشأ عند تصميم التطبيق. لكننا اليوم لا نتحدث عنهم، بل عن أضدادهم - الأنماط المضادة. النمط المضاد هو أسلوب شائع لحل فئة من المشكلات الشائعة التي تكون غير فعالة أو محفوفة بالمخاطر أو غير منتجة. بمعنى آخر، إنه نمط خطأ (يُسمى أيضًا أحيانًا بالمصيدة). ما هي الأنماط المضادة؟  دعونا نلقي نظرة على الأمثلة (الجزء 1) - 2كقاعدة عامة، تنقسم الأنماط المضادة إلى الأنواع التالية:
  1. الأنماط المعمارية المضادة - الأنماط المعمارية المضادة التي تنشأ عند تصميم هيكل النظام (عادةً بواسطة مهندس معماري).
  2. الأنماط المضادة للإدارة - الأنماط المضادة في مجال الإدارة، والتي عادة ما يواجهها مختلف المديرين (أو مجموعات من المديرين).
  3. تطوير الأنماط المضادة - الأنماط المضادة هي مشاكل تطوير تنشأ عندما يكتب المبرمجون العاديون نظامًا.
إن غرابة الأنماط المضادة أوسع بكثير، لكننا لن نأخذها في الاعتبار اليوم، لأنها ستكون ساحقة بالنسبة للمطورين العاديين. في البداية، دعونا نأخذ مثالاً على النمط المضاد في مجال الإدارة.

1. الشلل التحليلي

يعتبر شلل التحليل نمطًا تنظيميًا كلاسيكيًا مضادًا. وهو ينطوي على المبالغة في تحليل الموقف عند التخطيط بحيث لا يتم اتخاذ أي قرار أو إجراء، مما يؤدي إلى شل التنمية بشكل أساسي. يحدث هذا غالبًا عندما يكون الهدف هو تحقيق الكمال والإكمال الكامل لفترة التحليل. يتميز هذا النمط المضاد بالمشي في دوائر (نوع من الحلقة المغلقة)، ومراجعة وإنشاء نماذج تفصيلية، والتي بدورها تتعارض مع سير العمل. على سبيل المثال، أنت تحاول التنبؤ بأشياء مثل: ماذا لو أراد المستخدم فجأة إنشاء قائمة بالموظفين بناءً على الحرفين الرابع والخامس من أسمائهم، بما في ذلك المشاريع التي خصصوا لها أكبر عدد من ساعات العمل بين العام الجديد و الثامن من مارس في السنوات الأربع السابقة؟ في جوهرها، هذا هو الإفراط في التحليل. ومن الأمثلة الواقعية الجيدة على ذلك كيف أدى شلل التحليل إلى إفلاس شركة كوداك . فيما يلي بعض النصائح السريعة لمكافحة شلل التحليل:
  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مع منطق الأعمال. مثل هذا الشيء الإلهي يصبح ضخمًا وأخرق لدعمه بشكل مناسب. علينا أن نصلحها في كل جزء من التعليمات البرمجية: تعتمد عليها العديد من العقد في النظام وترتبط بها بإحكام. أصبح الحفاظ على مثل هذا الكود أكثر صعوبة. في مثل هذه الحالات، يجب تقسيمها إلى فئات منفصلة، ​​\u200b\u200bسيكون لكل منها غرض واحد فقط (الهدف). في هذا المثال، يمكنك تقسيمها إلى فئة داو:
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) - 3دعونا نلقي نظرة على عيوب هذا القالب:
  1. الدولة العالمية. عندما نصل إلى مثيل لفئة ما، فإننا لا نعرف الحالة الحالية لتلك الفئة أو من قام بتغييرها أو متى، وقد لا تكون تلك الحالة هي ما نتوقعه. بمعنى آخر، تعتمد صحة العمل مع المفرد على ترتيب الاستدعاءات إليه، مما يجعل الأنظمة الفرعية تعتمد على بعضها البعض، ونتيجة لذلك، يزيد بشكل خطير من تعقيد التطوير.

  2. تنتهك Singleton أحد مبادئ SOLID - مبدأ المسؤولية الفردية - تتحكم فئة Singleton، بالإضافة إلى أداء مسؤولياتها المباشرة، في عدد مثيلاتها.

  3. إن تبعية الفصل العادي على المفردة غير مرئية في واجهة الفصل. نظرًا لأنه عادةً لا يتم تمرير مثيل المفرد في معلمات الطريقة، ولكن يتم الحصول عليه مباشرةً من خلاله، getInstance()لتحديد تبعية فئة ما على المفرد، فأنت بحاجة إلى التعمق في تنفيذ كل طريقة - ما عليك سوى عرض الجمهور عقد الكائن ليست كافية.

    يؤدي وجود المفرد إلى تقليل قابلية اختبار التطبيق بشكل عام والفئات التي تستخدم المفرد بشكل خاص. أولاً، لا يمكنك وضع كائن Mock بدلاً من كائن فردي، وثانيًا، إذا كان للكائن الفردي واجهة لتغيير حالته، فستعتمد الاختبارات على بعضها البعض.

    بمعنى آخر، يزيد المفرد من الاتصال، وكل ما سبق ليس أكثر من نتيجة لزيادة الاتصال.

    وإذا فكرت في الأمر، فيمكن تجنب استخدام المفردة. على سبيل المثال، للتحكم في عدد مثيلات الكائن، من الممكن (والضروري) استخدام أنواع مختلفة من المصانع.

    يكمن الخطر الأكبر في محاولة بناء بنية التطبيق بالكامل بناءً على المفردات. هناك العديد من البدائل الرائعة لهذا النهج. المثال الأكثر أهمية هو 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. مرساة القارب

مرساة القارب في سياق الأنماط المضادة تعني تخزين الأجزاء غير المستخدمة من النظام والتي تبقى بعد بعض التحسين أو إعادة البناء. أيضًا، يمكن ترك بعض أجزاء الكود "للمستقبل"، في حالة اضطرارك إلى استخدامها مرة أخرى. يؤدي هذا إلى تحويل الكود بشكل أساسي إلى سلة المهملات. ما هي الأنماط المضادة؟  دعونا نلقي نظرة على الأمثلة (الجزء 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);
   }
}
نقدم هذه الفئة في شكل النمط المفرد /النمط المضاد الموصوف أعلاه ، أي أنه يمكن أن يكون هناك كائن واحد فقط من هذا النوع، وهو يعمل على كائنات معينة 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). الأنماط المضادة هي فخاخ غالبًا ما يقع فيها المطورون بسبب النقص الحاد في الوقت أو عدم الانتباه أو قلة الخبرة أو الركلات من المديرين. يمكن أن يؤدي النقص المعتاد في الوقت والتسرع إلى حدوث مشكلات كبيرة للتطبيق في المستقبل، لذا يجب معرفة هذه الأخطاء وتجنبها مسبقًا. ما هي الأنماط المضادة؟  دعونا نلقي نظرة على الأمثلة (الجزء 1) - 6وبهذا يكون قد انتهى الجزء الأول من المقال: يتبع .
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION