Mã sạch và các đối tượng lai và tính năng ghen tị


10

Vì vậy, gần đây tôi đã thực hiện một số phép tái cấu trúc chính cho mã của mình. Một trong những điều chính tôi đã cố gắng làm là tách các lớp của tôi thành các đối tượng dữ liệu và các đối tượng worker. Điều này đã được truyền cảm hứng, trong số những thứ khác, bởi phần này của Clean Code :

Giống lai

Sự nhầm lẫn này đôi khi dẫn đến các cấu trúc dữ liệu lai không may là một nửa đối tượng và một nửa cấu trúc dữ liệu. Chúng có các hàm làm những việc quan trọng, và chúng cũng có các biến công khai hoặc các bộ truy cập và bộ biến đổi công khai, với tất cả ý nghĩa và mục đích, làm cho các biến riêng tư công khai, cám dỗ các hàm bên ngoài khác sử dụng các biến đó theo cách mà một chương trình thủ tục sẽ sử dụng cấu trúc dữ liệu.

Các giống lai này làm cho việc thêm các chức năng mới trở nên khó khăn nhưng cũng khiến việc thêm các cấu trúc dữ liệu mới trở nên khó khăn. Họ là tồi tệ nhất của cả hai thế giới. Tránh tạo ra chúng. Chúng là dấu hiệu của một thiết kế lộn xộn mà các tác giả không chắc chắn - hoặc tệ hơn, không biết gì - cho dù họ cần bảo vệ khỏi các chức năng hoặc loại.

Gần đây tôi đã xem mã cho một trong các đối tượng công nhân của tôi (điều đó xảy ra để triển khai Mẫu khách truy cập ) và thấy điều này:

@Override
public void visit(MarketTrade trade) {
    this.data.handleTrade(trade);
    updateRun(trade);
}

private void updateRun(MarketTrade newTrade) {
    if(this.data.getLastAggressor() != newTrade.getAggressor()) {
        this.data.setRunLength(0);
        this.data.setLastAggressor(newTrade.getAggressor());
    }
    this.data.setRunLength(this.data.getRunLength() + newTrade.getLots());
}

Tôi ngay lập tức nói với bản thân mình "tính đố kị! Logic này nên có trong Datalớp - cụ thể là trong handleTradephương thức. handleTradeupdateRunnên luôn luôn xảy ra cùng nhau". Nhưng sau đó tôi nghĩ "lớp dữ liệu chỉ là một publiccấu trúc dữ liệu, nếu tôi bắt đầu làm điều đó, thì nó sẽ trở thành một đối tượng lai!"

Điều gì tốt hơn và tại sao? Làm thế nào để bạn quyết định làm gì?


2
Tại sao 'dữ liệu' phải là một cấu trúc dữ liệu. Nó có hành vi rõ ràng. Vì vậy, chỉ cần tắt tất cả các getters và setters, để không có đối tượng có thể thao tác trạng thái nội bộ.
Cormac Mulhall

Câu trả lời:


9

Văn bản bạn trích dẫn có lời khuyên tốt, mặc dù tôi sẽ thay thế các cấu trúc dữ liệu của Cameron bằng cách ghi lại các bản ghi chép, giả sử rằng một cái gì đó giống như cấu trúc là có nghĩa. Hồ sơ chỉ là tập hợp dữ liệu câm. Mặc dù chúng có thể biến đổi (và do đó có trạng thái trong tư duy lập trình chức năng), chúng không có bất kỳ trạng thái bên trong nào, không có bất biến nào phải được bảo vệ. Hoàn toàn hợp lệ để thêm các hoạt động vào một bản ghi giúp việc sử dụng nó dễ dàng hơn.

Ví dụ, chúng ta có thể lập luận rằng một vectơ 3D là một bản ghi câm. Tuy nhiên, điều đó không nên ngăn chúng ta thêm một phương thức như thế add, điều này làm cho việc thêm vectơ dễ dàng hơn. Thêm hành vi không biến một bản ghi (không quá ngu ngốc) thành một bản lai.

Dòng này được gạch chéo khi giao diện chung của một đối tượng cho phép chúng ta phá vỡ đóng gói: Có một số phần bên trong mà chúng ta có thể truy cập trực tiếp, do đó đưa đối tượng vào trạng thái không hợp lệ. Dường như với tôi Datacó trạng thái và nó có thể được đưa vào trạng thái không hợp lệ:

  • Sau khi xử lý một giao dịch, kẻ xâm lược cuối cùng có thể không được cập nhật.
  • Kẻ gây hấn cuối cùng có thể được cập nhật ngay cả khi không có giao dịch mới xảy ra.
  • Độ dài chạy có thể giữ lại giá trị cũ của nó ngay cả khi kẻ xâm lược được cập nhật.
  • Vân vân.

Nếu bất kỳ trạng thái nào là hợp lệ cho dữ liệu của bạn, thì mọi thứ đều ổn với mã của bạn và bạn có thể tiếp tục. Mặt khác: DataLớp chịu trách nhiệm về tính nhất quán dữ liệu của chính nó. Nếu xử lý một giao dịch luôn liên quan đến việc cập nhật kẻ xâm lược, hành vi này phải là một phần của Datalớp. Nếu thay đổi kẻ xâm lược liên quan đến việc thiết lập độ dài chạy thành 0, thì hành vi này phải là một phần của Datalớp. Datachưa bao giờ là một kỷ lục ngu ngốc. Bạn đã làm cho nó một lai bằng cách thêm setters công cộng.

