ตัวสร้างและวิธีการของโรงงาน [ปิด]


181

เมื่อสร้างโมเดลคลาสอะไรที่เป็นวิธีที่ดีที่สุดในการเริ่มต้น:

  1. ตัวสร้างหรือ
  2. วิธีการของโรงงาน

และอะไรคือข้อควรพิจารณาในการใช้ทั้งสองอย่าง?

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

บอกว่าฉันมีนวกรรมิกในคลาสที่คาดว่าจะมีค่า id ตัวสร้างใช้ค่านี้เพื่อเติมคลาสจากฐานข้อมูล ในกรณีที่ไม่มีเร็กคอร์ดที่มี id ที่ระบุตัวสร้างจะสร้าง RecordNotFoundException ในกรณีนี้ฉันจะต้องล้อมรอบการสร้างคลาสดังกล่าวทั้งหมดภายใน try..catch block

ตรงกันข้ามกับที่ฉันสามารถมีวิธีการโรงงานคงที่ในชั้นเรียนเหล่านั้นซึ่งจะกลับเป็นโมฆะถ้าไม่พบบันทึก

ในกรณีนี้วิธีการที่ดีกว่าวิธีการก่อสร้างหรือโรงงาน?

คำตอบ:


66

จากหน้า 108 ของรูปแบบการออกแบบ: องค์ประกอบของซอฟต์แวร์เชิงวัตถุที่ใช้ซ้ำได้โดย Gamma, Helm, Johnson และ Vlissides

ใช้รูปแบบวิธีการจากโรงงานเมื่อ

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

21
วิธีโรงงานแบบคงที่จะแตกต่างจากรูปแบบการออกแบบ GoF - รูปแบบวิธีโรงงาน stackoverflow.com/questions/929021/…
Sree Rama

โปรดอย่าเปรียบเทียบกับรูปแบบวิธีการโรงงานของรูปแบบการออกแบบ GoF
Sree พระราม

137
นี้จะอธิบายอะไรฉัน
Sushant

@ ทำไมจึงเป็นเช่นนั้น?
PaulD

2
คำตอบนี้ไม่ตอบคำถามเพียงแค่ถ่ายทอดข้อมูลเพื่ออ่านเพื่อทำความเข้าใจ / อธิบายแนวคิด ... นี่เป็นความคิดเห็นที่มากขึ้น
Crt

202

ถามตัวเองว่าพวกเขาคืออะไรและทำไมเราถึงมีพวกเขา พวกเขาทั้งสองอยู่ที่นั่นเพื่อสร้างตัวอย่างของวัตถุ

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

ยังไม่มีความแตกต่าง ตอนนี้ลองนึกภาพว่าเรามีโรงเรียนหลายประเภทและเราต้องการเปลี่ยนจากการใช้ ElementarySchool เป็น HighSchool (ซึ่งได้มาจาก ElementarySchool หรือใช้อินเทอร์เฟซเดียวกันกับ ISchool เป็น ElementarySchool) การเปลี่ยนรหัสจะเป็น:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

ในกรณีของอินเตอร์เฟสเราจะมี:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

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

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

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

ตอนนี้คุณมีที่เดียวในแอปของคุณที่มีตรรกะทางธุรกิจที่กำหนดว่าวัตถุ ISchool ใดจะยกตัวอย่างสำหรับวัตถุ IStudent ที่แตกต่างกัน

ดังนั้น - สำหรับคลาสที่เรียบง่าย (วัตถุที่มีค่า ฯลฯ ) คอนสตรัคเตอร์นั้นใช้ได้ (คุณไม่ต้องการ overengineer แอปพลิเคชันของคุณ) แต่สำหรับเมธอดคลาสโรงงานที่ซับซ้อนเป็นวิธีที่ต้องการ

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


