Java - มันเป็นความคิดที่ดีที่จะมีคลาสแบบคงที่?


16

ฉันกำลังทำงานในโครงการเดี่ยวขนาดใหญ่ขึ้นและตอนนี้และฉันมีหลายชั้นเรียนซึ่งฉันไม่เห็นเหตุผลที่จะสร้างตัวอย่างของ

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

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

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


1
หากโปรแกรมของคุณเป็นแบบเต็มขั้นตอน ทำไมคุณถึงเลือกจาวา
Laiv

12
ลองเขียนการทดสอบหน่วยสำหรับชั้นเรียนเหล่านี้และคุณจะเข้าใจว่าทำไมคนไม่ชอบวิธีการคงที่ที่เข้าถึงสถานะคงที่
Joeri Sebrechts

@ Laiv ฉันยังใหม่กับการเขียนโปรแกรมประมาณหนึ่งปีของ C ++ ฉันกำลังเรียน Java ภาคการศึกษานี้และฉันเริ่มชอบ Java มากขึ้นห้องสมุดกราฟิกโดยเฉพาะ
HexTeke


5
เกี่ยวกับการเรียนแบบคงที่ หากวิธีการแบบคงที่บริสุทธิ์ (ไม่ค้างสถานะให้มีอาร์กิวเมนต์ที่ป้อนเข้าและชนิดส่งคืน) ไม่มีอะไรต้องกังวลเกี่ยวกับ
Laiv

คำตอบ:


20

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

หากDice.roll()เพียงแค่ส่งกลับหมายเลขสุ่มใหม่จาก 1 ถึง 6 ก็จะไม่เปลี่ยนสถานะ จริงอยู่ที่คุณอาจจะแบ่งปันRandomอินสแตนซ์ แต่ฉันไม่คิดว่าการเปลี่ยนแปลงของรัฐตามคำนิยามผลลัพธ์จะเป็นไปได้เสมอโดยการสุ่ม นอกจากนี้ยังเป็นเธรดที่ปลอดภัยดังนั้นจึงไม่มีปัญหาที่นี่

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

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


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

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

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

// DON'T DO THIS!
class Singleton {
  private String name; 
  private static Singleton instance = null;

  private Singleton(String name) {
    this.name = name;
  }

  public static Singleton getInstance() {
    if(instance == null) {
      instance = new Singleton("George");
    }
    return instance;
  }

  public getName() {
    return name;
  }
}

assert Singleton.getInstance().getName() == "George"

7
ถ้าฉันต้องการทดสอบว่าจะเกิดอะไรขึ้นเมื่อมีการหมุนหกคู่ฉันติดอยู่ที่นี่เนื่องจากคุณมีคลาสสแตติกที่มีตัวสร้างตัวเลขสุ่มแบบสแตติก ดังนั้นDice.roll()ไม่ใช่ไม่ใช่ข้อยกเว้นที่ถูกต้องสำหรับกฎ "no global state"
David Arno

1
@HexTeke คำตอบล่าสุด
Neil

1
@DavidArno จริง random.nextInt(6) + 1แต่ผมคิดว่าที่จุดที่เราจริงๆในปัญหาถ้าเรากำลังทดสอบสายเดียวที่จะ ;)
Neil

3
@ Neil ขอโทษฉันไม่ได้อธิบายตัวเองดีมาก เราไม่ได้ทดสอบลำดับหมายเลขสุ่มเรามีผลกับลำดับนั้นเพื่อช่วยในการทดสอบอื่น ๆ หากเราทดสอบที่เช่นRollAndMove()ม้วนอีกครั้งเมื่อเราจัดหาสองหกวิธีที่ง่ายที่สุดและแข็งแกร่งที่สุดในการทำเช่นนั้นคือการเยาะเย้ยออกDiceหรือเครื่องกำเนิดตัวเลขแบบสุ่ม ดังนั้นDiceไม่ต้องการเป็นคลาสแบบคงที่โดยใช้ตัวสร้างแบบสุ่มแบบคงที่
David Arno

12
การอัปเดตของคุณจะกระทบกับประเด็นสำคัญ: วิธีการคงที่ควรกำหนดไว้ล่วงหน้า พวกเขาไม่ควรมีผลข้างเคียง
David Arno

9

เพื่อยกตัวอย่างข้อ จำกัด ของstaticคลาสหากผู้เล่นบางคนของคุณต้องการรับโบนัสเล็กน้อยจากการทอยลูกเต๋า และพวกเขายินดีที่จะจ่ายเงินก้อนโต! :-)

