JavaRush /Java блогу /Random-KY /Antipatterns деген эмне? Мисалдарды карап көрөлү (1-бөлүк...
Константин
Деңгээл

Antipatterns деген эмне? Мисалдарды карап көрөлү (1-бөлүк)

Группада жарыяланган
Antipatterns деген эмне?  Мисалдарды карап көрөлү (1-бөлүк) - 1Баарыңарга жакшы күн! Өткөндө менден интервью алып, менден антипаттерндер жөнүндө суроо беришти: бул кандай жырткыч, алардын кандай түрлөрү жана иш жүзүндө мисалдары бар. Албетте, мен суроого, бирок өтө үстүртөн жооп бердим, анткени мен бул маселени изилдөөгө өтө терең киришкен эмесмин. Интервьюдан кийин мен бул темага көбүрөөк сүңгүп, интернетти кыдыра баштадым. Бүгүн мен эң популярдуу антипаттерндерге жана алардын мисалдарына кыскача сереп салгым келет, окуу бул маселе боюнча керектүү бorмди бере алат. Баштайлы! Андыктан, антиплаттер деген эмне экенин талкуулоодон мурун, үлгү деген эмне экенин эстеп көрөлү. Үлгү - тиркемени иштеп чыгууда пайда болгон жалпы көйгөйлөрдү же кырдаалдарды чечүү үчүн кайталануучу архитектуралык долбоор. Бирок бүгүн биз алар жөнүндө эмес, алардын карама-каршы жактары - антипаттерндер жөнүндө сөз болуп жатат. Анти-үлгү – натыйжасыз, кооптуу же жемишсиз көп кездешкен көйгөйлөрдүн классын чечүүнүн жалпы ыкмасы. Башка сөз менен айтканда, бул ката үлгүсү (ошондой эле кээде тузак деп аталат). Antipatterns деген эмне?  Мисалдарды карап көрөлү (1-бөлүк) - 2Эреже катары, антипаттерндер төмөнкү түрлөргө бөлүнөт:
  1. Архитектуралык антипаттерндер - системанын структурасын долбоорлоодо (көбүнчө архитектор тарабынан) пайда болгон архитектуралык антиплаттерлор.
  2. Management Anti Pattern - адатта ар кандай менеджерлер (же менеджерлердин топтору) туш болгон башкаруу чөйрөсүндөгү антиплаттерндер.
  3. Development Anti Pattern - антипаттерндер бул жөнөкөй программисттер системаны жазганда пайда болгон иштеп чыгуу көйгөйлөрү.
Антиптерндердин экзотикасы алда канча кеңири, бирок биз аларды бүгүн карабайбыз, анткени жөнөкөй иштеп чыгуучулар үчүн бул өтө оор болот. Баштоо үчүн, менеджмент тармагындагы антипаттернди мисалга алалы.

1. Аналитикалык паралич

Анализдин шал оорусу классикалык уюштуруучулукка каршы үлгү болуп эсептелет. Бул пландоодо кырдаалды ашыкча талдоону камтыйт, ошондуктан эч кандай чечим же иш-аракет кабыл алынбай, өнүгүүнү олуттуу түрдө токтотот. Бул көп учурда максат кемчorксиздикке жетүү жана талдоо мезгorн толук аяктоо болгондо болот. Бул анти-үлгү, тегерекчелерде жүрүү (жабык циклдин бир түрү), кайра карап чыгуу жана деталдуу моделдерди түзүү менен мүнөздөлөт, бул өз кезегинде иштин жүрүшүнө тоскоол болот. Мисалы, сиз мындай нерселерди алдын ала айтууга аракет кылып жатасыз: эгер колдонуучу күтүлбөгөн жерден өзүнүн аты-жөнүн төртүнчү жана бешинчи тамгаларынын негизинде кызматкерлердин тизмесин түзгүсү келсе, анын ичинде Жаңы Жыл менен Жаңы Жылдын ортосунда эң көп жумуш убактысын арнаган долбоорлор. мурдагы төрт жылдын сегизинчи март? Чындыгында, бул ашыкча талдоо. Жашоодогу жакшы мисал, анализдин шал оорусу Kodakты банкротко алып келген . Бул жерде талдоо шал оорусу менен күрөшүү үчүн бир нече тез кеңештер бар:
  1. Ар бир кабыл алган чечимиңиз сизди максатыңызга жакындатып, убакытты белгилөөгө мажбурлабашы үчүн, узак мөөнөттүү максатты чечим кабыл алуу үчүн маяк катары аныкташыңыз керек.
  2. Майда-чүйдө нерселерге көңүл бурбаңыз (эмне үчүн бул жашооңуздагы акыркы чечимдей кичинекей нюанс боюнча чечим чыгарасыз?)
  3. Чечим кабыл алуу мөөнөтүн белгилеңиз.
  4. Бир ишти кемчorксиз аткарууга аракет кылбаңыз: аны абдан жакшы аткарганыңыз жакшы.
Биз азыр тереңдеп кетпей, башкаруунун башка антипаттерндерин карап чыгабыз. Ошондуктан, преамбуласыз, кээ бир архитектуралык антипаттерндерге өтөбүз, анткени бул макаланы менеджерлер эмес, келечектеги иштеп чыгуучулар окушат.

2. Кудай каршы

Кудайдын an objectиси – бул анти-үлгү, ал өтө көп түрдүү функциялардын ашыкча концентрациясын сүрөттөп, көп сандагы ар түрдүү маалыматтарды (колдонмо айлана турган an 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бизнес логикасы менен фасад ыкмасын да көрөбүз. Мындай кудайлык an object чоң жана олдоксон болуп калат. Биз аны codeдун ар бир бөлүгүндө иштешибиз керек: системадагы көптөгөн түйүндөр ага таянышат жана аны менен тыгыз байланышта. Мындай codeду сактоо барган сайын кыйын болуп баратат. Мындай учурларда, аны өзүнчө класстарга бөлүү керек, алардын ар бири бир гана максатка (максат) ээ болот. Бул мисалда сиз аны дао классына бөлсөңүз болот:
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.Синглтон

Синглтон – бул бир жиптүү тиркемеде кандайдыр бир класстын бир эле нускасы болушуна кепилдик берүүчү жана ошол an objectке глобалдык кирүү чекитинин камсыз кылган эң жөнөкөй үлгүсү. Бул тууралуу кененирээк бул жерден окуй аласыз . Бирок бул үлгүбү же антипаттернби? Antipatterns деген эмне?  Мисалдарды карап көрөлү (1-бөлүк) - 3Келгиле, бул шаблондун кемчorктерин карап көрөлү:
  1. Глобалдык мамлекет. Класстын инстанциясына киргенде, биз ал класстын учурдагы абалын же аны ким же качан өзгөрткөнүн билбейбиз жана ал абал биз күткөндөй болбошу мүмкүн. Башкача айтканда, синглтон менен иштөөнүн тууралыгы ага чалуулардын тартибине жараша болот, бул подсистемалардын бири-бирине көз каранды болушуна алып келет жана натыйжада өнүгүүнүн татаалдыгын олуттуу түрдө жогорулатат.

  2. Singleton SOLID принциптеринин бирин бузуп жатат - Бирдиктүү жоопкерчorк принциби - Singleton классы өзүнүн түздөн-түз милдеттерин аткаруудан тышкары, анын инстанцияларынын санын да көзөмөлдөйт.

  3. Кадимки класстын синглтонго болгон көз карандылыгы класс интерфейсинде көрүнбөйт. Адатта синглтондун мисалы методдун параметрлеринде берилбестен, бирок getInstance()класстын синглтонго көз карандылыгын аныктоо үчүн түздөн-түз алынгандыктан, ар бир ыкманы ишке ашырууга тереңдеп киришиңиз керек - жөн гана жалпыга ачык көрүү an objectинин келишими жетишсиз.

    Синглтондун болушу жалпысынан колдонмонун жана өзгөчө синглтонду колдонгон класстардын сыналышын азайтат. Биринчиден, синглтондун ордуна Mock an objectисин коё албайсыз, экинчиден, синглтондун абалын өзгөртүү үчүн интерфейси болсо, тесттер бири-биринен көз каранды болот.

    Башка сөз менен айтканда, синглтон байланышты жогорулатат жана жогоруда айтылгандардын баары байланыштын жогорулашынын натыйжасынан башка эч нерсе эмес.

    Эгер сиз бул жөнүндө ойлонуп көрсөңүз, синглтонду колдонуудан качууга болот. Мисалы, an objectтин инстанцияларынын санын көзөмөлдөө үчүн, ар кандай фабрикаларды колдонуу толук мүмкүн (жана зарыл).

    Эң чоң коркунуч синглтондорго негизделген колдонмонун бүт архитектурасын куруу аракетинде. Бул ыкманын көптөгөн сонун альтернативалары бар. Эң маанилүү мисал Spring, тактап айтканда, анын IoC контейнерлери: ал жерде кызматтарды түзүүнү көзөмөлдөө маселеси табигый түрдө чечилет, анткени алар чындыгында "стероиддик заводдор" болуп саналат.

    Азыр бул темада көп голивар бар, андыктан синглтон үлгү же антипаттерн экендигин сиз чечесиз.

    Ал эми биз бул жөнүндө токтоп калбайбыз жана бүгүнкү күндүн акыркы дизайн үлгүсүнө - poltergeistке өтөбүз.

4. Полтергеист

Poltergeist башка класстын ыкмаларын чакыруу үчүн колдонулган же жөн эле абстракциянын керексиз катмарын кошо турган пайдалуу эмес класстын антипаттерн. Антиптерн абалы жок кыска мөөнөттүү an objectтер түрүндө көрүнөт. Бул an objectтер көбүнчө башка, бир кыйла туруктуу an objectтерди инициализациялоо үчүн колдонулат.
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);
   }
}
Жөн эле ортомчу болуп, ишин башка бирөөгө өткөрүп берген an objectтин бизге эмне кереги бар? Биз аны жок кылабыз жана ал ишке ашырган кичинекей функцияларды узак мөөнөттүү an objectтерге жылдырабыз. Андан кийин, биз (жөнөкөй иштеп чыгуучулар катары) бизди абдан кызыктырган үлгүлөргө өтөбүз - Development antipatterns .

