มีเหตุผลใดที่จะใช้คลาส "ข้อมูลเก่าแบบธรรมดา" หรือไม่?


43

ในรหัสดั้งเดิมบางครั้งฉันเห็นชั้นเรียนที่ไม่มีอะไรนอกจากห่อหุ้มข้อมูล สิ่งที่ต้องการ:

class Bottle {
   int height;
   int diameter;
   Cap capType;

   getters/setters, maybe a constructor
}

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

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


1
สิ่งนี้ไม่ค่อยตอบคำถามของคุณ แต่ดูเหมือนว่าจะเกี่ยวข้อง: stackoverflow.com/questions/36701/struct-like-objects-in-java
Adam Lear

2
ฉันคิดว่าคำที่คุณกำลังมองหาคือ POD (Plain Old Data)
Gaurav

9
นี่คือตัวอย่างทั่วไปของการเขียนโปรแกรมที่มีโครงสร้าง ไม่จำเป็นต้องไม่ดีเพียงแค่ไม่ได้มุ่งเน้นวัตถุ
Konrad Rudolph

1
ไม่ควรเป็นแบบล้นสแต็กหรือไม่
Muad'Dib

7
@ โมดดิบ: ในทางเทคนิคมันเป็นเรื่องเกี่ยวกับการเขียนโปรแกรม คอมไพเลอร์ของคุณไม่สนใจว่าคุณใช้โครงสร้างข้อมูลธรรมดาแบบเก่าหรือไม่ CPU ของคุณอาจสนุกกับมัน (ใน "ฉันชอบกลิ่นของข้อมูลที่สดใหม่จากแคช" วิธีการ) มันเป็นคนที่ถูกแขวนอยู่บน "สิ่งนี้ทำให้วิธีการของฉันบริสุทธิ์น้อยลงหรือไม่" คำถาม
Shog9

คำตอบ:


67

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

public void DoStuffWithBottle(Bottle b)
{
    // do something that doesn't modify Bottle, so the method doesn't belong
    // on that class
}

กว่า

public void DoStuffWithBottle(int bottleHeight, int bottleDiameter, Cap capType)
{
}

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

นอกจากนี้ยังมีวัตถุข้อมูลธรรมดาที่สามารถส่งคืนเป็นผลมาจากแบบสอบถามฐานข้อมูลตัวอย่างเช่น ฉันเชื่อว่าคำศัพท์เหล่านั้นในกรณีนี้คือ "Data Transfer Object"

ในบางภาษามีข้อควรพิจารณาอื่น ๆ เช่นกัน ตัวอย่างเช่นในคลาส C # และ structs ทำงานแตกต่างกันเนื่องจาก structs เป็นประเภทค่าและคลาสเป็นประเภทอ้างอิง


25
Nah DoStuffWithควรจะมีวิธีการของBottleชั้นใน OOP (และส่วนใหญ่อาจจะไม่เปลี่ยนรูปด้วย) สิ่งที่คุณเขียนไว้ข้างต้นไม่ใช่รูปแบบที่ดีในภาษา OO (เว้นแต่คุณกำลังเชื่อมต่อกับ API ดั้งเดิม) อย่างไรก็ตามมันคือการออกแบบที่ถูกต้องในสภาพแวดล้อมที่ไม่ใช่ OO
Konrad Rudolph

11
@Javier: จากนั้น Java ก็ไม่ใช่คำตอบที่ดีที่สุด Java เน้นเรื่อง OO อย่างล้นหลามภาษานั้นไม่ดีในสิ่งอื่นใด
Konrad Rudolph

11
@ JohnL: แน่นอนว่าฉันทำให้มันง่ายขึ้น แต่โดยทั่วไปวัตถุใน OO จะสรุปสถานะไม่ใช่ข้อมูล นี่คือความแตกต่างที่ดี แต่สำคัญ จุดของ OOP นั้นไม่ได้มีข้อมูลอยู่มากมาย มันกำลังส่งข้อความระหว่างรัฐที่สร้างรัฐใหม่ ฉันไม่เห็นว่าคุณสามารถส่งข้อความไปยังหรือจากวัตถุที่ไม่มีระเบียบได้อย่างไร (และนี่คือคำนิยามดั้งเดิมของ OOP ดังนั้นฉันจะไม่พิจารณาว่ามันมีข้อบกพร่อง)
Konrad Rudolph