2
แม้ว่าคุณจะคิดว่าเป็นคลาสที่เรียบง่าย แต่ก็มีโอกาสที่บางคนต้องการที่จะขยายชั้นเรียนแบบธรรมดาของคุณดังนั้นวิธีการจากโรงงานก็ยังดีกว่า เช่นคุณอาจเริ่มต้นด้วย ElementarySchool แต่ต่อมามีบางคน (รวมถึงตัวคุณเอง) อาจขยายมันด้วย PrivateElementarySchool และ PublicElementarySchool
แจ็ค

10
นี่ควรเป็นคำตอบที่ได้รับการยอมรับ
am05mhz

2
@ David เป็นคำตอบที่ดี แต่คุณสามารถขยายตัวอย่างที่การใช้อินเทอร์เฟซแต่ละครั้งอาจต้องการพารามิเตอร์ที่แตกต่างกันสำหรับการก่อสร้าง นี่เป็นตัวอย่างที่โง่เง่า: IFood sandwich = new Sandwich(Cheese chz, Meat meat);และIFood soup = new Soup(Broth broth, Vegetable veg);โรงงานและหรือผู้สร้างสามารถช่วยได้อย่างไร
Brian

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

ทำไมคำตอบนี้ถึงไม่ยอมรับ
โทมัส

74

คุณจะต้องอ่าน (ถ้าคุณมีการเข้าถึง) มีผลบังคับใช้ Java 2 รายการที่ 1: พิจารณาวิธีการโรงงานคงแทนการก่อสร้าง

ข้อดีวิธีการโรงงานคงที่:

  1. พวกเขามีชื่อ
  2. ไม่จำเป็นต้องสร้างวัตถุใหม่ทุกครั้งที่มีการเรียกใช้
  3. พวกเขาสามารถส่งคืนวัตถุประเภทย่อยใด ๆ ของประเภทการส่งคืนของพวกเขา
  4. พวกเขาลดความละเอียดของการสร้างอินสแตนซ์ชนิดพารามิเตอร์

ข้อเสียวิธีการโรงงานแบบคงที่:

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

4
ดูเหมือนว่าฉันจะเป็นข้อบกพร่องที่รุนแรงใน Java จากนั้นเป็นปัญหาทั่วไปของ OOD มีภาษา OO จำนวนมากที่ไม่ได้อยู่ที่มีการก่อสร้าง subclassing ทำงานได้ดี แต่
Jörg W Mittag

1
@cherouvim ทำไมรหัสส่วนใหญ่ถูกเขียนโดยใช้ Constructors ถ้า( factory methods are better than Constructors. ( Item-1 ) ) Effective java
Asif Mushtaq

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

30

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

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


17
ดังนั้น (สไตล์ TDD) คุณจะเริ่มจากตัวสร้างเป็นวิธีที่ง่ายที่สุดในการทำให้งานเสร็จ และจากนั้น refactor ไปยังโรงงานเมื่อคุณเริ่มรับกลิ่นโค้ด (เช่นตรรกะตามเงื่อนไขซ้ำ ๆ เพื่อพิจารณาว่านวกรรมิกตัวไหนจะเรียก)
AndyM

1
จุดสำคัญมาก การศึกษาการใช้เปรียบเทียบโรงงานและก่อสร้างพบผลลัพธ์ที่สำคัญอย่างมากแสดงให้เห็นว่าโรงงานเป็นอันตรายต่อ API การใช้งาน: "ผู้ใช้ต้องใช้เวลาอย่างมีนัยสำคัญ (p = 0.005) ในการสร้างวัตถุที่มีโรงงานกว่ากับผู้สร้าง" [รูปแบบการโรงงานใน API การออกแบบ : การประเมินการใช้งาน ]
mdeff

12

ใช้โรงงานเฉพาะเมื่อคุณต้องการการควบคุมพิเศษด้วยการสร้างวัตถุในวิธีที่ไม่สามารถทำได้กับตัวสร้าง

โรงงานมีความเป็นไปได้ของการแคชเช่น

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


11

ข้อความอ้างอิงจาก "Effective Java", 2nd ed., Item 1: พิจารณาวิธีการคงที่จากโรงงานแทนที่จะเป็น constructors, p. 5:

"โปรดทราบว่าวิธีโรงงานแบบคงที่นั้นไม่เหมือนกับรูปแบบวิธีโรงงานจากรูปแบบการออกแบบ [Gamma95, หน้า 107] วิธีโรงงานแบบคงที่ที่อธิบายไว้ในรายการนี้ไม่มีเทียบเท่าโดยตรงในรูปแบบการออกแบบ"


10

นอกจาก"java ที่มีประสิทธิภาพ" (ดังที่ได้กล่าวไว้ในคำตอบอื่น) หนังสือคลาสสิกเล่มอื่นยังแนะนำ:

ชอบวิธีการของโรงงานแบบคงที่ (มีชื่อที่อธิบายถึงข้อโต้แย้ง) เพื่อตัวสร้างมากเกินไป

เช่น. อย่าเขียน

Complex complex = new Complex(23.0);

แต่แทนที่จะเขียน

Complex complex = Complex.fromRealNumber(23.0);

หนังสือเล่มนี้แนะนำให้สร้างคอนComplex(float)สตรัคเตอร์ให้เป็นส่วนตัวเพื่อบังคับให้ผู้ใช้เรียกวิธีการสร้างโรงงานแบบคงที่


2
การอ่านส่วนนั้นของหนังสือทำให้ฉันมาที่นี่
Purple Haze

1
@Bayrem: ฉันด้วยฉันเพิ่งอ่านมันอีกครั้งเมื่อเร็ว ๆ นี้และคิดว่าฉันควรจะเพิ่มเข้าไปในคำตอบ
blue_note

1
ในบันทึกที่เกี่ยวข้องคุณอาจพบว่าเป็นประโยชน์บางอย่างการตั้งชื่ออนุสัญญาทำงานออกโดยjava.timeกรอบเกี่ยวกับการตั้งชื่อfrom…, to…, parse…, with…และอื่น ๆ โปรดทราบว่าคลาสjava.timeถูกสร้างขึ้นให้ไม่เปลี่ยนรูป แต่บางส่วนของหลักการตั้งชื่อเหล่านี้อาจเป็นประโยชน์ในการติดตามคลาสที่ไม่แน่นอนเช่นกัน
Basil Bourque

7

ตัวอย่างที่ชัดเจนจากแอปพลิเคชัน CAD / CAM

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

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


5

บอกว่าฉันมีนวกรรมิกในคลาสที่คาดว่าจะมีค่า id ตัวสร้างใช้ค่านี้เพื่อเติมคลาสจากฐานข้อมูล

กระบวนการนี้ควรอยู่นอก Constructor

  1. ตัวสร้างไม่ควรเข้าถึงฐานข้อมูล

  2. งานและสาเหตุของการสร้างคือการเริ่มต้นข้อมูลสมาชิกและสร้างชั้นคงที่โดยใช้ค่าที่ส่งผ่านไปยังตัวสร้าง

  3. สำหรับทุกอย่างอื่นเป็นวิธีการที่ดีคือการใช้วิธีการโรงงานแบบคงที่หรือในกรณีที่ซับซ้อนมากขึ้นแยกโรงงานหรือสร้างระดับ

ตัวสร้างเส้นนำบางส่วนจาก Microsoft :

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

และ

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


2

บางครั้งคุณต้องตรวจสอบ / คำนวณค่า / เงื่อนไขบางอย่างในขณะที่สร้างวัตถุ และถ้ามันสามารถโยน Exception ได้ - constructro นั้นเป็นวิธีที่แย่มาก ดังนั้นคุณต้องทำสิ่งนี้:

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

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

มีคนบอกคุณเกี่ยวกับการแคช ดี. แต่คุณต้องจำเกี่ยวกับรูปแบบ Flyweight ซึ่งเป็นสิ่งที่ดีที่จะใช้กับวิธีโรงงาน

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