ทำให้เชื่องคลาส 'ฟังก์ชันยูทิลิตี้'


15

ใน codebase Java ของเราฉันเห็นรูปแบบต่อไปนี้:

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

สิ่งที่รบกวนจิตใจฉันคือฉันต้องส่งตัวอย่างของFooUtilทุกที่เพราะการทดสอบ

  • ฉันไม่สามารถทำให้FooUtilวิธีการคงที่เพราะฉันจะไม่สามารถเยาะเย้ยพวกเขาสำหรับการทดสอบทั้งFooUtilชั้นเรียนและลูกค้า
  • ฉันไม่สามารถสร้างอินสแตนซ์ของFooUtilสถานที่บริโภคด้วยnewอีกครั้งเพราะฉันจะไม่สามารถเยาะเย้ยมันสำหรับการทดสอบ

ฉันคิดว่าทางออกที่ดีที่สุดของฉันคือการใช้การฉีด (และฉันทำ) แต่มันเพิ่มชุดยุ่งยากของตัวเอง นอกจากนี้การใช้อินสแตนซ์หลายตัวจะขยายขนาดของรายการพารามิเตอร์เมธอด

มีวิธีจัดการกับสิ่งที่ดีกว่าที่ฉันไม่เห็นหรือไม่?

อัปเดต:เนื่องจากคลาสยูทิลิตี้ไร้สัญชาติฉันอาจเพิ่มINSTANCEสมาชิกแบบสแตติกตันหรือgetInstance()วิธีแบบคงที่ในขณะที่ยังคงความสามารถในการอัปเดตฟิลด์สแตติกพื้นฐานของคลาสสำหรับการทดสอบ ดูไม่สะอาดเกินไปเช่นกัน


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

4
ทำไมวิธีการเหล่านี้ไม่ได้เป็นสมาชิกของชั้นเรียนที่มีข้อมูลที่พวกเขาจัดการ?
จูลส์

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

2
+1 สำหรับความคิดเห็น Jules ชั้นเรียนที่มีประโยชน์มีกลิ่นรหัส ขึ้นอยู่กับความหมายควรมีทางออกที่ดีกว่า บางทีFooUtilวิธีการที่ควรจะใส่ลงไปFooSomethingอาจมีคุณสมบัติFooSomethingที่ควรจะเปลี่ยน / ขยายเพื่อให้FooUtilวิธีการนั้นไม่จำเป็นอีกต่อไป โปรดยกตัวอย่างที่เป็นรูปธรรมเกี่ยวFooSomethingกับสิ่งที่จะช่วยเราในการให้คำตอบที่ดีสำหรับคำถามนี้
valenterry

13
@valenterry: เหตุผลเดียวที่ใช้คลาสเรียนคือกลิ่นรหัสเพราะ Java ไม่มีวิธีที่ดีในการมีฟังก์ชั่นแบบสแตนด์อโลน มีเหตุผลที่ดีมากที่มีฟังก์ชั่นที่ไม่เฉพาะเจาะจงกับคลาส
Robert Harvey

คำตอบ:


16

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

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

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

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

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

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

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


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


@Dupuplicator ในรอบ 40 ปีของการเขียนโปรแกรมฉันไม่ค่อยได้เห็นกรณีที่ฉันถูกบล็อกโดยการอ้อมในระดับมากเกินไป (นอกเหนือจากการต่อสู้เป็นครั้งคราวกับ IDE ของฉันเพื่อข้ามไปยังการนำไปใช้มากกว่าอินเตอร์เฟส) แต่ฉัน ' บ่อยครั้งที่พบบ่อยมากผู้คนเขียนโค้ดที่น่ากลัวมากกว่าสร้างคลาสที่ต้องการอย่างชัดเจน แม้ว่าฉันจะเชื่อว่าการมีคนทางอ้อมมากเกินไปอาจกัดคนบางคน แต่ฉันก็ผิดพลาดจากหลักการของ OO ที่เหมาะสมเช่นแต่ละชั้นเรียนทำสิ่งหนึ่งได้ดีการกักกันที่เหมาะสม ฯลฯ
Bill K

@ Billk โดยทั่วไปความคิดที่ดี แต่ถ้าไม่มีช่องโหว่การห่อหุ้มก็สามารถเข้าทางได้ และมีความงามบางอย่างสำหรับอัลกอริทึมฟรีที่คุณสามารถจับคู่กับประเภทใดก็ได้ที่คุณต้องการ แต่ยอมรับในภาษาที่เข้มงวด -OO อะไรก็ได้ แต่ OO ค่อนข้างอึดอัด
Deduplicator

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

1

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

FooUtilProvider จะมีวิธีเดียวเท่านั้น: FooUtils get(); . ชั้นจะใช้FooUtilsอินสแตนซ์อะไรก็ได้ที่มีให้

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

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