วิธีการของชั้นเรียนควรเรียกผู้ได้รับและผู้ตั้งตน?


53

ฉันทำงานที่ไหนฉันเห็นชั้นเรียนจำนวนมากที่ทำสิ่งนี้:

public class ClassThatCallsItsOwnGettersAndSetters {

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public void methodWithLogic() {
        setField("value");
        //do stuff
        String localField = getField();
        //do stuff with "localField"
    }
}

หากฉันเขียนสิ่งนี้ตั้งแต่เริ่มต้นฉันจะเขียนสิ่งmethodWithLogic()นี้แทน:

public class ClassThatUsesItsOwnFields {

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public void methodWithLogic() {
        field = "value";
        //do stuff            
        //do stuff with "field"
    }
}

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

มีประโยชน์กับวิธีแรกหรือไม่? วิธีแรกดีกว่าจริงหรือ


เมื่อฉันทำการดีบักโค้ดที่ไม่คุ้นเคยใครจะพูดว่าข้อผิดพลาดนั้นไม่มีผลข้างเคียงในวิธีการนั้นบ้าง หน่วยของคุณทดสอบ :)
Joshua Taylor

1
การใช้ getters / setters ในรหัสภายในของคุณช่วยให้คุณสามารถสร้างเบรกพอยต์ในรหัสของคุณถ้าคุณจะผ่านมันด้วยดีบักเกอร์ นอกจากนี้ยังช่วยให้คุณมั่นใจได้ว่าหากมีสิ่งใดที่ผิดพลาดเกิดขึ้นกับการตั้งค่า / รับค่าคุณรู้ว่าเป็นเพราะวิธีการนั้นไม่ใช่เพราะการเข้าถึงที่ไม่ถูกต้อง โดยรวมแล้วจะให้รหัสความสอดคล้องมากขึ้น
callyalater

คำตอบ:


46

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

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


แต่สิ่งที่เกี่ยวกับเมื่อคุณเริ่มต้นข้อมูลในแบ็กเอนด์และคุณไม่ต้องการที่จะตีความว่าสกปรก หากคุณโทรsetField()จากจุดเริ่มต้นคุณเพียงแค่แนะนำข้อบกพร่อง
Daniel Kaplan

6
นั่นคือเหตุผลที่ฉันพูดว่าขึ้นอยู่กับสถานการณ์ อาจเริ่มต้นข้อมูลของคุณควรข้าม setters แต่รหัสตรรกะอื่น ๆ ไม่ควร เลือกกฎที่เหมาะสมและติดกับมัน
Matt S

3
@DanielKaplan: ประเด็นก็คือว่าการเรียก getters และ setters เป็นกรณีส่วนใหญ่สิ่งที่ถูกต้องที่จะทำและเฉพาะในสถานการณ์ที่เฉพาะเจาะจงไม่ได้ อย่างไรก็ตามในรหัสโลกแห่งความเป็นจริงเมื่อคุณเปลี่ยน getter / setter-implementation ที่มีอยู่ในภายหลังเพื่อแนะนำผลข้างเคียงโดยเจตนาคุณอาจต้องตรวจสอบการโทรทุกครั้งที่ getter หรือ setter ภายในชั้นเรียนและเข้าถึงโดยตรงไปยัง สนาม นั่นเป็นเหตุผลที่คุณควรพยายามทำให้คลาสของคุณเล็กที่สุดเท่าที่จะเป็นไปได้
Doc Brown

33

การเรียกใช้ตัวตั้งค่าโดยตรงไม่ใช่ปัญหาและในตัวของมันเอง คำถามควรเป็น: ทำไมรหัสของเรามี setters ทุกที่?

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

หากต้องตั้งค่าฟิลด์จากภายนอกให้ถามตัวคุณเองว่าควรใช้วิธีการที่มีตรรกะหรือไม่ คลาสของคุณเป็นคลาส data หรือ state จริงๆหรือ? คลาสนี้มีความรับผิดชอบมากกว่าหนึ่งอย่างหรือไม่?

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


1
ฉันเห็นด้วย / ฝึกฝนแนวความคิดนี้อย่างสมบูรณ์ ฉันยังคิดว่าคุณนำประเด็นที่สำคัญกว่าคำถามของฉัน ที่กล่าวว่าฉันไม่รู้สึกว่ามันตอบคำถามดั้งเดิมของฉันโดยตรง ถ้าคุณไม่พูดว่า "ในรูปแบบที่ยิ่งใหญ่ของสิ่งที่คำถามของคุณไม่สำคัญ" ซึ่งอาจเป็นจริงด้วยเช่นกัน :)
Daniel Kaplan

@DanielKaplan: ฉันกำลังพูดอย่างนั้น :) ฉันถูกฉีกขาดระหว่างคำตอบและแสดงความคิดเห็นกับสิ่งนี้ แต่จริงๆฉันไม่รู้สึกว่ามีคำตอบที่ถูกต้องที่ไม่ถามคำถามที่ใหญ่กว่า
pdr

9

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


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

1
@DanielKaplan จุดของฉันคือเหตุผลที่เป็นหนึ่งเดียวกัน หากคุณเพิ่มตรรกะตัวตั้งค่าในภายหลังมันจะส่งผลกระทบต่อรหัสภายในเพียงมาก (อาจ) เป็นภายนอก
Michael

2
@Michael: มักจะมีเหตุผลที่ดีสำหรับการเรียนที่จะไม่ใช้ตัวรับ / setters ของตัวเอง สถานการณ์หนึ่งที่พบบ่อยคือที่มีหลายตัวตั้งค่าของรูปแบบsomeField=newValue; refresh(); ถ้าวิธีการอนุญาตให้ตั้งค่าหลายเขตข้อมูลการเรียกตัวตั้งค่าเพื่อเขียนเขตข้อมูลเหล่านั้นจะทำให้เกิดการดำเนินการ "รีเฟรช" ซ้ำซ้อน การเขียนฟิลด์ทั้งหมดแล้วโทรrefresh()หนึ่งครั้งอาจทำให้การดำเนินการดูมีประสิทธิภาพและราบรื่นยิ่งขึ้น
supercat