ใช่คุณสามารถเพิ่มพารามิเตอร์อื่นดังนั้นDice.roll(bonus),

หลังจากนั้นคุณต้องใช้ D20

Dice.roll(bonus, sides)

ใช่ แต่ผู้เล่นบางคนมีความสามารถ "ยอดเยี่ยม" ดังนั้นพวกเขาจึงไม่สามารถ "คลำ" (ม้วน 1)

Dice.roll(bonus, sides, isFumbleAllowed).

นี่มันยุ่งเหยิงใช่ไหม?


นี่ดูเหมือนจะเป็นคำถามสำหรับคำถามที่ว่ามันยุ่งเหยิงไม่ว่าจะเป็นวิธีการคงที่หรือวิธีการปกติ
jk

4
@ jk ฉันคิดว่าฉันเข้าใจประเด็นของเขา ไม่สมเหตุสมผลที่จะมีคลาสลูกเต๋าแบบคงที่เมื่อคุณคิดในรูปแบบของลูกเต๋าเพิ่มเติม ในกรณีนั้นเราสามารถมีวัตถุลูกเต๋าที่แตกต่างกันสร้างแบบจำลองด้วยวิธีปฏิบัติที่ดีของ OOP
Dherik

1
@ TimothyTruckle ฉันไม่ได้โต้แย้งว่าทั้งหมดที่ฉันพูดคือคำตอบนี้ไม่ได้ตอบคำถามจริงเพราะขอบเขตของการคืบชนิดนี้ไม่มีอะไรเกี่ยวข้องกับวิธีการแบบคงที่หรือไม่
nvoigt

2
@nvoigt "ฉันไม่ได้โต้แย้งว่า" - ดีฉันทำ คุณลักษณะที่มีประสิทธิภาพมากที่สุดของภาษา OO คือความแตกต่าง และการเข้าถึงแบบคงที่ทำให้เราไม่สามารถใช้สิ่งนั้นได้อย่างมีประสิทธิภาพ และคุณกำลังขวา: new Dice().roll(...)จะต้องมีการ concidered เข้าถึงแบบคงที่Dice.roll(...)เช่นเดียวกับ เราได้รับประโยชน์ก็ต่อเมื่อเราฉีดการพึ่งพานั้น
ทิโมธี Truckle

2
@jk ความแตกต่างที่staticเกิดขึ้นกับความยุ่งเหยิงในการโทรทุกครั้งและจำเป็นต้องมีความรู้ที่จำเป็นในการโทรทุกครั้ง ในโครงสร้าง OOP เฉพาะผู้สร้าง / โรงงานเท่านั้นที่ต้องยุ่งและรายละเอียดของแม่พิมพ์นั้นจะถูกห่อหุ้ม roll()หลังจากนั้นการใช้งานหลายรูปแบบและเพียงแค่โทร ฉันอาจแก้ไขคำตอบนี้เพื่อชี้แจง
949300

3

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

หากคุณต้องการหน่วยทดสอบสิ่งที่ใช้อินสแตนซ์ของลูกเต๋า (เช่นคลาสเกม) จากการทดสอบของคุณคุณสามารถฉีดรูปแบบการทดสอบสองเท่าของลูกเต๋าที่ส่งคืนค่าคงที่ตามลำดับ การทดสอบของคุณสามารถตรวจสอบว่าเกมมีผลที่ถูกต้องสำหรับลูกเต๋าเหล่านั้น

ฉันไม่ใช่นักพัฒนา Java แต่ฉันคิดว่ามันจะยากกว่าหากจะทำอย่างนั้นกับคลาสลูกเต๋าแบบคงที่ ดู/programming/4482315/why-does-mockito-not-mock-static-methods


เพียงบันทึก: ใน Java เรามีPowerMockเพื่อแทนที่การพึ่งพาแบบคงที่โดยการจัดการรหัสไบต์ แต่การใช้ก็เป็นเพียงการยอมจำนนในการออกแบบไม่ดี ...
ทิโมธียอมจำนน

0

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

หัวข้อย่อยเล็กน้อย: ฉันไม่ค่อยเห็นด้วยกับการตั้งชื่อของคำหลักใน VisualBasic แต่ในกรณีนี้ฉันคิดว่าsharedแน่นอนชัดเจนและดีกว่าความหมาย (มันถูกใช้ร่วมกันในชั้นเรียนของตัวเองและทุกกรณี) กว่าstatic(ยังคงอยู่หลังจากวงจรชีวิต จากขอบเขตที่ประกาศไว้)

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