โรงงานคงเทียบกับโรงงานเป็นซิงเกิล


24

ในบางรหัสของฉันฉันมีโรงงานแบบคงที่เช่นนี้:

public class SomeFactory {

    // Static class
    private SomeFactory() {...}

    public static Foo createFoo() {...}

    public static Foo createFooerFoo() {...}
}

ในระหว่างการตรวจสอบรหัสมันก็เสนอว่าควรจะเป็นซิงเกิลตันและฉีด ดังนั้นควรมีลักษณะดังนี้:

public class SomeFactory {

    public SomeFactory() {}

    public Foo createFoo() {...}

    public Foo createFooerFoo() {...}
}

บางสิ่งที่ควรเน้น:

  • โรงงานทั้งสองไร้สัญชาติ
  • ข้อแตกต่างระหว่างเมธอดคือขอบเขต (อินสแตนซ์ vs คงที่) การใช้งานเหมือนกัน
  • Foo เป็นถั่วที่ไม่มีส่วนต่อประสาน

ข้อโต้แย้งที่ฉันได้รับจากการคงที่คือ:

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

ข้อโต้แย้งสำหรับโรงงานในฐานะซิงเกิลคือ:

  • มันเป็นการดีที่จะฉีดทุกอย่าง
  • แม้จะไร้สัญชาติของโรงงานการทดสอบก็ง่ายขึ้นด้วยการฉีด (ง่ายต่อการเยาะเย้ย)
  • มันควรจะล้อเลียนเมื่อทดสอบผู้บริโภค

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

ชุมชนคิดอย่างไร ในขณะที่ฉันไม่ชอบวิธีการเดี่ยว แต่ฉันไม่ได้มีข้อโต้แย้งที่แข็งแกร่งมากกับมัน


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

ดังนั้นคุณจึงเรียกร้องให้มีการใช้งานยูทิลิตี้คงที่ทั่วไปหรือเพียงแค่โรงงานคงที่?
bstempi

1
ฉันสนับสนุนพวกเขาโดยทั่วไป ในตัวอย่าง C # DateTimeและFileคลาสนั้นยากที่จะทดสอบด้วยเหตุผลเดียวกันทั้งหมด หากคุณมีคลาสเช่นที่กำหนดCreatedวันที่DateTime.Nowใน Constructor คุณจะสร้างการทดสอบหน่วยด้วยวัตถุเหล่านี้สองตัวที่ถูกสร้างขึ้นจากกันได้อย่างไร 5 นาที สิ่งที่เกี่ยวกับปีออกจากกัน? คุณไม่สามารถทำได้ (โดยไม่ต้องทำงานมาก)
ไมค์

2
โรงงานซิงเกิลของคุณไม่ควรมีคอนprivateสตรัคเตอร์และgetInstance()วิธีการหรือไม่? ขออภัยไม่มีตัวเลือก nit-สมควร!
TMN

1
@Mike - ตกลง - ฉันมักจะใช้คลาส DateTimeProvider ซึ่งส่งกลับ DateTime เล็กน้อยตอนนี้ จากนั้นคุณสามารถสลับเป็นเยาะเย้ยซึ่งคืนเวลาที่กำหนดไว้ล่วงหน้าในการทดสอบของคุณ มันเป็นงานพิเศษที่ส่งผ่านมันให้กับคอนสตรัคเตอร์ทุกคน - อาการปวดหัวที่ใหญ่ที่สุดคือเมื่อเพื่อนร่วมงานไม่ซื้อมัน 100% และส่งการอ้างอิงที่ชัดเจนใน DateTime ออกตอนนี้เพราะความเกียจคร้าน ... :)
Julia Hayward

คำตอบ:


15

ทำไมคุณต้องแยกโรงงานของคุณออกจากประเภทวัตถุที่สร้างขึ้น

public class Foo {

    // Private constructor
    private Foo(...) {...}

    public static Foo of(...) { return new Foo(...); }
}

นี่คือสิ่งที่ Joshua Bloch อธิบายไว้ในข้อ 1 ของเขาในหน้า 5 ของหนังสือ "Effective Java" Java นั้นเพียงพอแล้วโดยไม่ต้องเพิ่มคลาสพิเศษและซิงเกิลตันและอะไรอื่นอีก มีบางกรณีที่คลาสโรงงานแยกต่างหากเหมาะสม แต่มีอยู่ไม่มาก

ในการถอดความรายการที่ Joshua Bloch 1 ซึ่งต่างจากตัวสร้างวิธีการคงที่ของโรงงานสามารถ:

  • มีชื่อที่สามารถอธิบายประเภทการสร้างวัตถุเฉพาะ (Bloch ใช้ probablePrime (int, int, Random) เป็นตัวอย่าง)
  • ส่งคืนวัตถุที่มีอยู่ (คิดว่า: flyweight)
  • ส่งคืนชนิดย่อยของคลาสดั้งเดิมหรืออินเทอร์เฟซที่ใช้
  • ลดความฟุ่มเฟื่อย (จากการระบุพารามิเตอร์ประเภท - ดู Bloch ตัวอย่าง)