6

เพื่อตอบคำถามของคุณด้วยคำเดียวใช่

การมีคลาสเรียก getters และ setters ของตัวเองเพิ่มความสามารถในการขยายและให้พื้นฐานที่ดีกว่าสำหรับโค้ดในอนาคต

สมมติว่าคุณมีสิ่งนี้:

public class Vehicle
{
    private int year;
    private String make;

    public Vehicle(int year, String make)
    {
        setYear(year);
        setMake(make);
    }

    public void setYear(int year)
    {
        this.year = year;
    }

    public void setMake(String make)
    {
        this.make = make;
    }
}

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

public class Vehicle
{
    private int year;
    private String make;

    public Vehicle(int year, String make)
    {
        setYear(year);
        setMake(make);
    }

    public void setYear(int year)
    {
        if(year > 0)
        {
            this.year = year;
        }
        else
        {
            System.out.println(year + " is not a valid year!");
        }
    }

    public void setMake(String make)
    {
        this.make = make;
    }
}

5

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


3

หากคุณกำลังพยายามที่จะทำสิ่งที่ให้ไว้โดยส่วนต่อประสานสาธารณะให้ใช้ getters / setters

อย่างไรก็ตามในฐานะเจ้าของ / ผู้พัฒนาชั้นเรียนคุณได้รับอนุญาตให้เข้าถึงส่วนส่วนตัวของรหัสของคุณ (จากภายในชั้นเรียนของหลักสูตร) ​​แต่คุณยังรับผิดชอบในการบรรเทาอันตรายด้วย

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

public class MyClass
{
    private int i;
    private List<string> list;
    public string getNextString()
    {
        i++;
        return list[i];
    }

    private void getString()
    {
        // Do not increment
        string currentString = list[i];

        // Increment
        string nextString = getNextString();
    }
}

คุณสามารถให้เหตุผลในเรื่องนี้ได้หรือไม่?
Daniel Kaplan

1

ใช่. Getters และ setters เป็นตัวแทนของรัฐดังนั้นให้หันคำถามนี้ไป - คุณต้องการติดตามหลายวิธีในการเปลี่ยนสถานะของวัตถุในลักษณะเดียวกันเว้นแต่คุณจะต้องทำอย่างไร

สิ่งที่น้อยกว่าที่คุณต้องติดตามคือสิ่งที่ดีกว่า - นี่คือเหตุผลที่วัตถุที่ไม่เปลี่ยนรูปนั้นง่ายต่อการจัดการ

ลองคิดดูสิไม่มีอะไรขวางกั้นคุณจากการมีทั้งสนามสาธารณะและผู้ทะลุทะลวง / ผู้เซทเทอร์ - แต่คุณจะได้อะไร?

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


ประเด็นคำถามของฉันคือฉันเพียงถามเกี่ยวกับการเข้าถึงโดยตรงของเขตข้อมูลในชั้นเรียนเดียวกัน ฉันเข้าใจวัตถุประสงค์ของผู้ได้รับและผู้ตั้งค่าสำหรับรหัสลูกค้าแล้ว
Daniel Kaplan

@DanielKaplan: ฉันเข้าใจและสิ่งที่ฉันกำลังพูดคือเท่าที่คุณควรปฏิบัติต่อพวกเขาเท่าที่ปฏิบัติได้
jmoreno

@DanielKaplan: คุณสามารถมี setter ส่วนตัวหนึ่งตัวและ setter สาธารณะหนึ่งตัวที่มีผลลัพธ์เหมือนกันอย่างแน่นอน (ทุกด้านมีผลเหมือนกัน) อย่างน้อยสำหรับ momement แต่สิ่งใดที่คุณจะได้รับนอกเหนือจากศักยภาพที่จะแยกออกจากกัน ความแตกต่างระหว่างสถานการณ์นี้และสิ่งที่คุณอธิบายคือการที่คุณไม่สามารถหลีกเลี่ยงการถูกสามารถในการเข้าถึงรัฐนอก getters / setters แต่คุณสามารถหลีกเลี่ยงการทำเช่นนั้นจริง อย่าทำให้รหัสของคุณซับซ้อนเว้นแต่คุณจะต้อง
jmoreno

1

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

เป็นความรับผิดชอบของคุณในการรักษาสิ่งต่าง ๆ ให้สอดคล้องกัน Setter ทำสิ่งนี้ตามคำจำกัดความ แต่ไม่สามารถครอบคลุมกรณีที่ซับซ้อนได้


1

ฉันจะบอกว่าไม่ ..

หาก getters / setters ของคุณเพิ่งได้รับและตั้งค่า (ไม่มีการเริ่มต้นที่ขี้เกียจไม่มีการตรวจสอบ ฯลฯ ) จากนั้นใช้ตัวแปรของคุณ หากในอนาคตคุณเปลี่ยน getters / setters ของคุณคุณสามารถเปลี่ยนmethodWithLogic()(เพราะชั้นเรียนของคุณเปลี่ยนไป) และคุณสามารถเรียก getter / setter แทนการมอบหมายโดยตรง คุณสามารถบันทึกการเรียกใช้ getter / setter (เพราะจะแปลกที่จะเรียกใช้ getter / setter เมื่อรหัสคลาสที่เหลือของคุณใช้ตัวแปรโดยตรง)

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

หวังว่านี่จะช่วย ..

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