13
@ Konrad Rudolph: นี่คือเหตุผลที่ฉันได้แสดงความคิดเห็นอย่างชัดเจนภายในวิธีการ ฉันยอมรับว่าวิธีการที่มีผลต่ออินสแตนซ์ของขวดควรไปในคลาสนั้น แต่ถ้าวัตถุอื่นต้องการแก้ไขสถานะของมันตามข้อมูลในขวดแล้วฉันคิดว่าการออกแบบของฉันจะค่อนข้างถูกต้อง
อดัมเลียร์

10
@ Konrad ฉันไม่เห็นด้วยที่ doStuffWithBottle ควรไปที่คลาสขวด ทำไมขวดควรรู้วิธีการทำสิ่งต่าง ๆ ด้วยตัวเอง doStuffWithBottle ระบุว่ามีสิ่งอื่นที่จะทำอะไรกับขวด หากขวดมีสิ่งนั้นอยู่ในนั้นก็จะเป็นการแต่งงานกัน อย่างไรก็ตามหากคลาสขวดมีวิธี isFull () จะเหมาะสมอย่างยิ่ง
Nemi

25

คลาสข้อมูลถูกต้องในบางกรณี DTO เป็นตัวอย่างที่ดีที่ Anna Lear พูดถึง โดยทั่วไปแล้วคุณควรถือว่าพวกเขาเป็นเมล็ดพันธุ์ของคลาสที่วิธีการยังไม่งอก และหากคุณพบว่ามีโค้ดเก่า ๆ จำนวนมากให้ถือว่ามันเป็นกลิ่นที่รุนแรง พวกเขามักจะถูกใช้โดยโปรแกรมเมอร์ C / C ++ เก่าที่ไม่เคยเปลี่ยนไปใช้การเขียนโปรแกรม OO และเป็นสัญญาณของการเขียนโปรแกรมขั้นตอน การพึ่งพาผู้ได้รับและผู้ตั้งค่าตลอดเวลา (หรือแย่กว่านั้นคือการเข้าถึงสมาชิกที่ไม่ใช่ส่วนตัวโดยตรง) อาจทำให้คุณมีปัญหาก่อนที่คุณจะรู้ Bottleลองพิจารณาตัวอย่างของวิธีการภายนอกที่ต้องการข้อมูลจาก

นี่Bottleคือคลาสข้อมูล):

void selectShippingContainer(Bottle bottle) {
    if (bottle.getDiameter() > MAX_DIMENSION || bottle.getHeight() > MAX_DIMENSION ||
            bottle.getCapType() == Cap.FANCY_CAP ) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

ที่นี่เราได้รับBottleพฤติกรรมบางอย่าง):

void selectShippingContainer(Bottle bottle) {
    if (bottle.isBiggerThan(MAX_DIMENSION) || bottle.isFragile()) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

วิธีแรกละเมิดหลักการ Tell-Don't-Ask และด้วยการทำให้Bottleโง่เราได้ให้ความรู้โดยปริยายเกี่ยวกับขวดเช่นสิ่งที่ทำให้ชิ้นเดียว (the Cap) แตกออกเป็นตรรกะที่อยู่นอกBottleชั้นเรียน คุณต้องอยู่ที่นิ้วเท้าของคุณเพื่อป้องกัน 'การรั่วไหล' แบบนี้เมื่อคุณต้องพึ่งพาผู้ได้รับเป็นประจำ

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

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


1
ไม่อยากเชื่อเลยว่าคำตอบนี้ไม่มีการลงคะแนน (ตอนนี้คุณมีแล้ว) นี่อาจเป็นตัวอย่างง่ายๆ แต่เมื่อ OO ถูกทารุณกรรมเพียงพอคุณจะได้รับคลาสบริการที่กลายเป็นฝันร้ายที่มีฟังก์ชันมากมายที่ควรได้รับการห่อหุ้มในชั้นเรียน
Alb

"โดยโปรแกรมเมอร์ C / C ++ เก่าที่ไม่เคยทำการแปลง (sic) ไปยัง OO"? โปรแกรมเมอร์ C ++ มักจะเป็น OO ที่น่ารักเพราะเป็นภาษา OO นั่นคือจุดรวมของการใช้ C ++ แทน C
nappyfalcon

7

ถ้านี่คือสิ่งที่คุณต้องการมันก็ดี แต่ได้โปรดได้โปรดได้โปรดทำเช่นนั้น

public class Bottle {
    public int height;
    public int diameter;
    public Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }
}