ข้อเสีย:

  • คลาสที่ไม่มี public หรือ constructor ที่ได้รับการป้องกันไม่สามารถ subclassed (แต่คุณสามารถใช้ constructors ที่ได้รับการป้องกันและ / หรือรายการที่ 16: คอมโพสิตที่โปรดปรานมากกว่าการสืบทอด)
  • วิธีการที่คงที่จากโรงงานไม่โดดเด่นจากวิธีการคงที่อื่น ๆ (ใช้ชื่อมาตรฐานเช่น () หรือ valueOf ())

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


ฉันก็คิดเกี่ยวกับสิ่งนี้เช่นกันและฉันก็ชอบ ฉันจำไม่ได้ว่าทำไมฉันถึงแยกพวกเขาตั้งแต่แรก
bstempi

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

แต่ ... มันช่างน่ากลัว คุณน่าจะต้องการคลาสที่ใช้ IFoo และผ่านไปแล้วการใช้รูปแบบนี้ก็เป็นไปไม่ได้อีกต่อไป คุณต้องฮาร์ดโค้ด IFoo หมายถึง Foo เพื่อรับอินสแตนซ์ หากคุณมี IFooFactory ที่มี IFoo create () รหัสลูกค้าไม่จำเป็นต้องทราบรายละเอียดการใช้งานซึ่ง Concrete Foo ได้รับเลือกให้เป็น IFoo
Wilbert

@ Wilbert public static IFoo of(...) { return new Foo(...); } ปัญหาคืออะไร โบลชแสดงรายการที่คุณกังวลว่าเป็นหนึ่งในข้อได้เปรียบของการออกแบบนี้ ฉันต้องไม่เข้าใจความคิดเห็นของคุณ
GlenPeterson

เพื่อสร้างอินสแตนซ์การออกแบบนี้คาดว่า: Foo.of (... ) อย่างไรก็ตามการมีอยู่ของ Foo ไม่ควรถูกเปิดเผยมีเพียง IFoo เท่านั้นที่ควรรู้ (เนื่องจาก Foo เป็นเพียงนัยของ IFoo ที่เป็นรูปธรรม) การมีวิธีการโรงงานแบบคงที่บนพื้นคอนกรีตจะทำให้สิ่งนี้แตกหักและนำไปสู่การมีเพศสัมพันธ์ระหว่างรหัสลูกค้าและการใช้งานที่เป็นรูปธรรม
Wilbert

5

เพียงแค่ด้านบนของหัวของฉันนี่คือปัญหาบางอย่างกับสถิตศาสตร์ใน Java:

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

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

  • เขตข้อมูลสถิตค่อนข้างคล้ายกับกลม


คุณพูดถึง:

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

ซึ่งทำให้ฉันอยากถามคุณ:

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

เฮ้ขอบคุณสำหรับคำตอบ! ตามลำดับที่คุณถาม: (1) ข้อดีของวิธีการแบบคงที่คือน้ำตาลเชิงซ้อนและการขาดอินสแตนซ์ที่ไม่จำเป็น มันดีที่จะอ่านโค้ดที่มีการนำเข้าแบบสแตติกและพูดอะไรFoo f = createFoo();มากกว่าที่จะมีอินสแตนซ์ที่มีชื่อ อินสแตนซ์ของตัวเองไม่มียูทิลิตี้ (2) ฉันคิดอย่างนั้น มันถูกออกแบบมาเพื่อเอาชนะความจริงที่ว่าวิธีการเหล่านั้นไม่ได้อยู่ในStringชั้นเรียน การแทนที่บางสิ่งบางอย่างในระดับพื้นฐานเป็นStringไม่ใช่ตัวเลือกดังนั้นสิ่งที่เป็นไปได้ (ต่อ)
bstempi

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

3
พวกเขาไม่ได้เยาะเย้ยStringUtilsเพราะมีความเชื่อมั่นอย่างสมเหตุสมผลว่าวิธีการที่ไม่มีผลข้างเคียงใด ๆ
Robert Harvey

@RobertHarvey ฉันคิดว่าฉันทำเรื่องเดียวกันกับโรงงานของฉัน - มันไม่ได้แก้ไขอะไรเลย เช่นเดียวกับที่StringUtilsคืนผลลัพธ์บางอย่างโดยไม่เปลี่ยนอินพุตโรงงานของฉันก็ไม่ได้แก้ไขอะไรเลย
bstempi

