Đó có phải là một thói quen xấu để (hơn) sử dụng sự phản chiếu?


16

Đó có phải là một cách thực hành tốt để sử dụng sự phản chiếu nếu làm giảm đáng kể số lượng mã soạn sẵn?

Về cơ bản, có một sự đánh đổi giữa hiệu suất và có thể dễ đọc ở một bên và sự trừu tượng hóa / tự động hóa / giảm mã nồi hơi ở phía bên kia.

Chỉnh sửa: Đây là một ví dụ về việc sử dụng phản xạ được đề nghị .

Để đưa ra một ví dụ, giả sử có một lớp trừu tượng Basecó 10 trường và có 3 lớp con SubclassA, SubclassBSubclassCmỗi lớp có 10 trường khác nhau; chúng đều là những hạt đậu đơn giản Vấn đề là bạn có được hai Baseloại tham chiếu và bạn muốn xem các đối tượng tương ứng của chúng có cùng loại (phụ) không và có bằng nhau không.

Vì các giải pháp có giải pháp thô trong đó trước tiên bạn kiểm tra xem các loại có bằng nhau không và sau đó kiểm tra tất cả các trường hoặc bạn có thể sử dụng phản xạ và tự động xem liệu chúng có cùng loại không và lặp lại trên tất cả các phương thức bắt đầu bằng "get" (quy ước qua cấu hình), gọi chúng trên cả hai đối tượng và gọi bằng với kết quả.

boolean compare(Base base1, Base, base2) {
    if (base1 instanceof SubclassA && base2 instanceof SubclassA) { 
         SubclassA subclassA1 = (SubclassA) base1;
         SubclassA subclassA2 = (SubclassA) base2;
         compare(subclassA1, subclassA2);
    } else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
         //the same
    }
    //boilerplate
}

boolean compare(SubclassA subA1, SubclassA subA2) {
    if (!subA1.getField1().equals(subA2.getField1)) {
         return false;
    }
    if (!subA1.getField2().equals(subA2.getField2)) {
         return false;
    }
    //boilerplate
}

boolean compare(SubclassB subB1, SubclassB subB2) {
    //boilerplate
}

//boilerplate

//alternative with reflection 
boolean compare(Base base1, Base base2) {
        if (!base1.getClass().isAssignableFrom(base2.getClass())) {
            System.out.println("not same");
            System.exit(1);
        }
        Method[] methods = base1.getClass().getMethods();
        boolean isOk = true;
        for (Method method : methods) {
            final String methodName = method.getName();
            if (methodName.startsWith("get")) {
                Object object1 = method.invoke(base1);
                Object object2 = method.invoke(base2);
                if(object1 == null || object2 == null)  {
                    continue;
                }
                if (!object1.equals(object2)) {
                    System.out.println("not equals because " + object1 + " not equal with " + object2);
                    isOk = false;
                }
            }
        }

        if (isOk) {
            System.out.println("is OK");
        }
}

20
Lạm dụng bất cứ điều gì là một thói quen xấu.
Tulains Córdova

1
@ user61852 Đúng vậy, quá nhiều tự do dẫn đến chế độ độc tài. Một số người Hy Lạp cổ đã biết về điều này.
ott--

6
Có quá nhiều nước sẽ không tốt cho bạn. Rõ ràng, quá nhiều chính xác là số lượng quá mức đó là ý nghĩa của nó! Rằng - Stephen Fry
Jon Purdy

4
"Bất cứ lúc nào bạn thấy mình viết code có dạng 'nếu đối tượng là loại T1, sau đó làm điều gì đó, nhưng nếu đó là loại T2, sau đó làm một cái gì đó khác,' tát mình. Javapractices.com/topic/TopicAction.do?Id = 31
rm5248

Câu trả lời:


25

Reflection đã được tạo ra cho một mục đích cụ thể, để khám phá các chức năng của một lớp học đó là chưa có thời gian biên dịch, tương tự như những gì dlopendlsymchức năng làm trong C. Bất kỳ bên ngoài sử dụng nên được rất nhiều xem xét kỹ lưỡng.

Có bao giờ xảy ra với bạn rằng chính các nhà thiết kế Java đã gặp phải vấn đề này không? Đó là lý do tại sao thực tế mỗi lớp có một equalsphương pháp. Các lớp khác nhau có định nghĩa khác nhau về bình đẳng. Trong một số trường hợp, một đối tượng dẫn xuất có thể bằng một đối tượng cơ sở. Trong một số trường hợp, sự bình đẳng có thể được xác định dựa trên các lĩnh vực riêng tư mà không cần getters. Bạn không biết.