5. Катуу code

Ошентип, биз бул коркунучтуу сөзгө жеттик - hardcode. Бул антипаттерндин маңызы codeдун белгилүү бир аппараттык конфигурацияга жана/же системалык чөйрөгө катуу байланышкандыгында турат, бул аны башка конфигурацияларга порттоштурууну абдан кыйындатат. Бул антипаттерн сыйкырдуу сандар менен тыгыз байланышта (алар көбүнчө бири-бирине чырмалышкан). Мисал:
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;
}
Какталган, туурабы? Бул жерде биз түздөн-түз байланышыбыздын конфигурациясын орнотобуз, натыйжада code MySQL менен гана туура иштейт жана маалымат базасын өзгөртүү үчүн codeго кирип, баарын кол менен өзгөртүү керек. Конфигурацияларды өзүнчө файлга коюу жакшы чечим болмок:
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. Кайыктын якорь

Антипаттерндердин контекстиндеги кайык анкери кандайдыр бир оптималдаштыруудан же рефакторингден кийин калган системанын пайдаланылбаган бөлүктөрүн сактоону билдирет. Ошондой эле, codeдун кээ бир бөлүктөрүн кайра колдонууга туура келсе, "келечекке" калтырылышы мүмкүн. Бул codeду таштанды челекине айлантат. Antipatterns деген эмне?  Мисалдарды карап көрөлү (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ал мындан ары колдонулbyte жана аны жок кылуу өкүнүчтүү: ал (же анын идеясы) дагы эле пайдалуу болсочу? Мындай code тутумдарды татаалдантат жана чаташтырат, бирок эч кандай практикалык маани бербейт. Башка долбоорго кеткенде "өлүк кесектер" бар мындай codeду кесиптешиңизге өткөрүп берүү кыйынга турганын унутпашыбыз керек. Кайык якорлору менен иштөөнүн эң жакшы ыкмасы - codeдун рефакторинги, тактап айтканда, codeдун бул бөлүмдөрүн жок кылуу (тиле, тилекке каршы). Ошондой эле, иштеп чыгууну пландаштырууда, мындай анкерлердин пайда болушун эске алуу керек (калдыктарды тазалоо үчүн убакыт бөлүү).

7. Объекттин агындысы

Бул антипаттернди сүрөттөө үчүн, адегенде an object бассейнинин үлгүсү менен таанышуу керек . Объект пулу (ресурстук бассейн) бул генеративдик дизайн үлгүсү , инициализацияланган жана колдонууга даяр an objectтердин жыйындысы. Тиркеме an objectти талап кылганда, ал жаңыдан түзүлбөйт, бирок бул бассейнден алынат. Объекттин кереги жок болгондо, ал жок кылынbyte, бирок бассейнге кайтарылат. Адатта, ар бир жолу түзүү үчүн ресурсту көп талап кылган оор an objectтер үчүн колдонулат, мисалы, маалымат базасын туташтыруу. Мисал үчүн кичинекей жана жөнөкөй бир мисалды карап көрөлү. Ошентип, бизде бул үлгүнү чагылдырган класс бар:
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);
   }
}
Биз бул классты жогоруда сүрөттөлгөн синглтон үлгүсү/антипаттерн түрүндө сунуштайбыз , башкача айтканда, бул типтеги бир гана an object болушу мүмкүн, ал белгилүү an objectтерде иштейт Resource, демейки боюнча конструктордо бассейн 4 нуска менен толтурулат; мындай нерсе алынганда бассейнден чыгарылат (эгер ал жок болсо түзүлүп, дароо берилет), аягында an objectти кайра коюу ыкмасы бар. Объекттер 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;
   }
}
Бул жерде биз ачкыч катары үлгүлөрдүн аттары менен картаны камтыган кичинекей an object жана маани катары аларга шилтемелер, ошондой эле картага кирүү ыкмалары бар. Кел карайбыз 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);
   }
}
Бул жерде бардыгы түшүнүктүү: биз бассейн an objectисин алабыз, андан ресурстары бар an objectти алып чыгабыз, андан картаны алып, аны менен бир нерсе кылып, андан ары кайра колдонуу үчүн баарын кайра бассейнге салабыз. Voila: бул жерде сизде an object бассейнинин үлгүсү бар. Бирок биз антипаттерндер жөнүндө айтып жатканбыз, туурабы? Келгиле, бул ишти карап көрөлү 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);
Бул жерде дагы ресурс an objectи алынат, анын үлгүлөрү бар картасы алынат жана аны менен бир нерсе жасалат, бирок an object пулуна кайра сактоодон мурун карта тазаланып, түшүнүксүз маалыматтар менен толтурулат, бул бул Ресурс an objectисин кайра колдонууга жараксыз кылат. Объект пулунун негизги нюанстарынын бири an object кайтарылгандан кийин аны андан ары кайра колдонууга ылайыктуу абалга кайтаруу керек. Эгерде an objectтер бассейнге кайтарылгандан кийин туура эмес же аныкталбаган абалда болсо, анда бул конструкция an objectилердин таштанды чуңкуру деп аталат. Кайра колдонууга болбой турган an objectилерди сактоонун эмне кереги бар? Бул жагдайда, сиз конструктордо ички картаны өзгөрүлбөс кыла аласыз:
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 менен бирге түшөт). Антиптерндер – бул иштеп чыгуучулар көбүнчө убакыттын жетишсиздигинен, көңүл бурбоодон, тажрыйбасыздыктан же менеджерлердин соккусунан улам түшө турган тузак. Кадимки убакыттын жетишсиздиги жана шашылыш келечекте колдонуу үчүн чоң көйгөйлөргө алып келиши мүмкүн, андыктан бул каталарды алдын ала бorп, алдын алуу керек. Antipatterns деген эмне?  Мисалдарды карап көрөлү (1-бөлүк) - 6Ушуну менен макаланын биринчи бөлүгү аяктады: уландысы .
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION