Xử lý code smell Primitive Obsession – Part 1

Bài này nói về việc xử lý code smell Primitive Obsession – Code smell số 9 trong bài về danh sách các dấu hiệu code smell.

Để hiểu được code smell này đầu tiên biết 2 loại dữ liệu cơ bản trong lập trình OOP: kiểu dữ liệu cơ bản (các kiểu cơ bản như int, double, float, string,… không phải object) và kiểu object. Code smell Primitive Obserssion thường xuất hiện khi:

  • sử dụng kiểu cơ bản  để biểu diễn các đối tượng mang tính trừu tượng. Ví dụ dùng 1 số double để biểu diễn currency (tiền tệ), chuỗi biểu diễn cho số điện thoại, zipcode chẳng hạn. sử dụng mấy kiểu này cho kiểu trừu tượng đến một lúc nào đó sẽ phát sinh thêm các thao tác xử lý hoặc các dữ liệu kèm thêm. nếu dữ liệu đó lặp lại nhiều nơi dẫn đến việc thao tác xử lý trên nó hoặc dữ liệu cần kèm theo sẽ đi cùng –> code khó quản lý, việc duplicate code là tất yếu.
  • Sử dụng biến, giá trị hằng để biểu diễn cho các thông tin mang tính quy ước. Ví dụ:  const USER_ADMIN = 1

Minh họa các xử lý phần 1, phần 2 sẽ viết tiếp sau:

  • Sử dụng kĩ thuật thay thế giá trị dữ liệu bằng object –Replace Data Value With Object:Capture.PNG

Ta thấy mỗi object của class Order sẽ có thông tin về khách hàng customer. thuộc tính customer nó có thể sẽ mở rộng khi có thể cần thêm thông tin như ngày sinh, số điện thoại,… không chỉ đơn thuần là tên. Về cơ bản customer có ý nghĩa trừu tượng là khách hàng mà ý nghĩa này người dùng về sau có thể cần thêm nhiều thuộc tính, thao tác có dữ liệu đó. Và ta thấy một điều khi để một thông tin có thể thay đổi trong một class như thế sẽ dẫn đến class Order sẽ thiếu tính ổn định khi phải xử lý cụ thể customer ở kiểu string trong khi nó là kiểu dữ liệu trừu tượng hơn rất nhiều. Điều đó sẽ dẫn đến là class Order không thỏa nguyên tắc Open/Closed principle trong bộ nguyên tắc SOLID.

Để khắc phục điều này ta có thể sử dụng Replace Data Value With Object di chuyển dữ liệu về customer ra một class khác (lúc này ta tùy ý mở rộng, thay đổi mà ý nghĩa ở phía client – order đối với customer không thay đổi) và Order sẽ chứa object customer.

Giả sử có 1 mẫu class Order
class Order{
public Order (String customer) {
_customer = customer;
}
public String getCustomer() {
return _customer;
}
public void setCustomer(String arg) {
_customer = arg;
}
private String _customer;
}

Xử lý:

Tạo class

class Customer {
public Customer (String name) {
_name = name;
}
public String getName() {
return _name;
}
private final String _name;
}

Chỉnh lại class Order:
Đổi tên (Rename Method) từ getCustomer thành getCusomterName. Đổi tên các đối số cho hợp lí với ngữ cảnh còn về signature của method sẽ giữ nguyên để tránh thay đổi việc sử dụng ở client.