แทนสิ่งที่ชอบ

public class Bottle {
    private int height;
    private int diameter;
    private Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getDiameter() {
        return diameter;
    }

    public void setDiameter(int diameter) {
        this.diameter = diameter;
    }

    public Cap getCapType() {
        return capType;
    }

    public void setCapType(Cap capType) {
        this.capType = capType;
    }
}

โปรด.


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

6
เป็นพื้นที่หนึ่งที่. NET มีขอบเหนือคุณสมบัติเล็กน้อยเนื่องจากคุณสามารถเพิ่มคุณสมบัติการเข้าถึงด้วยตรรกะการตรวจสอบด้วยไวยากรณ์เดียวกันกับถ้าคุณกำลังเข้าถึงฟิลด์ คุณยังสามารถกำหนดคุณสมบัติที่คลาสสามารถรับค่าคุณสมบัติ แต่ไม่ได้ตั้งค่า
JohnL

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

2
ข้อดีของการใช้ Constructor คือมันทำให้การเปลี่ยนคลาสทำได้ง่ายขึ้นมาก นี่เป็นสิ่งสำคัญหากคุณต้องการเขียนโค้ดแบบมัลติเธรด
Fortyrunner

7
ฉันจะทำให้ฟิลด์สุดท้ายเมื่อใดก็ตามที่เป็นไปได้ IMHO ฉันต้องการให้ฟิลด์ที่ต้องการเป็นค่าเริ่มต้นและมีคำหลักสำหรับฟิลด์ที่ไม่แน่นอน เช่น var
Peter Lawrey

6

อย่างที่ @Anna พูดไว้ไม่ใช่ความชั่วร้าย แน่ใจว่าคุณสามารถใส่การดำเนินงาน (วิธีการ) ลงในคลาส แต่ถ้าคุณต้องการเท่านั้น คุณไม่ต้องไป

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

  1. สร้างคลาสมากกว่าที่ต้องการ (โครงสร้างข้อมูลซ้ำซ้อน) เมื่อโครงสร้างข้อมูลมีส่วนประกอบมากกว่าที่จำเป็นน้อยที่สุดจะไม่มีการทำให้เป็นมาตรฐานและดังนั้นจึงมีสถานะไม่สอดคล้องกัน กล่าวอีกนัยหนึ่งเมื่อมีการเปลี่ยนแปลงจะต้องมีการเปลี่ยนแปลงมากกว่าหนึ่งแห่งเพื่อให้สอดคล้องกัน การไม่ประสานการเปลี่ยนแปลงทั้งหมดทำให้ไม่สอดคล้องกันและเป็นจุดบกพร่อง

  2. แก้ไขปัญหาที่ 1 โดยการใส่วิธีการแจ้งเตือนเพื่อที่ว่าหากส่วน A ได้รับการแก้ไขก็จะพยายามเผยแพร่การเปลี่ยนแปลงที่จำเป็นไปยังส่วน B และ C นี่คือเหตุผลหลักว่าทำไมมันจึงแนะนำให้มีวิธี accessor รับและตั้งค่า เนื่องจากนี่เป็นแนวทางปฏิบัติที่แนะนำดูเหมือนว่าจะแก้ปัญหา 1 ทำให้เกิดปัญหามากขึ้น 1 และแก้ปัญหาเพิ่มเติม 2 ผลลัพธ์นี้ไม่เพียง แต่ในข้อบกพร่องเนื่องจากการใช้การแจ้งเตือนที่ไม่สมบูรณ์ แต่เกิดจากปัญหาการเพิ่มประสิทธิภาพของการแจ้งเตือน สิ่งเหล่านี้ไม่ใช่การคำนวณแบบไม่มีที่สิ้นสุด แต่มีความยาวมาก

แนวคิดเหล่านี้ได้รับการสอนว่าเป็นสิ่งที่ดีโดยทั่วไปโดยครูที่ไม่ต้องทำงานภายในแอพสัตว์ประหลาดนับล้านเส้นที่เต็มไปด้วยปัญหาเหล่านี้

นี่คือสิ่งที่ฉันพยายามทำ:

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

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


3

คลาสประเภทนี้มีประโยชน์มากเมื่อคุณใช้งานแอพพลิเคชั่นขนาดกลาง / ใหญ่ด้วยเหตุผลบางประการ:

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

