ควรกำหนดค่าคงที่สตริงถ้าจะใช้เพียงครั้งเดียวหรือไม่


24

เรากำลังใช้งานอะแดปเตอร์สำหรับ Jaxen (ไลบรารี XPath สำหรับ Java) ที่ช่วยให้เราใช้ XPath เพื่อเข้าถึงโมเดลข้อมูลของแอปพลิเคชันของเรา

สิ่งนี้ทำได้โดยการใช้คลาสที่จับคู่สตริง (ส่งถึงเราจาก Jaxen) ไปยังองค์ประกอบของโมเดลข้อมูลของเรา เราประเมินว่าเราต้องการประมาณ 100 คลาสโดยมีการเปรียบเทียบสตริงมากกว่า 1,000 รายการ

ฉันคิดว่าวิธีที่ดีที่สุดในการทำเช่นนี้คือง่ายถ้าคำสั่ง / else พร้อมสตริงที่เขียนลงในโค้ดโดยตรงแทนที่จะกำหนดแต่ละสตริงเป็นค่าคงที่ ตัวอย่างเช่น:

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();
    ...

อย่างไรก็ตามฉันถูกสอนเสมอว่าเราไม่ควรฝังค่าสตริงลงในรหัสโดยตรง แต่สร้างค่าคงที่สตริงแทน ที่จะมีลักษณะเช่นนี้:

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();
    ...

ในกรณีนี้ฉันคิดว่ามันเป็นความคิดที่ไม่ดี ค่าคงที่จะถูกใช้ครั้งเดียวเท่านั้นในgetNode()วิธีการ การใช้สตริงโดยตรงนั้นง่ายต่อการอ่านและทำความเข้าใจเหมือนกับการใช้ค่าคงที่และช่วยให้เราเขียนโค้ดอย่างน้อยหนึ่งพันบรรทัด

ดังนั้นมีเหตุผลใดที่จะกำหนดค่าคงที่สตริงสำหรับการใช้งานครั้งเดียว? หรือเป็นที่ยอมรับในการใช้สตริงโดยตรง


PS ก่อนใครแนะนำให้ใช้ enums แทนเราทำต้นแบบว่า แต่การแปลง enum ช้ากว่าการเปรียบเทียบสตริงธรรมดา 15 เท่าดังนั้นจึงไม่ได้รับการพิจารณา


สรุป: คำตอบด้านล่างขยายขอบเขตของคำถามนี้นอกเหนือจากค่าคงที่สตริงดังนั้นฉันจึงมีข้อสรุปสองข้อ:

  • อาจเป็นไปได้ที่จะใช้สตริงโดยตรงแทนที่จะเป็นค่าคงที่สตริงในสถานการณ์นี้แต่
  • มีวิธีหลีกเลี่ยงการใช้สตริงเลยซึ่งอาจจะดีกว่า

ดังนั้นฉันจะลองใช้เทคนิค wrapper ซึ่งหลีกเลี่ยงสตริงอย่างสมบูรณ์ น่าเสียดายที่เราไม่สามารถใช้คำสั่ง string switch ได้เนื่องจากเรายังไม่ได้ใช้บน Java 7 ในท้ายที่สุดฉันคิดว่าคำตอบที่ดีที่สุดสำหรับเราคือการลองใช้เทคนิคแต่ละอย่างและประเมินประสิทธิภาพ ความจริงก็คือถ้าเทคนิคหนึ่งเร็วขึ้นอย่างเห็นได้ชัดเราอาจเลือกโดยไม่คำนึงถึงความงามหรือการยึดมั่นในการประชุม


3
คุณไม่ได้วางแผนที่จะใช้แป้นพิมพ์ 1,000 ด้วยตนเองหากคุณมีคำสั่งหรือไม่
JeffO

1
ฉันคิดว่ามันเศร้าวิธีการที่ไม่พึงประสงค์สิ่งง่ายๆเหล่านี้สามารถอยู่ในบางภาษา ...
จอนจัง

5
Java 7 อนุญาตให้ Strings เป็นswitchป้ายกำกับ ใช้สวิตช์แทนการเรียงifซ้อน
Reinstate Monica - M. Schröder

3
การแปลง enum ช้าลง 15 เท่าหากคุณแปลงสตริงเป็นค่า enum! ผ่าน enum โดยตรงและเปรียบเทียบกับค่า enum ประเภทเดียวกัน!
Neil

