Có nên xác định một hằng chuỗi nếu nó chỉ được sử dụng một lần?


24

Chúng tôi đang triển khai bộ điều hợp cho Jaxen (thư viện XPath cho Java) cho phép chúng tôi sử dụng XPath để truy cập mô hình dữ liệu của ứng dụng.

Điều này được thực hiện bằng cách triển khai các lớp ánh xạ các chuỗi (được truyền cho chúng tôi từ Jaxen) thành các phần tử của mô hình dữ liệu của chúng tôi. Chúng tôi ước tính chúng tôi sẽ cần khoảng 100 lớp với tổng số hơn 1000 chuỗi so sánh.

Tôi nghĩ rằng cách tốt nhất để làm điều này là đơn giản nếu / khác phát biểu với các chuỗi được viết trực tiếp vào mã - thay vì xác định mỗi chuỗi là một hằng số. Ví dụ:

public Object getNode(String name) {
    if ("name".equals(name)) {
        return contact.getFullName();
    } else if ("title".equals(name)) {
        return contact.getTitle();
    } else if ("first_name".equals(name)) {
        return contact.getFirstName();
    } else if ("last_name".equals(name)) {
        return contact.getLastName();
    ...

Tuy nhiên, tôi luôn được dạy rằng chúng ta không nên nhúng các giá trị chuỗi trực tiếp vào mã mà thay vào đó hãy tạo các hằng chuỗi. Điều đó sẽ trông giống như thế này:

private static final String NAME = "name";
private static final String TITLE = "title";
private static final String FIRST_NAME = "first_name";
private static final String LAST_NAME = "last_name";

public Object getNode(String name) {
    if (NAME.equals(name)) {
        return contact.getFullName();
    } else if (TITLE.equals(name)) {
        return contact.getTitle();
    } else if (FIRST_NAME.equals(name)) {
        return contact.getFirstName();
    } else if (LAST_NAME.equals(name)) {
        return contact.getLastName();
    ...

Trong trường hợp này tôi nghĩ đó là một ý tưởng tồi. Hằng số sẽ chỉ được sử dụng một lần, trong getNode()phương thức. Sử dụng các chuỗi trực tiếp cũng dễ đọc và dễ hiểu như sử dụng các hằng số, và giúp chúng ta tiết kiệm ít nhất một nghìn dòng mã.

Vì vậy, có bất kỳ lý do để xác định hằng chuỗi cho một lần sử dụng? Hoặc có thể chấp nhận sử dụng chuỗi trực tiếp?


Tái bút Trước khi bất cứ ai đề nghị sử dụng enum thay vào đó, chúng tôi đã tạo ra mẫu đó nhưng việc chuyển đổi enum chậm hơn 15 lần so với so sánh chuỗi đơn giản để nó không được xem xét.


Kết luận: Các câu trả lời dưới đây đã mở rộng phạm vi của câu hỏi này ngoài các hằng chuỗi, vì vậy tôi có hai kết luận:

  • Có thể sử dụng chuỗi trực tiếp thay vì hằng chuỗi trong kịch bản này, nhưng
  • Có nhiều cách để tránh sử dụng chuỗi, có thể tốt hơn.

Vì vậy, tôi sẽ thử kỹ thuật bao bọc để tránh các chuỗi hoàn toàn. Thật không may, chúng tôi không thể sử dụng câu lệnh chuyển đổi chuỗi vì chúng tôi chưa có trên Java 7. Cuối cùng, mặc dù, tôi nghĩ rằng câu trả lời tốt nhất cho chúng tôi là thử từng kỹ thuật và đánh giá hiệu suất của nó. Thực tế là nếu một kỹ thuật rõ ràng nhanh hơn thì có lẽ chúng ta sẽ chọn nó bất kể vẻ đẹp hay sự tuân thủ quy ước của nó.


3
Bạn không có kế hoạch bàn phím 1000 thủ công nếu bạn phát biểu?
JeffO

1
Tôi thấy thật đáng buồn khi một thứ ngôn ngữ đơn giản đến khó chịu này lại có thể bằng một số ngôn ngữ như vậy
Jon Purdy

5
Java 7 cho phép Chuỗi làm switchnhãn. Sử dụng một công tắc thay vì ifthác.
Tái lập Monica - M. Schröder

3
chuyển đổi enum chậm hơn 15 lần nếu bạn chuyển đổi một chuỗi thành giá trị enum của nó! Truyền enum trực tiếp và so sánh với một giá trị enum khác cùng loại!
Neil

2
Mùi như HashMap có thể là một giải pháp.
MarioDS

Câu trả lời:


5

Thử đi. Sự phản ánh ban đầu chắc chắn rất tốn kém, nhưng nếu bạn sẽ sử dụng nó nhiều lần, mà tôi nghĩ bạn sẽ làm, đây chắc chắn là một giải pháp tốt hơn những gì bạn đang đề xuất. Tôi không thích sử dụng sự phản chiếu, nhưng tôi thấy mình sử dụng nó khi tôi không thích sự thay thế cho sự phản chiếu. Tôi nghĩ rằng điều này sẽ cứu đội của bạn rất nhiều đau đầu, nhưng bạn phải vượt qua tên của phương thức (viết thường).

Nói cách khác, thay vì chuyển "tên", bạn sẽ chuyển "tên đầy đủ" vì tên của phương thức get là "getFullName ()".

Map<String, Method> methodMapping = null;

public Object getNode(String name) {
    Map<String, Method> methods = getMethodMapping(contact.getClass());
    return methods.get(name).invoke(contact);
}

public Map<String, Method> getMethodMapping(Class<?> contact) {
    if(methodMapping == null) {
        Map<String, Method> mapping = new HashMap<String, Method>();
        Method[] methods = contact.getDeclaredMethods();
        for(Method method : methods) {
            if(method.getParameterTypes().length() == 0) {
                if(method.getName().startsWith("get")) {
                    mapping.put(method.getName().substring(3).toLower(), method);
                } else if (method.getName().startsWith("is"))) {
                    mapping.put(method.getName().substring(2).toLower(), method);
                }
            }
        }
        methodMapping = mapping;
    }
    return methodMapping;
}

Nếu bạn cần truy cập dữ liệu có trong các thành viên của liên hệ, bạn có thể xem xét việc xây dựng một lớp bao bọc cho liên hệ có tất cả các phương thức để truy cập bất kỳ thông tin nào được yêu cầu. Điều này cũng hữu ích để đảm bảo rằng tên của các trường truy cập sẽ luôn giữ nguyên (Tức là nếu lớp trình bao bọc có getFullName () và bạn gọi với tên đầy đủ, nó sẽ luôn hoạt động ngay cả khi liên hệ với getFullName () đã được đổi tên - sẽ gây ra lỗi biên dịch trước khi nó cho phép bạn làm điều đó).

public class ContactWrapper {
    private Contact contact;

    public ContactWrapper(Contact contact) {
        this.contact = contact;
    }

    public String getFullName() {
        return contact.getFullName();
    }
    ...
}

Giải pháp này đã giúp tôi tiết kiệm nhiều lần, cụ thể là khi tôi muốn có một biểu diễn dữ liệu duy nhất để sử dụng trong các cơ sở dữ liệu jsf và khi dữ liệu đó cần được xuất thành báo cáo bằng jasper (không xử lý tốt các trình truy cập đối tượng phức tạp theo kinh nghiệm của tôi) .


Tôi thích ý tưởng về một đối tượng trình bao bọc với các phương thức được gọi thông qua .invoke(), bởi vì nó hoàn toàn loại bỏ các hằng chuỗi. Tôi không thích phản xạ thời gian chạy để thiết lập bản đồ, mặc dù có thể thực thi getMethodMapping()trong một statickhối sẽ ổn để nó xảy ra khi khởi động thay vì một khi hệ thống đang chạy.
gutch

@gutch, mẫu trình bao bọc là một mẫu tôi thường sử dụng, vì nó có xu hướng giải quyết rất nhiều vấn đề liên quan đến giao diện / bộ điều khiển. Giao diện luôn có thể sử dụng trình bao bọc và hài lòng với nó và bộ điều khiển có thể được bật ra trong khi chờ đợi. Tất cả những gì bạn cần biết là dữ liệu nào bạn muốn có sẵn trong giao diện. Và một lần nữa, tôi nói để nhấn mạnh, tôi thường không thích sự phản chiếu, nhưng nếu đó là một ứng dụng web, bạn hoàn toàn có thể chấp nhận được nếu bạn khởi động vì khách hàng sẽ không thấy bất kỳ thời gian chờ đợi nào.
Neil

@Neil Tại sao không sử dụng BeanUtils từ Apache commons? Nó cũng hỗ trợ các đối tượng nhúng. Bạn có thể đi qua toàn bộ cấu trúc dữ liệu obj.attrA.attrB.attrN và Nó có nhiều khả năng khác :-)
Laiv

Thay vì ánh xạ với Bản đồ, tôi sẽ chọn @Annotations. Một cái gì đó giống như JPA. Để xác định Chú thích của riêng tôi cho ánh xạ (các chuỗi) của trình điều khiển ánh xạ với một attr hoặc getter cụ thể. Để làm việc với Chú thích khá dễ dàng và Nó có sẵn từ Java 1.6 (tôi nghĩ)
Laiv

5

Nếu có thể, hãy sử dụng Java 7, cho phép bạn sử dụng Chuỗi trong các switchcâu lệnh.

Từ http://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html

public class StringSwitchDemo {

    public static int getMonthNumber(String month) {

        int monthNumber = 0;

        if (month == null) {
            return monthNumber;
        }

        switch (month.toLowerCase()) {
            case "january":
                monthNumber = 1;
                break;
            case "february":
                monthNumber = 2;
                break;
            case "march":
                monthNumber = 3;
                break;
            case "april":
                monthNumber = 4;
                break;
            case "may":
                monthNumber = 5;
                break;
            case "june":
                monthNumber = 6;
                break;
            case "july":
                monthNumber = 7;
                break;
            case "august":
                monthNumber = 8;
                break;
            case "september":
                monthNumber = 9;
                break;
            case "october":
                monthNumber = 10;
                break;
            case "november":
                monthNumber = 11;
                break;
            case "december":
                monthNumber = 12;
                break;
            default: 
                monthNumber = 0;
                break;
        }

        return monthNumber;
    }

    public static void main(String[] args) {

        String month = "August";

        int returnedMonthNumber =
            StringSwitchDemo.getMonthNumber(month);

        if (returnedMonthNumber == 0) {
            System.out.println("Invalid month");
        } else {
            System.out.println(returnedMonthNumber);
        }
    }
}

Tôi chưa đo, nhưng tôi tin rằng các câu lệnh chuyển đổi biên dịch thành một bảng nhảy thay vì một danh sách dài các so sánh. Điều này sẽ còn nhanh hơn.

Về câu hỏi thực tế của bạn: Nếu bạn chỉ sử dụng nó một lần, bạn không cần phải biến nó thành một hằng số. Tuy nhiên, hãy xem xét rằng một hằng số có thể được ghi lại và hiển thị trong Javadoc. Điều này có thể quan trọng đối với các giá trị chuỗi không tầm thường.


2
Liên quan đến bàn nhảy. Công tắc chuỗi được thay thế bằng công tắc, đầu tiên dựa trên mã băm (kiểm tra đẳng thức cho tất cả các hằng số có cùng mã băm) và chọn chỉ mục nhánh, công tắc thứ hai trên chỉ mục chi nhánh và chọn mã chi nhánh ban đầu. Cái sau rõ ràng là phù hợp cho một bảng nhánh, cái trước không phải do sự phân phối của hàm băm. Vì vậy, bất kỳ lợi thế hiệu suất có lẽ là do thực hiện dựa trên băm.
Scarfridge

Một điểm rất tốt; nếu nó hoạt động tốt, có thể đáng để chuyển sang Java 7 chỉ vì điều này ...
gutch

4

Nếu bạn sẽ duy trì điều này (thực hiện bất kỳ loại thay đổi không cần thiết nào), tôi thực sự có thể xem xét sử dụng một số loại tạo mã điều khiển chú thích (có thể thông qua CGLib ) hoặc thậm chí chỉ là một tập lệnh viết tất cả mã cho bạn. Hãy tưởng tượng số lỗi chính tả và lỗi có thể xuất hiện trong cách tiếp cận bạn đang xem xét ...


Chúng tôi đã xem xét chú thích các phương thức hiện có, nhưng một số ánh xạ đi qua nhiều đối tượng (ví dụ ánh xạ "quốc gia" đến object.getAddress().getCountry()) rất khó biểu thị bằng các chú thích. Các so sánh chuỗi if / other không đẹp, nhưng chúng nhanh, linh hoạt, dễ hiểu và dễ kiểm tra đơn vị.
Gutch

1
Bạn nói đúng về khả năng lỗi chính tả và lỗi; phòng thủ duy nhất của tôi có bài kiểm tra đơn vị. Tất nhiên điều đó có nghĩa là còn nhiều mã hơn ...
gutch

2

Tôi vẫn sẽ sử dụng các hằng số được xác định ở đầu các lớp của bạn. Nó làm cho mã của bạn dễ bảo trì hơn vì dễ dàng thấy những gì có thể thay đổi sau đó (nếu cần). Ví dụ, "first_name"có thể trở thành "firstName"một thời gian sau.


Tuy nhiên, tôi đồng ý, nếu mã này sẽ được tạo tự động và các hằng số không được sử dụng ở nơi khác, thì điều đó không thành vấn đề (OP nói rằng họ cần phải làm điều này trong 100 lớp).
NoChance

5
Tôi chỉ không thấy góc "khả năng duy trì" ở đây bạn thay đổi "First_name" thành "giveName" một lần ở một nơi trong cả hai trường hợp. Tuy nhiên, trong trường hợp các hằng số có tên, bạn hiện đang để lại một biến lộn xộn "First_name" đề cập đến một chuỗi "giveName" vì vậy bạn có thể muốn thay đổi điều đó để bây giờ bạn có ba thay đổi ở hai nơi
James Anderson

1
Với IDE phù hợp, những thay đổi này là không đáng kể. Điều tôi ủng hộ là việc thực hiện những thay đổi này rõ ràng hơn ở đâu vì bạn đã dành thời gian để khai báo các hằng số ở đầu lớp và không phải đọc qua phần còn lại của mã trong lớp để thực hiện những thay đổi này
Bernard

Nhưng khi bạn đang đọc câu lệnh if, bạn phải quay lại và kiểm tra xem hằng có chứa chuỗi mà bạn nghĩ nó chứa không - không có gì được lưu ở đây.
James Anderson

1
Có lẽ, nhưng đây là lý do tại sao tôi đặt tên cho hằng số của mình tốt.
Bernard

1

Nếu việc đặt tên của bạn nhất quán (aka "some_whatever"luôn được ánh xạ tới getSomeWhatever()), bạn có thể sử dụng sự phản chiếu để xác định và thực hiện phương thức get.


Tốt hơn getSome_whthing (). Có thể phá vỡ vỏ lạc đà, nhưng điều quan trọng hơn nhiều để đảm bảo phản xạ hoạt động. Thêm vào đó, nó có thêm lợi thế mà nó khiến bạn phải thốt lên, "Tại sao chúng ta lại làm theo cách đó .. oh chờ đã .. Đợi đã! George đừng thay đổi tên của phương pháp đó!"
Neil

0

Tôi đoán, xử lý chú thích có thể là giải pháp, thậm chí không có chú thích. Đó là thứ có thể tạo ra tất cả các mã nhàm chán cho bạn. Nhược điểm là bạn sẽ nhận được các lớp N được tạo cho các lớp mô hình N. Bạn cũng không thể thêm bất cứ thứ gì vào một lớp hiện có, nhưng viết một cái gì đó như

public Object getNode(String name) {
    return SomeModelClassHelper.getNode(this, name);
}

một lần mỗi lớp không phải là một vấn đề. Ngoài ra, bạn có thể viết một cái gì đó như

public Object getNode(String name) {
    return getHelper(getClass()).getNode(this, name);
}

trong một siêu lớp phổ biến.


Bạn có thể sử dụng sự phản chiếu thay vì xử lý chú thích để tạo mã. Nhược điểm là bạn cần mã của bạn để biên dịch trước khi bạn có thể sử dụng sự phản chiếu trên nó. Điều này có nghĩa là bạn không thể dựa vào mã được tạo trong các lớp mô hình của mình, trừ khi bạn tạo một số sơ khai.


Tôi cũng sẽ xem xét việc sử dụng trực tiếp sự phản ánh. Chắc chắn, phản xạ là chậm, nhưng tại sao nó chậm? Đó là bởi vì nó phải làm tất cả những việc bạn cần làm, ví dụ, bật tên trường.

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.