Có một kịch bản trong đó bạn có thể xem xét nới lỏng các trách nhiệm nghiêm ngặt này: Nếu Datalà riêng tư cho dự án của bạn và do đó không phải là một phần của bất kỳ giao diện công cộng nào, bạn vẫn có thể đảm bảo sử dụng đúng lớp. Tuy nhiên, điều này đặt trách nhiệm duy trì tính Datanhất quán trong toàn bộ mã, thay vì thu thập chúng tại một vị trí trung tâm.

Gần đây tôi đã viết một câu trả lời về đóng gói , trong đó đi sâu hơn về đóng gói là gì và làm thế nào bạn có thể đảm bảo nó.


5

Thực tế là handleTrade()updateRun()luôn xảy ra cùng nhau (và phương thức thứ hai thực sự thuộc về khách truy cập và gọi một số phương thức khác trên đối tượng dữ liệu) có mùi của khớp nối tạm thời . Điều này có nghĩa là bạn phải gọi các phương thức theo một thứ tự cụ thể và tôi đoán rằng các phương thức gọi không theo thứ tự sẽ phá vỡ điều gì đó tồi tệ nhất hoặc không cung cấp kết quả có ý nghĩa tốt nhất. Không tốt.

Thông thường, cách chính xác để cấu trúc lại sự phụ thuộc đó là cho mỗi phương thức trả về một kết quả có thể được đưa vào phương thức tiếp theo hoặc được thực hiện trực tiếp.

Mã cũ:

MyObject x = ...;
x.actionOne();
x.actionTwo();
String result = x.actionThree();

Mã mới:

MyObject x = ...;
OneResult r1 = x.actionOne();
TwoResult r2 = r1.actionTwo();
String result = r2.actionThree();

Điều này có một số lợi thế:

  • Nó di chuyển các mối quan tâm riêng biệt vào các đối tượng riêng biệt ( SRP ).
  • Nó loại bỏ khớp nối tạm thời: không thể gọi các phương thức không theo thứ tự và chữ ký phương thức cung cấp tài liệu ngầm về cách gọi chúng. Bạn đã bao giờ nhìn vào tài liệu, thấy đối tượng bạn muốn và làm việc ngược chưa? Tôi muốn đối tượng Z. Nhưng tôi cần Y để có Z. Để có Y, tôi cần X. Aha! Tôi có W, được yêu cầu để có X. Xâu chuỗi tất cả lại với nhau và giờ W của bạn có thể được sử dụng để lấy Z.
  • Chia tách các đối tượng như thế này có nhiều khả năng làm cho chúng trở nên bất biến, có nhiều lợi thế vượt ra ngoài phạm vi của câu hỏi này. Việc nhanh chóng là các đối tượng bất biến có xu hướng dẫn đến mã an toàn hơn.

Không có khớp nối tạm thời giữa hai cuộc gọi phương thức đó. Trao đổi thứ tự của họ và hành vi không thay đổi.
durron597

1
Ban đầu tôi cũng nghĩ đến việc ghép nối tiếp / thời gian khi đọc câu hỏi, nhưng sau đó nhận thấy rằng updateRunphương pháp này là riêng tư . Tránh ghép nối tiếp là lời khuyên tốt, nhưng nó chỉ áp dụng cho thiết kế API / giao diện công cộng chứ không áp dụng cho các chi tiết triển khai. Câu hỏi thực sự có vẻ là liệu updateRunnên có trong khách truy cập hay trong lớp dữ liệu và tôi không thấy câu trả lời này giải quyết vấn đề đó như thế nào.
amon

Khả năng hiển thị của updateRunkhông liên quan, điều quan trọng là việc triển khai this.datakhông có trong câu hỏi và là đối tượng đang bị đối tượng khách truy cập thao túng.

Nếu bất cứ điều gì, thực tế là khách truy cập này chỉ đơn giản là gọi một loạt các setters và không thực sự xử lý bất cứ điều gì là một lý do cho khớp nối tạm thời không có mặt. Nó có vẻ không quan trọng thứ tự các setters đang được gọi.

0

Theo quan điểm của tôi, một lớp nên chứa "các giá trị cho trạng thái (biến thành viên) và việc thực hiện hành vi (hàm thành viên, phương thức)".

"Cấu trúc dữ liệu lai không may" xuất hiện nếu bạn đặt các biến thành viên trạng thái lớp (hoặc getters / setters của chúng) không công khai.

Vì vậy, tôi thấy không cần phải có các lớp riêng biệt cho các đối tượng dữ liệu dữ liệu và các đối tượng worker.

Bạn sẽ có thể giữ các biến thành viên trạng thái không công khai (lớp cơ sở dữ liệu của bạn sẽ có thể xử lý các biến thành viên không công khai)

Ghen tị với tính năng là một lớp sử dụng các phương thức của một lớp khác quá mức. Xem Code_smell . Có một lớp với các phương thức và trạng thái sẽ loại bỏ điều này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.