2
กลิ่นเหมือน HashMap สามารถเป็นทางออก
MarioDS

คำตอบ:


5

ลองสิ่งนี้ การสะท้อนครั้งแรกนั้นมีราคาแพงอย่างแน่นอน แต่ถ้าคุณจะใช้มันหลายครั้งซึ่งฉันคิดว่าคุณจะต้องใช้นี่เป็นวิธีแก้ปัญหาที่ดีที่สุดในสิ่งที่คุณเสนอ ฉันไม่ชอบใช้การสะท้อน แต่ฉันพบว่าตัวเองใช้มันเมื่อฉันไม่ชอบทางเลือกในการสะท้อนกลับ ฉันคิดว่าสิ่งนี้จะช่วยให้ทีมของคุณปวดหัวมาก แต่คุณต้องผ่านชื่อของวิธีการ (เป็นตัวพิมพ์เล็ก)

กล่าวอีกนัยหนึ่งแทนที่จะผ่าน "ชื่อ" คุณจะต้องผ่าน "ชื่อเต็ม" เพราะชื่อของวิธีการรับคือ "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;
}

หากคุณต้องการเข้าถึงข้อมูลที่มีอยู่ภายในสมาชิกของผู้ติดต่อคุณอาจพิจารณาสร้างคลาส wrapper สำหรับผู้ติดต่อที่มีวิธีการทั้งหมดในการเข้าถึงข้อมูลที่จำเป็น สิ่งนี้จะเป็นประโยชน์สำหรับการรับประกันว่าชื่อของฟิลด์การเข้าถึงจะยังคงเหมือนเดิมเสมอ (เช่นถ้าคลาส wrapper มี getFullName () และคุณโทรด้วยชื่อเต็มมันจะใช้ได้เสมอแม้ว่าชื่อ getFullName () ของผู้ติดต่อจะถูกเปลี่ยน - จะทำให้เกิดข้อผิดพลาดในการรวบรวมก่อนที่จะให้คุณทำเช่นนั้น)

public class ContactWrapper {
    private Contact contact;

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

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

โซลูชันนี้ช่วยฉันหลายครั้งคือเมื่อฉันต้องการให้มีการแสดงข้อมูลเดี่ยวเพื่อใช้ใน DataTables jsf และเมื่อข้อมูลนั้นจำเป็นต้องส่งออกไปยังรายงานโดยใช้ jasper (ซึ่งไม่จัดการกับ access object ที่ซับซ้อนในประสบการณ์ของฉัน) .


ฉันชอบความคิดของวัตถุห่อหุ้มด้วยวิธีการที่เรียกว่าผ่าน.invoke()เพราะมันจะไปกับค่าคงที่สตริงทั้งหมด ฉันไม่ค่อยกระตือรือร้นในการไตร่ตรองรันไทม์เพื่อตั้งค่าแผนที่แม้ว่าอาจจะเรียกใช้งานgetMethodMapping()ในstaticบล็อกจะเป็นเรื่องปกติเพื่อให้มันเกิดขึ้นเมื่อเริ่มต้นแทนที่จะเป็นเมื่อระบบกำลังทำงาน
gutch

@gutch, รูปแบบ wrapper เป็นสิ่งที่ฉันใช้บ่อย, เนื่องจากมันมีแนวโน้มที่จะแก้ไขปัญหามากมายที่เกี่ยวข้องกับอินเตอร์เฟส / คอนโทรลเลอร์ อินเทอร์เฟซสามารถใช้กระดาษห่อและมีความสุขกับมันและตัวควบคุมสามารถเปิดออกภายในในเวลาเดียวกัน สิ่งที่คุณต้องรู้คือข้อมูลที่คุณต้องการในอินเทอร์เฟซ และอีกครั้งฉันพูดเพื่อเน้นว่าฉันไม่ชอบภาพสะท้อนปกติ แต่ถ้าเป็นเว็บแอปพลิเคชันมันเป็นที่ยอมรับได้อย่างสมบูรณ์ถ้าคุณทำเมื่อเริ่มต้นเนื่องจากลูกค้าไม่เห็นเวลารอคอย
Neil

@Neil ทำไมไม่ใช้ BeanUtils จาก Apache ทั่วไป? นอกจากนี้ยังรองรับวัตถุฝังตัว คุณสามารถไปถึงโครงสร้างข้อมูลทั้งobj.attrA.attrB.attrNและมันมี possibilites อื่น ๆ อีกมากมาย :-)
LAIV

