มีสิ่งหนึ่งใน C ++ ที่ทำให้ฉันรู้สึกไม่สบายตัวเป็นเวลานานเพราะฉันไม่รู้วิธีการทำอย่างสุจริตแม้ว่ามันจะฟังดูง่าย:
ฉันจะใช้วิธีการโรงงานใน C ++ ได้อย่างถูกต้องได้อย่างไร
เป้าหมาย: เพื่อให้ลูกค้าสามารถสร้างอินสแตนซ์ของวัตถุโดยใช้วิธีการจากโรงงานแทนตัวสร้างของวัตถุโดยไม่มีผลกระทบที่ยอมรับไม่ได้และประสิทธิภาพในการทำงาน
โดย "รูปแบบวิธีการของโรงงาน" ฉันหมายถึงทั้งวิธีคงที่จากโรงงานภายในวัตถุหรือวิธีที่กำหนดไว้ในคลาสอื่นหรือฟังก์ชั่นระดับโลก เพียงแค่โดยทั่วไป "แนวคิดของการเปลี่ยนเส้นทางวิธีปกติของการสร้างอินสแตนซ์ของคลาส X ไปที่อื่นนอกเหนือจากตัวสร้าง"
ให้ฉันอ่านคำตอบที่เป็นไปได้ที่ฉันคิด
0) อย่าสร้างโรงงานสร้างสิ่งก่อสร้าง
สิ่งนี้ฟังดูดี (และมักจะเป็นทางออกที่ดีที่สุด) แต่ไม่ใช่วิธีการรักษาทั่วไป ประการแรกมีกรณีที่การก่อสร้างวัตถุเป็นงานที่ซับซ้อนพอที่จะพิสูจน์การแยกไปยังชั้นอื่น แต่ถึงกระนั้นก็ยังมีการวางความจริงนั้นไว้แม้กระทั่งสำหรับวัตถุอย่างง่ายที่ใช้เพียงแค่ตัวสร้างมักจะไม่ทำ
ตัวอย่างที่ง่ายที่สุดที่ฉันรู้คือคลาสเวกเตอร์ 2 มิติ เรียบง่าย แต่ซับซ้อน ฉันต้องการสร้างมันทั้งจากพิกัดคาร์ทีเซียนและขั้ว เห็นได้ชัดว่าฉันไม่สามารถทำ:
struct Vec2 {
Vec2(float x, float y);
Vec2(float angle, float magnitude); // not a valid overload!
// ...
};
วิธีคิดตามธรรมชาติของฉันคือ:
struct Vec2 {
static Vec2 fromLinear(float x, float y);
static Vec2 fromPolar(float angle, float magnitude);
// ...
};
ซึ่งแทนที่ constructors ทำให้ฉันใช้วิธีการคงที่จากโรงงาน ... ซึ่งหมายความว่าฉันใช้รูปแบบโรงงานในทางใดทางหนึ่ง ("ชั้นกลายเป็นโรงงานของตัวเอง") นี่ดูดี (และจะเหมาะกับกรณีนี้) แต่ล้มเหลวในบางกรณีซึ่งฉันจะอธิบายในจุดที่ 2 อ่านต่อ
อีกกรณีหนึ่ง: พยายามโอเวอร์โหลดโดย typedef ทึบสองตัวของ API บางตัว (เช่น GUID ของโดเมนที่ไม่เกี่ยวข้องกัน, หรือ GUID และ bitfield), พิมพ์ semantically ต่างกันโดยสิ้นเชิง (ดังนั้น - ในทางทฤษฎี - overloads ที่ถูกต้อง) สิ่งเดียวกัน - เช่น ints ที่ไม่ได้ลงชื่อหรือตัวชี้โมฆะ
1) วิธี Java
Java ทำได้ง่ายเนื่องจากเรามีเพียงวัตถุที่จัดสรรแบบไดนามิก การทำให้โรงงานมีความสำคัญเหมือน:
class FooFactory {
public Foo createFooInSomeWay() {
// can be a static method as well,
// if we don't need the factory to provide its own object semantics
// and just serve as a group of methods
return new Foo(some, args);
}
}
ใน C ++ สิ่งนี้แปลเป็น:
class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
};
เย็น? บ่อยครั้งแน่นอน แต่ - สิ่งนี้บังคับให้ผู้ใช้ใช้การจัดสรรแบบไดนามิกเท่านั้น การจัดสรรแบบคงที่คือสิ่งที่ทำให้ C ++ ซับซ้อน แต่ก็เป็นสิ่งที่ทำให้มีประสิทธิภาพ นอกจากนี้ฉันเชื่อว่ามีเป้าหมายบางอย่าง (คำหลัก: ฝังตัว) ซึ่งไม่อนุญาตให้มีการจัดสรรแบบไดนามิก และนั่นไม่ได้หมายความว่าผู้ใช้แพลตฟอร์มเหล่านั้นชอบเขียน OOP ที่สะอาด
อย่างไรก็ตามปรัชญากัน: ในกรณีทั่วไปฉันไม่ต้องการบังคับให้ผู้ใช้ของโรงงานถูก จำกัด การจัดสรรแบบไดนามิก
2) ผลตอบแทนต่อมูลค่า
ตกลงเรารู้ว่า 1) ยอดเยี่ยมเมื่อเราต้องการการจัดสรรแบบไดนามิก ทำไมเราจะไม่เพิ่มการจัดสรรแบบคงที่ด้านบนของสิ่งนั้น?
class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooInSomeWay() {
return Foo(some, args);
}
};
อะไร? เราไม่สามารถโอเวอร์โหลดตามประเภทการคืนได้หรือไม่ โอ้แน่นอนเราทำไม่ได้ ดังนั้นขอเปลี่ยนชื่อเมธอดเพื่อสะท้อนสิ่งนั้น และใช่ฉันได้เขียนตัวอย่างโค้ดที่ไม่ถูกต้องด้านบนเพื่อเน้นว่าฉันไม่ชอบความต้องการเปลี่ยนชื่อเมธอดมากแค่ไหนเนื่องจากเราไม่สามารถใช้การออกแบบโรงงานผู้ไม่เชื่อเรื่องภาษาได้อย่างถูกต้องเนื่องจากเราต้องเปลี่ยนชื่อ - และ ผู้ใช้ทุกคนของรหัสนี้จะต้องจำความแตกต่างของการใช้งานจากข้อกำหนดที่
class FooFactory {
public:
Foo* createDynamicFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooObjectInSomeWay() {
return Foo(some, args);
}
};
ตกลง ... เรามีมัน มันน่าเกลียดเพราะเราต้องเปลี่ยนชื่อวิธี มันไม่สมบูรณ์เนื่องจากเราจำเป็นต้องเขียนรหัสเดียวกันสองครั้ง แต่เมื่อเสร็จแล้วก็ใช้งานได้ ขวา?
ปกติแล้ว แต่บางครั้งมันก็ไม่ได้ เมื่อสร้าง Foo เราต้องพึ่งพาคอมไพเลอร์เพื่อทำการเพิ่มประสิทธิภาพค่าส่งคืนสำหรับเราเนื่องจากมาตรฐาน C ++ นั้นมีความสำคัญมากพอสำหรับผู้จำหน่ายคอมไพเลอร์ที่จะไม่ระบุว่าเมื่อใดที่วัตถุจะถูกสร้างขึ้นและจะถูกคัดลอกเมื่อส่งคืน วัตถุชั่วคราวโดยค่าใน C ++ ดังนั้นหาก Foo มีราคาแพงในการคัดลอกแนวทางนี้มีความเสี่ยง
และจะทำอย่างไรถ้า Foo นั้นไม่สามารถจัดการได้เลย? เอ่อ ( โปรดทราบว่าใน C ++ 17 พร้อมการคัดลอกการรับประกันการทำสำเนาไม่ถูกต้องไม่มีปัญหาสำหรับรหัสด้านบนอีกต่อไป )
สรุป: การสร้างโรงงานโดยส่งคืนวัตถุนั้นเป็นวิธีแก้ปัญหาสำหรับบางกรณี (เช่นเวกเตอร์ 2 มิติที่กล่าวถึงก่อนหน้านี้) แต่ก็ยังไม่ใช่การทดแทนโดยทั่วไปสำหรับตัวสร้าง
3) การก่อสร้างสองเฟส
อีกสิ่งที่ใครบางคนอาจจะเกิดขึ้นก็คือการแยกปัญหาของการจัดสรรวัตถุและการเริ่มต้น ซึ่งมักจะส่งผลในรหัสเช่นนี้:
class Foo {
public:
Foo() {
// empty or almost empty
}
// ...
};
class FooFactory {
public:
void createFooInSomeWay(Foo& foo, some, args);
};
void clientCode() {
Foo staticFoo;
auto_ptr<Foo> dynamicFoo = new Foo();
FooFactory factory;
factory.createFooInSomeWay(&staticFoo);
factory.createFooInSomeWay(&dynamicFoo.get());
// ...
}
บางคนอาจคิดว่ามันใช้งานได้อย่างมีเสน่ห์ ราคาเดียวที่เราจ่ายในรหัสของเรา ...
เนื่องจากฉันเขียนทั้งหมดนี้และปล่อยให้สิ่งนี้เป็นครั้งสุดท้ายฉันต้องไม่ชอบเช่นกัน :) ทำไม
ก่อนอื่น ... ฉันไม่ชอบแนวคิดเรื่องการก่อสร้างสองเฟสอย่างจริงใจและฉันรู้สึกผิดเมื่อใช้มัน ถ้าฉันออกแบบวัตถุด้วยการยืนยันว่า "ถ้ามีอยู่มันอยู่ในสถานะที่ถูกต้อง" ฉันรู้สึกว่ารหัสของฉันปลอดภัยกว่าและมีข้อผิดพลาดน้อยกว่า ฉันชอบแบบนั้น.
ต้องลดการประชุมและเปลี่ยนการออกแบบวัตถุของฉันเพียงเพื่อวัตถุประสงค์ในการทำให้โรงงานของมันคือ .. ดีอุ้ยอ้าย
ฉันรู้ว่าข้างต้นจะไม่โน้มน้าวใจใครหลายคนดังนั้นฉันจะให้ข้อโต้แย้งที่มั่นคงมากขึ้น เมื่อใช้การก่อสร้างแบบสองเฟสคุณจะไม่สามารถ:
- เริ่มต้น
const
หรือตัวแปรสมาชิกอ้างอิง - ส่งอาร์กิวเมนต์ไปยังตัวสร้างคลาสพื้นฐานและตัวสร้างวัตถุสมาชิก
และอาจจะมีข้อเสียเพิ่มเติมบางอย่างที่ฉันไม่สามารถคิดได้ในตอนนี้และฉันก็ไม่ได้รู้สึกว่าจำเป็นโดยเฉพาะอย่างยิ่งเนื่องจากสัญลักษณ์แสดงหัวข้อย่อยข้างต้นโน้มน้าวฉันแล้ว
ดังนั้น: ไม่ใกล้กับโซลูชันทั่วไปที่ดีสำหรับการใช้งานโรงงาน
สรุป:
เราต้องการมีวิธีสร้างอินสแตนซ์ของวัตถุซึ่งจะ:
- อนุญาตให้มีการสร้างอินสแตนซ์เครื่องแบบโดยไม่คำนึงถึงการจัดสรร
- ให้ชื่อที่แตกต่างและมีความหมายกับวิธีการก่อสร้าง (ดังนั้นไม่ต้องพึ่งพาการโต้แย้งมากไป)
- ไม่แนะนำการโจมตีที่มีประสิทธิภาพอย่างมากและโดยเฉพาะอย่างยิ่งการเผยแพร่โค้ดที่มีความหมายโดยเฉพาะที่ฝั่งไคลเอ็นต์
- ทั่วไปใน: เป็นไปได้ที่จะได้รับการแนะนำสำหรับชั้นเรียนใด ๆ
ฉันเชื่อว่าฉันได้พิสูจน์แล้วว่าวิธีการที่กล่าวถึงไม่เป็นไปตามข้อกำหนดเหล่านั้น
คำใบ้ใด ๆ โปรดให้วิธีการแก้ปัญหาแก่ฉันฉันไม่ต้องการคิดว่าภาษานี้จะไม่อนุญาตให้ฉันนำแนวคิดดังกล่าวไปใช้อย่างไม่เหมาะสม
delete
ได้ วิธีการเหล่านี้ใช้ได้อย่างสมบูรณ์ตราบใดที่มันเป็น "เอกสาร" (ซอร์สโค้ดคือเอกสาร ;-)) ที่ผู้เรียกใช้เป็นเจ้าของตัวชี้ (อ่าน: มีหน้าที่รับผิดชอบในการลบเมื่อเหมาะสม)
unique_ptr<T>
T*