- 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).
- 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.
- 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.
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:- 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.
- Đừ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?)
- Đặt ra thời hạn để đưa ra quyết định.
- Đừ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.
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 findAllWithoutPageEn
có 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? Hãy xem xét những nhược điểm của mẫu này:-
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.
-
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ó.
-
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. Ví 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à mergeUser
nó 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 Resource
trô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. Đến đây, phần đầu tiên của bài viết đã kết thúc: còn tiếp tục .
GO TO FULL VERSION