@bstempi ฉันขอใช้วิธีการแบบเสครา ฉันเดาว่าฉันดูดมัน

0

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

ลองนึกภาพว่าแอปพลิเคชันของคุณวิวัฒนาการและตอนนี้ในบางกรณีคุณจำเป็นต้องสร้างFooBar(คลาสย่อยของ Foo) ตอนนี้คุณต้องไปผ่านทุกสถานที่ในรหัสของคุณที่รู้เกี่ยวกับคุณSomeFactoryและเขียนชนิดของตรรกะบางอย่างที่มีลักษณะที่someConditionและบริการโทรหรือSomeFactory SomeOtherFactoryที่จริงแล้วเมื่อดูที่โค้ดตัวอย่างของคุณมันแย่กว่านั้น คุณวางแผนที่จะเพิ่มเพียงวิธีหลังจากวิธีการสร้าง Foos ที่แตกต่างกันและรหัสลูกค้าทั้งหมดของคุณจะต้องตรวจสอบเงื่อนไขก่อนที่จะหาว่า Foo ตัวไหนจะทำ ซึ่งหมายความว่าลูกค้าทุกคนจำเป็นต้องมีความรู้ที่ลึกซึ้งเกี่ยวกับวิธีการทำงานของโรงงานของคุณและพวกเขาทั้งหมดต้องเปลี่ยนเมื่อใดก็ตามที่จำเป็นต้องมี Foo ใหม่ (หรือลบออก!)

ตอนนี้คิดว่าคุณเพียงแค่สร้างที่ทำให้IFooFactory IFooตอนนี้การผลิตคลาสย่อยที่แตกต่างกันหรือแม้กระทั่งการใช้งานที่แตกต่างกันโดยสิ้นเชิงก็คือการเล่นของเด็ก ๆ - คุณเพียงป้อน IFooFactory ที่ถูกต้องให้สูงขึ้นในห่วงโซ่แล้วเมื่อลูกค้าได้รับมันก็จะเรียกgimmeAFoo()และจะได้รับ .

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

คำถามสามารถนำเสนอในคำแนะนำหรือแบบฝึกหัด (ซึ่งทั้งคู่นำไปใช้IContentBlock) ดังนั้นแต่ละคำถามInstructionsFactoryหรือExerciseFactoryจะได้รับสำเนาของ QuestionFactoryMap จากนั้นจะมีการส่งคำแนะนำและโรงงานการออกกำลังกายต่างๆContentBlockBuilderซึ่งจะเป็นการแฮชที่จะใช้เมื่อใด และเมื่อโหลด XML แล้ว ContentBlockBuilder จะเรียกใช้แบบฝึกหัดหรือคำแนะนำจากโรงงานและขอให้ทำcreateContent()แล้วโรงงานจะมอบหมายการสร้างคำถามให้กับสิ่งที่มันดึงออกมาจากภายใน QuestionFactoryMap อยู่ในแต่ละโหนด XML เทียบกับแฮช น่าเสียดายที่สิ่งนั้นเป็นสิ่งBaseQuestionแทนIQuestionแต่นั่นก็แสดงให้เห็นว่าแม้ในขณะที่คุณระมัดระวังในการออกแบบคุณสามารถทำผิดพลาดที่สามารถหลอกหลอนคุณได้

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


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

นอกจากนี้คุณคาดเดาเกี่ยวกับแอปพลิเคชันจะสวย แอปพลิเคชันมีส่วนร่วมอย่างเป็นธรรม นี้Fooแต่เป็นมากง่ายกว่าหลายชั้นเรียน มันเป็นเพียง POJO ง่าย ๆ
bstempi

ถ้า Foo ตั้งใจที่จะขยายออกไปนั่นหมายความว่ามีเหตุผลที่ Factory จะเป็นอินสแตนซ์ - คุณให้อินสแตนซ์ที่ถูกต้องของโรงงานเพื่อให้คลาสย่อยที่ถูกต้องแทนที่จะเรียกif (this) then {makeThisFoo()} else {makeThatFoo()}รหัสทั้งหมดของคุณ สิ่งหนึ่งที่ฉันสังเกตเห็นเกี่ยวกับคนที่มีสถาปัตยกรรมไม่ดีเรื้อรังคือพวกเขาเหมือนคนที่มีอาการปวดเรื้อรัง จริงๆแล้วพวกเขาไม่รู้ว่ามันรู้สึกอย่างไรที่จะไม่เจ็บปวดดังนั้นความเจ็บปวดที่อยู่ในนั้นก็ไม่เลวเลย
Amy Blankenship

นอกจากนี้คุณอาจต้องการตรวจสอบลิงค์นี้เกี่ยวกับปัญหาด้วยวิธีการคงที่ (แม้แต่คณิตศาสตร์!)
Amy Blankenship

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