Đó là lý do tại sao mọi đối tượng muốn bình đẳng tùy chỉnh nên thực hiện một equalsphương thức. Cuối cùng, bạn sẽ muốn đặt các đối tượng vào một bộ hoặc sử dụng chúng làm chỉ mục băm, sau đó bạn sẽ phải thực hiện bằng equalsmọi cách. Các ngôn ngữ khác làm điều đó khác nhau, nhưng Java sử dụng equals. Bạn nên tuân theo các quy ước của ngôn ngữ của bạn.

Ngoài ra, mã "soạn sẵn", nếu được đưa vào đúng lớp, khá khó để bắt vít. Sự phản chiếu làm tăng thêm độ phức tạp, nghĩa là cơ hội bổ sung cho các lỗi. Trong phương thức của bạn, ví dụ, hai đối tượng được coi là bằng nhau nếu một đối tượng trả về nullmột trường nhất định và đối tượng kia thì không. Điều gì sẽ xảy ra nếu một trong các getters của bạn trả về một trong các đối tượng của bạn mà không có một đối tượng thích hợp equals? Ý chí của bạn if (!object1.equals(object2))thất bại. Ngoài ra, làm cho nó dễ bị lỗi là thực tế là sự phản chiếu hiếm khi được sử dụng, vì vậy các lập trình viên không quen thuộc với các vấn đề của nó.


12

Việc lạm dụng sự phản chiếu có lẽ phụ thuộc vào ngôn ngữ được sử dụng. Ở đây bạn đang sử dụng Java. Trong trường hợp đó, sự phản chiếu nên được sử dụng cẩn thận vì thường nó chỉ là một cách giải quyết cho thiết kế xấu.

Vì vậy, bạn đang so sánh các lớp khác nhau, đây là một vấn đề hoàn hảo cho phương thức ghi đè . Lưu ý rằng các thể hiện của hai lớp khác nhau không bao giờ được coi là bằng nhau. Bạn chỉ có thể so sánh sự bình đẳng nếu bạn có các thể hiện của cùng một lớp. Xem /programming/27581/overriding-equals-and-hashcode-in-java để biết ví dụ về cách thực hiện so sánh chính xác.


16
+1 - Một số người trong chúng tôi tin rằng BẤT K use việc sử dụng phản chiếu là một lá cờ đỏ biểu thị thiết kế xấu.
Ross Patterson

1
Hầu hết có lẽ trong trường hợp này, một giải pháp dựa trên đẳng thức là mong muốn, nhưng như một ý tưởng chung có gì sai với giải pháp phản ánh? Trên thực tế, nó rất chung chung và các phương thức bằng không cần phải được viết rõ ràng trong mỗi lớp và lớp con (mặc dù chúng có thể dễ dàng được tạo bởi một IDE tốt).
m3th0dman

2
@RossPatterson Tại sao?
m3th0dman

2
@ m3th0dman Bởi vì nó dẫn đến những thứ như compare()phương thức của bạn giả sử rằng bất kỳ phương thức nào bắt đầu bằng "get" đều là getter và an toàn và thích hợp để gọi là một phần của hoạt động so sánh. Đó là vi phạm định nghĩa giao diện dự định của đối tượng và trong khi điều đó có thể là phù hợp, thì hầu như luôn luôn sai.
Ross Patterson

4
@ m3th0dman Nó vi phạm đóng gói - lớp cơ sở phải truy cập các thuộc tính trong các lớp con của nó. Điều gì nếu chúng là riêng tư (hoặc getter private)? Thế còn đa hình? Nếu tôi quyết định thêm một lớp con khác và tôi muốn làm sự so sánh khác nhau trong đó? Chà, lớp cơ sở đã làm điều đó cho tôi và tôi không thể thay đổi nó. Điều gì nếu các getters lười tải một cái gì đó? Tôi có muốn phương pháp so sánh để làm điều đó? Làm thế nào để tôi biết một phương thức bắt đầu bằng getmột getter và không phải là một phương thức tùy chỉnh trả về một cái gì đó?
Sulthan

1

Tôi nghĩ rằng bạn có hai vấn đề ở đây.

  1. Tôi nên có bao nhiêu mã động so với mã tĩnh?
  2. Làm thế nào để tôi thể hiện một phiên bản tùy chỉnh của bình đẳng?

