CodeGym /مدونة جافا /Random-AR /ما هي الأنماط المضادة؟ دعونا نلقي نظرة على بعض الأمثلة (ا...
John Squirrels
مستوى
San Francisco

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

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

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

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

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

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

    يكمن الخطر الأكبر في محاولة إنشاء بنية تطبيق كاملة تعتمد على المفردات. هناك الكثير من البدائل الرائعة لهذا النهج. المثال الأكثر أهمية هو Spring، أي حاويات IoC الخاصة به: فهي حل طبيعي لمشكلة التحكم في إنشاء الخدمات، لأنها في الواقع "مصانع على المنشطات".

    تدور الآن العديد من المناقشات التي لا نهاية لها والتي لا يمكن التوفيق بينها حول هذا الموضوع. الأمر متروك لك لتقرر ما إذا كان المفرد هو نمط أم مضاد للنمط.

    لن نطيل عليه. بدلاً من ذلك، سننتقل إلى نمط التصميم الأخير لهذا اليوم – روح شريرة.

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