ดังนั้นโดยสรุปในประสบการณ์ของฉันพวกเขามักจะมีประโยชน์มากกว่าน่ารำคาญ


3

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


3

เห็นด้วยกับ Anna Lear

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

บางครั้งผู้คนก็ลืมอ่านข้อกำหนดการเข้ารหัส Java 1999 ซึ่งทำให้มันชัดเจนว่าการเขียนโปรแกรมประเภทนี้ดีมาก ในความเป็นจริงถ้าคุณหลีกเลี่ยงมันแล้วรหัสของคุณจะได้กลิ่น! (มากเกินไป getters / setters)

จาก Java Code Conventions 1999: ตัวอย่างหนึ่งของตัวแปรอินสแตนซ์สาธารณะที่เหมาะสมคือกรณีที่คลาสนั้นเป็นโครงสร้างข้อมูลโดยไม่มีพฤติกรรม กล่าวอีกนัยหนึ่งถ้าคุณจะใช้ struct แทนที่จะเป็นคลาส (ถ้า Java สนับสนุน struct) ดังนั้นจึงเหมาะสมที่จะทำให้ตัวแปรอินสแตนซ์ของคลาสเป็นแบบสาธารณะ http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-137265.html#177

เมื่อใช้อย่างถูกต้อง PODs (โครงสร้างข้อมูลเก่าแบบธรรมดา) จะดีกว่า POJO เช่นเดียวกับ POJO มักจะดีกว่า EJB
http://en.wikipedia.org/wiki/Plain_Old_Data_Structures


3

โครงสร้างมีสถานที่ของพวกเขาแม้ใน Java คุณควรใช้พวกมันก็ต่อเมื่อสองสิ่งต่อไปนี้เป็นจริง:

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

หากเป็นกรณีนี้คุณควรกำหนดให้ฟิลด์นั้นเป็นสาธารณะและข้ามตัวรับสัญญาณ / ตัวตั้งค่า Getters และ setters เป็น clunky อยู่แล้วและ Java เป็นคนโง่ที่ไม่มีคุณสมบัติเช่นภาษาที่มีประโยชน์ เนื่องจากวัตถุที่มีโครงสร้างเหมือนของคุณไม่ควรมีวิธีการใด ๆ อยู่แล้วเขตข้อมูลสาธารณะจึงเหมาะสมที่สุด

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

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

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

ตัวอย่างขวดของคุณน่าจะล้มเหลวในการทดสอบทั้งคู่ คุณสามารถมีรหัส (contrived) ที่มีลักษณะดังนี้:

public double calculateVolumeAsCylinder(Bottle bottle) {
    return bottle.height * (bottle.diameter / 2.0) * Math.PI);
}

แต่มันควรจะเป็น

double volume = bottle.calculateVolumeAsCylinder();

ถ้าคุณเปลี่ยนความสูงและเส้นผ่าศูนย์กลางมันจะเป็นขวดเดียวกันหรือไม่? อาจจะไม่. สิ่งเหล่านั้นควรเป็นที่สิ้นสุด ค่าลบตกลงสำหรับเส้นผ่านศูนย์กลางหรือไม่ ขวดของคุณจะต้องสูงกว่าความกว้างหรือไม่ หมวกสามารถเป็นโมฆะได้หรือไม่ ไม่มี? คุณตรวจสอบเรื่องนี้อย่างไร สมมติว่าลูกค้านั้นโง่หรือชั่ว ( เป็นไปไม่ได้ที่จะบอกความแตกต่าง ) คุณต้องตรวจสอบค่าเหล่านี้

นี่คือลักษณะคลาสขวดใหม่ของคุณ:

public class Bottle {

    private final int height, diameter;

    private Cap capType;

    public Bottle(final int height, final int diameter, final Cap capType) {
        if (diameter < 1) throw new IllegalArgumentException("diameter must be positive");
        if (height < diameter) throw new IllegalArgumentException("bottle must be taller than its diameter");

        setCapType(capType);
        this.height = height;
        this.diameter = diameter;
    }

    public double getVolumeAsCylinder() {
        return height * (diameter / 2.0) * Math.PI;
    }

    public void setCapType(final Cap capType) {
        if (capType == null) throw new NullPointerException("capType cannot be null");
        this.capType = capType;
    }

    // potentially more methods...

}

0

IMHO มักจะมีคลาสไม่เพียงพอเช่นนี้ในระบบเชิงวัตถุ ฉันต้องมีคุณสมบัติอย่างรอบคอบว่า

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