Mã động so với mã tĩnh

Đây là một câu hỏi lâu năm và câu trả lời là rất nhiều ý kiến.

Một mặt, trình biên dịch của bạn rất giỏi trong việc nắm bắt tất cả các loại mã xấu. Nó thực hiện điều này thông qua các hình thức phân tích khác nhau, Phân tích loại là một hình thức phổ biến. Nó biết rằng bạn không thể sử dụng một Bananađối tượng trong mã đang mong đợi a Cog. Nó cho bạn biết điều này thông qua một lỗi biên dịch.

Bây giờ nó chỉ có thể làm điều này khi nó có thể suy ra cả Loại được chấp nhận và Loại đã cho từ ngữ cảnh. Có thể suy ra bao nhiêu và suy luận chung chung như thế nào, phần lớn phụ thuộc vào ngôn ngữ được sử dụng. Java có thể suy ra thông tin kiểu thông qua các cơ chế như Kế thừa, Giao diện và Generics. Số dặm không thay đổi, một số ngôn ngữ khác cung cấp ít cơ chế hơn và một số ngôn ngữ cung cấp nhiều hơn. Nó vẫn nắm rõ những gì trình biên dịch có thể biết là đúng.

Mặt khác, trình biên dịch của bạn không thể dự đoán hình dạng của mã nước ngoài và đôi khi một thuật toán chung có thể được biểu thị qua nhiều loại không thể dễ dàng diễn đạt bằng hệ thống loại ngôn ngữ. Trong những trường hợp này, trình biên dịch không thể luôn biết trước kết quả và thậm chí có thể không biết phải hỏi câu hỏi nào. Sự phản chiếu, giao diện và lớp Object là cách xử lý các vấn đề này của Java. Bạn sẽ phải cung cấp các kiểm tra và xử lý chính xác, nhưng không có hại cho loại mã này.

Cho dù làm cho mã của bạn rất cụ thể, hoặc rất chung chung đến vấn đề bạn đang cố gắng xử lý. Nếu bạn có thể dễ dàng thể hiện nó bằng cách sử dụng hệ thống loại. Hãy để trình biên dịch phát huy thế mạnh của nó và giúp bạn. Nếu hệ thống loại không thể biết trước (mã nước ngoài) hoặc hệ thống loại không phù hợp để thực hiện chung thuật toán của bạn thì phản xạ (và các phương tiện động khác) là công cụ phù hợp để sử dụng.

Chỉ cần lưu ý rằng bước ra ngoài hệ thống loại ngôn ngữ của bạn là đáng ngạc nhiên. Hãy tưởng tượng bạn đang đi đến gần bạn của bạn và bắt đầu một cuộc trò chuyện bằng tiếng Anh. Đột nhiên thả một vài từ từ tiếng Tây Ban Nha, tiếng Pháp và tiếng Quảng Đông trong đó diễn tả chính xác suy nghĩ của bạn. Bối cảnh sẽ nói với bạn của bạn rất nhiều, nhưng họ cũng có thể không biết cách xử lý những từ đó dẫn đến tất cả các loại hiểu lầm. Là đối phó với những hiểu lầm đó tốt hơn là giải thích những ý tưởng bằng tiếng Anh bằng cách sử dụng nhiều từ hơn?

Bình đẳng tùy chỉnh

Mặc dù tôi hiểu rằng Java phụ thuộc rất nhiều vào equalsphương thức để so sánh chung hai đối tượng, nhưng không phải lúc nào nó cũng phù hợp trong một bối cảnh nhất định.

Có một cách khác và đó cũng là một tiêu chuẩn Java. Nó được gọi là một so sánh .

Về cách bạn thực hiện bộ so sánh của bạn sẽ phụ thuộc vào những gì bạn đang so sánh, và làm thế nào.

  • Nó có thể được áp dụng cho bất kỳ hai đối tượng bất kể equalsviệc thực hiện cụ thể của chúng .
  • Nó có thể thực hiện một phương pháp so sánh chung (dựa trên phản xạ) để xử lý bất kỳ hai đối tượng nào.
  • Các chức năng so sánh nồi hơi có thể được thêm vào cho các loại đối tượng thường được so sánh, cấp độ an toàn và tối ưu hóa loại.

1