ฉันจะใช้ @Annotations แทนการแมปกับแผนที่ JPA ทำเช่นนั้น ในการกำหนดคำอธิบายประกอบของฉันเองสำหรับการแมปรายการคอนโทรลเลอร์ (สตริง) ด้วย attr หรือ getter ที่ระบุ จะทำงานร่วมกับหมายเหตุค่อนข้างง่ายและมันสามารถใช้ได้จาก Java 1.6 (ฉันคิด)
LAIV

5

หากเป็นไปได้ให้ใช้ Java 7 ซึ่งอนุญาตให้คุณใช้ Strings ในswitchข้อความสั่ง

จาก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);
        }
    }
}

ฉันไม่ได้วัด แต่ฉันเชื่อว่าคำสั่ง switch รวบรวมไปยังตารางกระโดดแทนที่จะเป็นรายการเปรียบเทียบแบบยาว ควรเร็วกว่านี้

เกี่ยวกับคำถามที่แท้จริงของคุณ: หากคุณจะใช้เมื่อคุณไม่จำเป็นต้องทำให้มันคงที่ พิจารณาว่าค่าคงที่สามารถจัดทำเป็นเอกสารและแสดงใน Javadoc สิ่งนี้อาจมีความสำคัญสำหรับค่าสตริงที่ไม่สำคัญ


2
เกี่ยวกับตารางการกระโดด สตริงสวิทช์จะถูกแทนที่ด้วยสวิทช์อันดับแรกขึ้นอยู่กับ hashcode (ตรวจสอบความเท่าเทียมกันสำหรับค่าคงที่ทั้งหมดที่มีแฮชโค้ดเดียวกัน) และเลือกดัชนีสาขาสวิตช์ที่สองในดัชนีสาขาและเลือกรหัสสาขาเดิม อันหลังนั้นเหมาะสมกับโต๊ะสาขาอย่างชัดเจน แต่เดิมไม่ได้เกิดจากการกระจายของฟังก์ชันแฮช ดังนั้นข้อได้เปรียบด้านประสิทธิภาพใด ๆ อาจเป็นเพราะการใช้แฮช
Scarfridge

เป็นจุดที่ดีมาก ถ้ามันทำงานได้ดีก็อาจจะคุ้มค่าที่จะย้ายไป Java 7 เพียงสำหรับการนี้ ...
gutch

4

หากคุณจะรักษาสิ่งนี้ไว้ (ทำการเปลี่ยนแปลงใด ๆ ที่ไม่ใช่เรื่องไร้สาระ ) ฉันอาจลองใช้การสร้างรหัสกำกับด้วยคำอธิบายประกอบ (อาจจะผ่านCGLib ) หรือแม้แต่สคริปต์ที่เขียนโค้ดทั้งหมดให้คุณ ลองนึกภาพจำนวนข้อผิดพลาดและข้อผิดพลาดที่อาจคืบคลานเข้ามาด้วยวิธีการที่คุณกำลังพิจารณา ...


เราพิจารณาคำอธิบายประกอบวิธีการที่มีอยู่ แต่การแมปบางอย่างสำรวจวัตถุหลายรายการ (ตัวอย่างเช่นการทำแผนที่ "ประเทศ" object.getAddress().getCountry()) ซึ่งเป็นการยากที่จะแสดงด้วยคำอธิบายประกอบ การเปรียบเทียบสตริง if / else ไม่ได้สวย แต่มีความรวดเร็วยืดหยุ่นเข้าใจง่ายและทดสอบหน่วยง่าย
gutch

1
คุณพูดถูกเกี่ยวกับความผิดพลาดและความผิดพลาด การป้องกันเพียงอย่างเดียวของฉันคือการทดสอบหน่วย แน่นอนว่าหมายถึงรหัสยังเพิ่มเติม ...
gutch

2

ฉันยังคงใช้ค่าคงที่ที่กำหนดไว้ที่ด้านบนของชั้นเรียนของคุณ มันทำให้โค้ดของคุณสามารถบำรุงรักษาได้มากขึ้นเนื่องจากง่ายต่อการดูสิ่งที่สามารถเปลี่ยนแปลงได้ในภายหลัง (ถ้าจำเป็น) ตัวอย่างเช่น"first_name"อาจกลายเป็น"firstName"ในภายหลัง


ฉันเห็นด้วยอย่างไรก็ตามถ้ารหัสนี้จะถูกสร้างขึ้นโดยอัตโนมัติและค่าคงที่ไม่ได้ใช้ที่อื่นมันก็ไม่สำคัญ (OP บอกว่าพวกเขาจำเป็นต้องทำใน 100 ชั้น)
NoChance

5
ฉันไม่เห็นมุม "maintainability" ที่นี่คุณเปลี่ยน "first_name" เป็น "GivenName" ครั้งเดียวในที่เดียวในกรณีใดกรณีหนึ่งอย่างไรก็ตามในกรณีของค่าคงที่ที่มีชื่อคุณจะถูกทิ้งไว้ด้วยตัวแปรยุ่ง "first_name" ซึ่งหมายถึง สตริง "givenName" ดังนั้นคุณอาจต้องการที่จะเปลี่ยนแปลงด้วยเช่นกันว่าเพื่อให้คุณในขณะนี้มีสามการเปลี่ยนแปลงในสถานที่สอง
เจมส์เดอร์สัน

1
ด้วย IDE ที่ถูกต้องการเปลี่ยนแปลงเหล่านี้ไม่สำคัญ สิ่งที่ฉันสนับสนุนคือมันชัดเจนกว่าที่จะทำการเปลี่ยนแปลงเหล่านี้เพราะคุณใช้เวลาในการประกาศค่าคงที่ที่ด้านบนของชั้นเรียนและไม่ต้องอ่านรหัสที่เหลือในชั้นเรียนเพื่อที่จะ ทำการเปลี่ยนแปลงเหล่านี้
เบอร์นาร์ด

แต่เมื่อคุณอ่านคำสั่ง if คุณต้องย้อนกลับไปและตรวจสอบว่าค่าคงที่มีสตริงที่คุณคิดว่ามันมีอยู่ - ไม่มีสิ่งใดถูกบันทึกไว้ที่นี่
James Anderson

1
บางที แต่นี่คือเหตุผลที่ฉันตั้งชื่อค่าคงที่ของฉันได้ดี
เบอร์นาร์ด

1

หากการตั้งชื่อของคุณสอดคล้องกัน (aka "some_whatever"ถูกแมปเสมอgetSomeWhatever()) คุณสามารถใช้การสะท้อนเพื่อกำหนดและเรียกใช้เมธอด get


ดีกว่า getSome_whething () อาจเป็นกรณีของอูฐ แต่สำคัญยิ่งกว่าเพื่อให้แน่ใจว่าการสะท้อนนั้นได้ผล นอกจากนี้ยังมีข้อดีเพิ่มเติมที่ทำให้คุณพูดว่า "ทำไมเราถึงทำแบบนั้นเหรอ .. โอ้เดี๋ยวก่อน .. เดี๋ยวก่อน! จอร์จไม่เปลี่ยนชื่อของวิธีการนั้น!"
Neil

0

ฉันเดาว่าการประมวลผลคำอธิบายประกอบอาจเป็นวิธีแก้ปัญหาแม้ว่าจะไม่มีคำอธิบายประกอบก็ตาม มันเป็นสิ่งที่สามารถสร้างรหัสที่น่าเบื่อสำหรับคุณ ข้อเสียคือคุณจะได้รับคลาส N ที่สร้างขึ้นสำหรับคลาสโมเดล N คุณไม่สามารถเพิ่มสิ่งใด ๆ ลงในคลาสที่มีอยู่เดิมได้ แต่เขียนสิ่งที่ต้องการ

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

หนึ่งครั้งต่อคลาสไม่น่าจะมีปัญหา หรือคุณสามารถเขียนบางอย่างเช่น

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

ในซูเปอร์คลาสทั่วไป


คุณสามารถใช้การไตร่ตรองแทนการประมวลผลคำอธิบายประกอบสำหรับการสร้างรหัส ข้อเสียคือคุณต้องรวบรวมรหัสของคุณก่อนจึงจะสามารถใช้การสะท้อนกลับได้ ซึ่งหมายความว่าคุณไม่สามารถพึ่งพาโค้ดที่สร้างในคลาสโมเดลของคุณได้เว้นแต่คุณจะสร้างสตับบางตัว


ฉันจะพิจารณาใช้การสะท้อนโดยตรง แน่นอนว่าการสะท้อนกลับช้า แต่ทำไมช้า? เป็นเพราะมันต้องทำทุกสิ่งที่คุณต้องทำเช่นเปิดชื่อฟิลด์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.