แต่มีหลายกรณีที่เขตข้อมูลดังกล่าวจะมีขอบเขตที่แคบมาก ตัวอย่างที่ตรงไปตรงมามากคือส่วนตัวNodeคลาสของโครงสร้างข้อมูล บ่อยครั้งที่มันสามารถลดความซับซ้อนของรหัสได้อย่างมากโดยการลดจำนวนการโต้ตอบของวัตถุที่เกิดขึ้นหากกล่าวว่าNodeอาจประกอบด้วยข้อมูลดิบ ที่ทำหน้าที่เป็นกลไก decoupling ตั้งแต่รุ่นทางเลือกที่อาจจำเป็นต้องมีเพศสัมพันธ์แบบสองทิศทางจากการพูดTree->Nodeและเมื่อเทียบกับเพียงNode->TreeTree->Node Data

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

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

ป้อนคำอธิบายรูปภาพที่นี่

... ตรงข้ามกับสิ่งนี้:

ป้อนคำอธิบายรูปภาพที่นี่

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


-1

มีสิ่งมีชีวิตที่แปลกประหลาดมากมายในโลกของ OOP ฉันเคยสร้างชั้นเรียนซึ่งเป็นนามธรรมและไม่มีอะไรเลยมันเป็นเพียงการเป็นผู้ปกครองร่วมกันของอีกสองชั้น ... มันเป็นชั้นพอร์ต:

SceneMember 
Message extends SceneMember
Port extends SceneMember
InputPort extends Port
OutputPort extends Port

ดังนั้น SceneMember เป็นคลาสพื้นฐานมันทำงานทั้งหมดซึ่งต้องปรากฏบนฉาก: เพิ่ม, ลบ, รับ ID ตั้ง ฯลฯ ข้อความคือการเชื่อมต่อระหว่างพอร์ตมันมีชีวิตของตัวเอง InputPort และ OutputPort มีฟังก์ชั่นเฉพาะของ i / o ของพวกเขาเอง

พอร์ตว่างเปล่ามันใช้เพื่อจัดกลุ่ม InputPort และ OutputPort ร่วมกันเพื่อพูดรายการพอร์ต มันว่างเปล่าเพราะ SceneMember มีทุกสิ่งที่ทำหน้าที่เป็นสมาชิกและ InputPort และ OutputPort มีงานที่ระบุพอร์ต InputPort และ OutputPort มีความแตกต่างกันซึ่งไม่มีฟังก์ชั่นทั่วไป (ยกเว้น SceneMember) ดังนั้นพอร์ตว่างเปล่า แต่มันถูกกฎหมาย

บางทีมันอาจเป็นปฏิปักษ์แต่ฉันชอบมัน

(มันเหมือนกับคำว่า "เพื่อน" ซึ่งใช้สำหรับทั้ง "ภรรยา" และ "สามี" คุณไม่เคยใช้คำว่า "เพื่อน" สำหรับคนที่เป็นรูปธรรมเพราะคุณรู้ว่าเพศของเขา / เธอและมันไม่สำคัญ ถ้าเขา / เธอแต่งงานหรือไม่คุณใช้ "คน" แทน แต่ก็ยังมีอยู่และใช้ในสถานการณ์ที่หายากและเป็นนามธรรมเช่นข้อความกฎหมาย)


1
เหตุใดพอร์ตของคุณจึงจำเป็นต้องขยาย SceneMember ทำไมไม่สร้างคลาสพอร์ตให้ทำงานบน SceneMembers
Michael K

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

1
@Michael: สำหรับเหตุผลทางทฤษฎีเท่านั้นฉันจองพอร์ตสำหรับใช้ในอนาคต แต่ในอนาคตยังไม่มาถึงและอาจจะไม่เคย ฉันไม่ได้ตระหนักว่าพวกเขาไม่มีอะไรเหมือนกันเลยไม่เหมือนกับชื่อของพวกเขา ฉันจะจ่ายค่าชดเชยให้กับทุกคนที่ประสบความสูญเสียที่เกิดขึ้นจากชั้นว่างนั้น ...
ern0

1
@TMN: ประเภท SceneMember (อนุพันธ์) มีวิธี getMemberType () มีหลายกรณีเมื่อสแกนซีนภาพสำหรับชุดย่อยของวัตถุ SceneMember
ern0

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