Tôi thích tránh lập trình phản chiếu càng nhiều càng tốt bởi vì nó

  • làm cho mã khó hơn để kiểm tra tĩnh bởi trình biên dịch
  • làm cho mã khó hơn để lý do về
  • làm cho mã khó hơn để cấu trúc lại

Nó cũng ít hiệu quả hơn nhiều so với các cuộc gọi phương thức đơn giản; nó từng chậm hơn bởi một thứ tự cường độ trở lên.

Mã kiểm tra tĩnh

Bất kỳ mã phản chiếu nào cũng tra cứu các lớp và phương thức bằng cách sử dụng các chuỗi; trong ví dụ ban đầu, nó tìm kiếm bất kỳ phương thức nào bắt đầu bằng "get"; nó sẽ trả về getters nhưng các phương thức khác ngoài ra, chẳng hạn như "gettysburgAddress ()". Các quy tắc có thể được thắt chặt trong mã, nhưng vấn đề vẫn là kiểm tra thời gian chạy ; một IDE và trình biên dịch không thể giúp đỡ. Nói chung, tôi không thích mã "gõ theo chuỗi" hoặc "bị ám ảnh nguyên thủy".

Khó hơn để lý do về

Mã phản chiếu dài hơn các lệnh gọi phương thức đơn giản. Nhiều mã hơn = nhiều lỗi hơn, hoặc ít nhất là tiềm năng nhiều lỗi hơn, nhiều mã hơn để đọc, để kiểm tra, v.v ... Ít hơn là nhiều hơn.

Khó tái cấu trúc hơn

Bởi vì mã được dựa trên chuỗi / động; ngay khi phản chiếu xuất hiện, bạn không thể cấu trúc lại mã với độ tin cậy 100% bằng các công cụ tái cấu trúc của IDE vì IDE không thể sử dụng phản xạ.

Về cơ bản, tránh phản ánh trong mã chung nếu có thể; tìm kiếm một thiết kế cải tiến


0

Quá lạm dụng bất cứ điều gì theo định nghĩa là xấu, phải không? Vì vậy, bây giờ hãy loại bỏ (hơn).

Bạn có thể sử dụng phản xạ nội bộ nặng nề của mùa xuân là một thói quen xấu?

Phản ánh mùa xuân bằng cách sử dụng các chú thích - Hibernate cũng vậy (và có thể là hàng chục / hàng trăm công cụ khác).

Thực hiện theo các mẫu đó nếu bạn sử dụng nó trong mã của riêng bạn. Sử dụng các chú thích để đảm bảo IDE người dùng của bạn vẫn có thể giúp họ (Ngay cả khi bạn là "Người dùng" mã duy nhất, việc sử dụng phản xạ bất cẩn có thể sẽ quay lại cắn vào mông bạn).

Tuy nhiên, không cần quan tâm đến cách mã của bạn sẽ được các nhà phát triển sử dụng, ngay cả việc sử dụng phản xạ đơn giản nhất có lẽ là sử dụng quá mức.


0

Tôi nghĩ rằng hầu hết các câu trả lời bỏ lỡ điểm.

  1. Có, bạn có lẽ nên viết equals()hashcode(), như được lưu ý bởi @KarlBielefeldt.

  2. Nhưng, đối với một lớp học có nhiều lĩnh vực, đây có thể là bản tóm tắt tẻ nhạt.

  3. Vì vậy, nó phụ thuộc .

    • Nếu bạn chỉ cần bằng và mã băm hiếm khi , thì thực tế và có thể là ổn, để sử dụng phép tính phản ánh mục đích chung. Ít nhất là một vượt qua đầu tiên nhanh chóng và bẩn.
    • nhưng nếu bạn cần bằng rất nhiều, ví dụ: các đối tượng này được đưa vào HashTables, do đó hiệu suất sẽ là một vấn đề, bạn chắc chắn nên viết mã. nó sẽ nhanh hơn
  4. Một khả năng khác: nếu các lớp của bạn thực sự có quá nhiều lĩnh vực mà việc viết một dấu bằng là tẻ nhạt, hãy xem xét

    • đưa các lĩnh vực vào một bản đồ.
    • bạn vẫn có thể viết getters và setters tùy chỉnh
    • sử dụng Map.equals()để so sánh của bạn. (hoặc Map.hashcode())

ví dụ: ( lưu ý : bỏ qua kiểm tra null, có lẽ nên sử dụng Enums thay vì các phím String, không được hiển thị nhiều ...)

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
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.