class Order{
public Order (String customerName) {
_customer = new Customer(customer);
}
public String getCustomerName() {
return _customer.getName();
}
private Customer _customer;
public void setCustomer(String customerName) {
_customer = new Customer(customer);
}

Lúc này về cơ bản là xong phần refactory nhưng hiện tại customer được sử dụng ở lại value object. Tức mỗi order sẽ tạo một đối tượng customer của riêng nó. Phần tiếp theo có thể thực hiện hoặc không thực hiện tùy thuộc đang làm với đối tượng nào. Nếu đối tượng là dạng như ngày tháng,… thì không cần làm thêm bước tiếp theo mà chỉ cần để dạng value object là đủ. Trong tình huống này thì cần làm thêm bước tiếp theo vì Mỗi lần cung cấp 1 customerName thì mỗi object mới của customer được tạo ra do đó trong tình huống muốn bổ sung thêm các thông tin khác như số điện thoại, … vào customer thì vẫn chưa làm ngay được vì mỗi đối tượng mới tạo ra thì các giá trị khác của đối tượng cũ đã mất. Đồng thời, một customer có thể mua chung nhiều order, trong tình huống ta muốn tất các customer trong các order của chung một người thì ta cần phải đảm bảo tất customer object trong order phải chỉ chung 1 object (giống như trong thực tế, một người có nhiều đơn hàng nhưng người đó chỉ là một chứ không có người số 2 là bản sao của họ cùng tồn tại). lúc này ta cần phải sử dụng kĩ thuật Change Value to Reference.

Để thực hiện Change Value to Reference

capture1.png

Như phân tích ở trên, giờ nhiệm vụ là làm sao để cho các đối tượng customer cùng chỉ chung một người sẽ phải tham chiếu đến chung 1 vùng nhớ.

Dùng  Replace Constructor with Factory Method (thay hàm khởi tạo bằng factory method) để điều khiển việc tạo đối tượng customer để class customer quản lý hoàn toàn việc tạo ra đối tượng customer mà ko để bên ngoài biết và bên ngoài cũng ko cần quan tâm như sau:

Thêm một hàm create, hàm này có nhiệm vụ nhận là tên khách hàng (giả sử trong bài toàn này không tồn tại 2 khách hàng chung tên, thực tế có thể chỗ này dùng id) trả về tham chiếu đến object của khách hàng có tên được truyền vào.

class Customer {
public static Customer create (String name) {
return new Customer(name);
}

Tiếp theo block constructor bằng private (lúc này bên ngoài không tự ý tạo bất kì đối tượng customer mới nào)

class Customer {
private Customer (String name) {
_name = name;
}

Khi đó bên class Order sẽ thay đổi các nên tạo object customer thông qua hàm create như sau:

class Order {
public Order (String customer) {
_customer = Customer.create(customer);
}

Giờ nhìn lại hàm create. hàm Create hiện tại là mỗi lần gọi create là tạo một đối tượng mới lúc này vẫn chưa xử lý được việc để các đối tượng customer giống nhau (cùng tên) sẽ cùng tham chiếu đến 1 vùng nhớ.

Việc đầu tiền là cần một nơi lưu trữ thông các đối tượng customers để từ đó dựa vào name mà lấy đối tượng hợp lý ra cho người dùng.

Ở đây, ta có thể sử dụng Hashtable (hoặc một cấu trúc dữ liệu bất kì có khả năng chứ nhiều đối tượng và truy xuất đối tượng theo key kiểu key-value) để lưu tất cả danh sách các customer. Khi có yêu cầu tạo một customer mới thì từ hàm create thì sẽ load customer theo tên (như là key) từ hashtable

class Customer {
private static Dictionary _instances = new Hashtable();

static void loadCustomers() {
new Customer ("Lemon Car Hire").store();
new Customer ("Associated Coffee Machines").store();
new Customer ("Bilston Gasworks").store();
}
private void store() {
_instances.put(this.getName(), this);
}

hàm loadCustomers giả lập tạo sẵn các customers hợp lê vào thêm vào _instances. loadCustomers sau này có thể thay thế cho các nguồn lưu trữ dữ liệu customer khách (ví dụ load danh sách customer từ file, từ cơ sỡ dữ liệu,…)

hàm store sẽ lưu customer vào bảng _instances để tái truy xuất. Về cơ bản đến bước này là factory xong. Việc đơn giản cuối cùng là sử dụng Rename Method chỉnh lại tên hàm create thành getNamed để khớp với nội dung.

class Customer{
public static Customer getNamed (String name) {
return (Customer) _instances.get(name);
}

Kết quả xong phát trình refactory

Untitled.png

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google photo

Bạn đang bình luận bằng tài khoản Google Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s

Tạo một website miễn phí hoặc 1 blog với WordPress.com.

Up ↑

%d bloggers like this: