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


9

ฉันทำงานกับเอ็นจิ้นเกมของตัวเองและตอนนี้ฉันกำลังออกแบบผู้จัดการของฉัน ฉันได้อ่านแล้วว่าสำหรับการจัดการหน่วยความจำการใช้Init()และCleanUp()ฟังก์ชั่นจะดีกว่าเมื่อใช้ Constructor และ Destructors

ฉันกำลังมองหาตัวอย่างรหัส C ++ เพื่อดูว่าฟังก์ชั่นเหล่านั้นทำงานอย่างไรและฉันจะนำไปใช้ในเครื่องยนต์ได้อย่างไร ทำงานอย่างไรInit()และCleanUp()ทำงานอย่างไรและฉันจะนำไปใช้กับเครื่องยนต์ได้อย่างไร



สำหรับ C ++ ดูstackoverflow.com/questions/3786853/…เหตุผลหลักในการใช้ Init () คือ 1) ป้องกันข้อยกเว้นและข้อขัดข้องใน Constructor ที่มีฟังก์ชั่นตัวช่วย 2) ความสามารถในการใช้วิธีเสมือนจากคลาสที่ได้รับ 3) 4) เป็นวิธีการส่วนตัวเพื่อหลีกเลี่ยงการทำซ้ำรหัส
brita_

คำตอบ:


12

มันง่ายจริง ๆ แล้ว:

แทนที่จะมีตัวสร้างซึ่งทำการตั้งค่าของคุณ

// c-family pseudo-code
public class Thing {
    public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}

... ให้นวกรรมิกของคุณทำน้อยหรือไม่มีเลยและเขียนวิธีการที่เรียกว่า.initหรือ.initializeซึ่งจะทำในสิ่งที่ตัวสร้างของคุณทำตามปกติ

public class Thing {
    public Thing () {}
    public void initialize (a, b, c, d) {
        this.x = a; /*...*/
    }
}

ดังนั้นตอนนี้แทนที่จะเป็นเช่น:

Thing thing = new Thing(1, 2, 3, 4);

คุณสามารถไปที่:

Thing thing = new Thing();

thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);

ประโยชน์ที่ได้รับคือตอนนี้คุณสามารถใช้การพึ่งพาการฉีด / การผกผันของการควบคุมได้ง่ายขึ้นในระบบของคุณ

แทนที่จะพูด

public class Soldier {
    private Weapon weapon;

    public Soldier (name, x, y) {
        this.weapon = new Weapon();
    }
}

คุณสามารถสร้างทหารให้เขาวิธีการติดตั้งที่คุณมอบให้เขาอาวุธแล้วโทรทั้งหมดส่วนที่เหลือของฟังก์ชั่นคอนสตรัคที่

ดังนั้นแทนที่จะเป็น subclassing ศัตรูที่ทหารคนหนึ่งมีปืนพกและอีกคนมีปืนยาวและอีกคนมีปืนลูกซองและนั่นเป็นความแตกต่างเพียงอย่างเดียวคุณสามารถพูดได้ว่า:

Soldier soldier1 = new Soldier(),
        soldier2 = new Soldier(),
        soldier3 = new Soldier();

soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());

soldier1.initialize("Bob",  32,  48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92,  30);

ข้อตกลงเดียวกันกับการทำลาย หากคุณมีความต้องการพิเศษ (ลบผู้ฟังเหตุการณ์ลบอินสแตนซ์จากอาร์เรย์ / โครงสร้างใด ๆ ที่คุณทำงานด้วย ฯลฯ ) จากนั้นคุณจะเรียกพวกเขาด้วยตนเองเพื่อให้คุณรู้ว่าเมื่อใดและที่ไหนในโปรแกรมที่เกิดขึ้น

แก้ไข


ดังที่ Kryotan ได้ระบุไว้ด้านล่างคำตอบนี้เป็นคำตอบของโพสต์ต้นฉบับว่า "อย่างไร"แต่ไม่ได้ผลที่ดีของ "ทำไม"

ดังที่คุณเห็นได้จากคำตอบข้างต้นอาจไม่มีความแตกต่างระหว่าง:

var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();

และการเขียน

var myObj = new Object(1,2);

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

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

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

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

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

อีกครั้งไม่ว่าคุณจะส่งข้อมูลที่ต้องการไปสู่การเริ่มต้นที่มโหฬารหรือการสร้างอินเทอร์เฟซนั้นไม่สำคัญสำหรับแนวคิดมากเท่าที่มันสำคัญสำหรับอินเทอร์เฟซของวัตถุของคุณและการออกแบบระบบของคุณ ...

แต่ในแง่ของการสร้างวัตถุคุณอาจทำสิ่งนี้:

var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);

async_loader อาจได้รับชื่อไฟล์หรือชื่อทรัพยากรหรืออะไรก็ตามโหลดทรัพยากรนั้น - มันอาจโหลดไฟล์เสียงหรือข้อมูลภาพหรืออาจโหลดสถิติอักขระที่บันทึกไว้ ...

... แล้วมันจะดึงข้อมูลนั้นกลับเข้าobj_w_async_dependencies.init(result);มา

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

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

ในแง่ของอินสแตนซ์เฉพาะของเกมให้พิจารณาGameคลาสจริง

ทำไมเราไม่สามารถโทรหา.startหรือ.runในตัวสร้าง?
จำเป็นต้องโหลดทรัพยากร - ทุกอย่างที่เหลือถูกกำหนดไว้แล้วและดีต่อการเดินทาง แต่ถ้าเราลองเล่นเกมโดยไม่ต้องเชื่อมต่อฐานข้อมูลหรือไม่มีพื้นผิวหรือแบบจำลองหรือเสียงหรือระดับมันจะไม่เป็นเช่นนั้น เกมที่น่าสนใจโดยเฉพาะ ...

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


2
" คุณจะเรียกมันด้วยตนเองเพื่อที่คุณจะได้รู้ว่าเมื่อไหร่และที่ไหนในโปรแกรมที่กำลังเกิดขึ้น " เวลาเดียวใน C ++ ที่ตัวทำลายจะถูกเรียกโดยปริยายว่าเป็นวัตถุสแต็ก (หรือทั่วโลก) อ็อบเจ็กต์ที่จัดสรรฮีปต้องการการทำลายอย่างชัดเจน ดังนั้นจึงชัดเจนเสมอเมื่อวัตถุถูกจัดสรรคืน
Nicol Bolas

6
มันไม่ถูกต้องที่จะบอกว่าคุณต้องการวิธีการแยกต่างหากนี้เพื่อเปิดใช้งานการฉีดอาวุธประเภทต่าง ๆ หรือว่านี่เป็นวิธีเดียวที่จะหลีกเลี่ยงการแพร่กระจายของคลาสย่อย คุณสามารถส่งตัวอย่างอาวุธผ่านทางนวกรรมิกได้! ดังนั้นมันจึงเป็น -1 จากฉันเนื่องจากนี่ไม่ใช่กรณีการใช้ที่น่าสนใจ
Kylotan

1
-1 จากฉันเช่นกันด้วยเหตุผลเดียวกับ Kylotan คุณไม่ได้โต้แย้งที่น่าสนใจมากนักทั้งหมดนี้สามารถทำได้กับคอนสตรัคเตอร์
พอล Manta

ใช่มันสามารถทำได้กับตัวสร้างและตัวทำลาย เขาถามถึงกรณีการใช้เทคนิคและทำไมและอย่างไรแทนที่จะทำงานอย่างไรหรือทำไมพวกเขาถึงทำ การมีระบบที่เป็นส่วนประกอบซึ่งคุณมีเมธอด setter / binding เมื่อเทียบกับพารามิเตอร์ที่ส่งผ่านคอนสตรัคเตอร์สำหรับ DI นั้นล้วนมาถึงวิธีที่คุณต้องการสร้างส่วนต่อประสานของคุณ แต่ถ้าวัตถุของคุณต้องการส่วนประกอบ 20 IOC คุณต้องการที่จะใส่มันทั้งหมดใน Constructor ของคุณหรือไม่? คุณสามารถ? แน่นอนคุณสามารถ. คุณควร อาจจะอาจจะไม่. หากคุณเลือกที่จะไม่ไปแล้วทำคุณต้องการ.initอาจจะไม่ได้ แต่มีแนวโน้มที่ ดังนั้นกรณีที่ถูกต้อง
Norguard

1
@ Kylotan ฉันแก้ไขชื่อคำถามเพื่อถามว่าทำไม OP ถามเพียง "อย่างไร" ฉันขยายคำถามเพื่อรวม "ทำไม" เป็น "อย่างไร" เป็นเรื่องเล็กน้อยสำหรับใครก็ตามที่รู้อะไรเกี่ยวกับการเขียนโปรแกรม ("เพียงแค่ย้ายตรรกะที่คุณจะต้องใส่ ctor เข้าไปในฟังก์ชั่นแยกต่างหากและเรียกมันว่า") และ "ทำไม" น่าสนใจกว่า / ทั่วไป
Tetrad

17

ไม่ว่าคุณจะอ่านอะไรที่กล่าวว่า Init และ CleanUp ดีกว่าคุณควรบอกเหตุผลด้วย บทความที่ไม่ปรับการอ้างสิทธิ์ของพวกเขาไม่น่าอ่าน

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

บางภาษาไม่มี destructors ที่คุณสามารถไว้วางใจได้เนื่องจากการนับการอ้างอิงและการรวบรวมขยะทำให้ยากที่จะทราบเมื่อวัตถุจะถูกทำลาย ในภาษาเหล่านี้คุณต้องการวิธีการปิด / ทำความสะอาดเกือบทุกครั้งและบางคนต้องการเพิ่มวิธีการเริ่มต้นสำหรับสมมาตร


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

3

ฉันคิดว่าเหตุผลที่ดีที่สุดคืออนุญาตให้รวมกำไร
หากคุณมี Init และ CleanUp คุณสามารถทำได้เมื่อวัตถุถูกฆ่าเพียงแค่เรียกใช้ CleanUp และผลักวัตถุนั้นลงบนสแต็กของวัตถุประเภทเดียวกัน: a 'pool'
จากนั้นเมื่อใดก็ตามที่คุณต้องการวัตถุใหม่คุณสามารถป๊อปอัพหนึ่งวัตถุจากพูลหรือถ้าพูลว่างเปล่า - ไม่ดี - คุณต้องสร้างใหม่ จากนั้นคุณเรียกใช้ Init บนวัตถุนี้
กลยุทธ์ที่ดีคือการเติมพูพูลล่วงหน้าก่อนที่เกมจะเริ่มต้นด้วยจำนวนวัตถุที่ 'ดี' ดังนั้นคุณไม่จำเป็นต้องสร้างออบเจ็กต์รวมระหว่างเกม
หากในอีกทางหนึ่งคุณใช้ 'ใหม่' และเพียงหยุดการอ้างอิงวัตถุเมื่อไม่มีประโยชน์กับคุณคุณสร้างขยะที่ต้องจำในบางครั้ง ความทรงจำนี้เป็นสิ่งที่ไม่ดีโดยเฉพาะอย่างยิ่งสำหรับภาษาแบบเธรดเดียวเช่น Javascript ซึ่งตัวรวบรวมข้อมูลขยะหยุดโค้ดทั้งหมดเมื่อประเมินว่าจำเป็นต้องเรียกคืนหน่วยความจำของวัตถุที่ไม่ได้ใช้งานอีกต่อไป เกมแฮงค์ในช่วงไม่กี่มิลลิวินาทีและประสบการณ์การเล่นจะเสีย
- คุณเข้าใจเรียบร้อยแล้ว: - หากคุณรวมสิ่งของทั้งหมดของคุณเข้าด้วยกันจะไม่มีการจดจำเกิดขึ้น

นอกจากนี้ยังเร็วกว่าการเรียกใช้ init บนวัตถุที่มาจากกลุ่มการจัดสรรหน่วยความจำ + เริ่มต้นวัตถุใหม่
แต่การปรับปรุงความเร็วนั้นมีความสำคัญน้อยกว่าเนื่องจากการสร้างวัตถุค่อนข้างบ่อยไม่ใช่คอขวดของประสิทธิภาพ ... มีข้อยกเว้นบางประการเช่นเกมที่น่าตื่นเต้นเครื่องยนต์อนุภาคหรือเครื่องยนต์ฟิสิกส์โดยใช้เวกเตอร์ 2D / 3d อย่างเข้มข้นสำหรับการคำนวณของพวกเขา ที่นี่ทั้งการสร้างความเร็วและขยะได้รับการปรับปรุงอย่างมากโดยใช้พูล

Rq: คุณอาจไม่จำเป็นต้องมีเมธอด CleanUp สำหรับออบเจ็กต์พูลของคุณหาก Init () รีเซ็ตทุกอย่าง

แก้ไข: ตอบกลับโพสต์นี้กระตุ้นให้ฉันจบบทความเล็ก ๆ น้อย ๆ ที่ผมทำเกี่ยวกับการร่วมกันใน Javascript
คุณสามารถค้นหาได้ที่นี่หากคุณสนใจ:
http://gamealchemist.wordpress.com/


1
-1: คุณไม่จำเป็นต้องทำสิ่งนี้เพียงเพื่อมีกลุ่มของวัตถุ คุณสามารถทำได้โดยเพียงแค่แยกการจัดสรรจากการสร้างผ่านตำแหน่งใหม่และยกเลิกการจัดสรรคืนจากการลบโดยการโทร destructor อย่างชัดเจน ดังนั้นนี่ไม่ใช่เหตุผลที่ถูกต้องในการแยก constructors / destructors จากวิธีการเริ่มต้นบางอย่าง
Nicol Bolas

ตำแหน่งใหม่เป็นเฉพาะ C ++ และมีความลับเล็กน้อยเช่นกัน
Kylotan

+1 อาจเป็นไปได้ที่จะทำเช่นนี้ใน c + แต่ไม่ใช่ในภาษาอื่น ... และนี่อาจเป็นเหตุผลเดียวที่ฉันจะใช้วิธี Init กับ gameobjects
Kikaimaru

1
@Nicol Bolas: ฉันคิดว่าคุณทำปฏิกิริยามากเกินไป ความจริงที่ว่ามีวิธีอื่นในการรวมกำไร (คุณพูดถึงวิธีที่ซับซ้อนเฉพาะสำหรับ C ++) ไม่ทำให้การใช้งานจริงแยกต่างหากจากข้อเท็จจริงที่ว่าการใช้ Init แยกเป็นวิธีที่ดีและง่ายในการนำมาใช้ร่วมกันในหลายภาษา ในการตั้งค่าของฉันไป GameDev เพื่อตอบทั่วไปมากขึ้น
GameAlchemist

@VincentPiel: การใช้ตำแหน่งใหม่และ "ซับซ้อน" ใน C ++ เป็นอย่างไร นอกจากนี้หากคุณกำลังทำงานในภาษา GC โอกาสที่ดีที่วัตถุจะมีวัตถุที่ใช้ GC ดังนั้นพวกเขาจะต้องสำรวจแต่ละคนด้วยหรือไม่ ดังนั้นการสร้างวัตถุใหม่จะเกี่ยวข้องกับการรับวัตถุใหม่จำนวนมากจากกลุ่ม
Nicol Bolas

0

คำถามของคุณจะกลับกัน ... ในอดีตการพูดคำถามที่เกี่ยวข้องมากขึ้นคือ:

ทำไมมีการ ก่อสร้าง + intialisation แฟทต์คือทำไมไม่เราทำตามขั้นตอนเหล่านี้แยกต่างหาก? แน่นอนว่าสิ่งนี้ขัดแย้งกับSoC ?

สำหรับ C ++ เจตนาของRAIIคือการได้มาซึ่งทรัพยากรและการปลดปล่อยจะเชื่อมโยงโดยตรงกับอายุการใช้งานของวัตถุด้วยความหวังว่าสิ่งนี้จะรับรองการปล่อยทรัพยากร ทำมัน? เป็นบางส่วน เป็นจริง 100% ในบริบทของตัวแปรสแต็คตาม / อัตโนมัติที่ออกจากขอบเขตที่เกี่ยวข้องโดยอัตโนมัติเรียก destructors / ปล่อยตัวแปรเหล่านี้ (จึงคัดเลือกautomatic) อย่างไรก็ตามสำหรับตัวแปรฮีปรูปแบบที่มีประโยชน์มากนี้น่าเศร้าเพราะคุณยังถูกบังคับให้โทรอย่างชัดเจนdeleteเพื่อเรียกใช้ destructor และถ้าคุณลืมที่จะทำเช่นนั้นคุณจะยังคงถูกกัดด้วยสิ่งที่ RAII พยายามแก้ไข ในบริบทของตัวแปรที่จัดสรรฮีปแล้ว C ++ จะให้ประโยชน์ที่ จำกัด เหนือ C ( deletevsfree()) ในขณะที่สร้างความสับสนกับการเริ่มต้นซึ่งส่งผลกระทบในแง่ลบต่อไปนี้:

  • การได้มาซึ่งทรัพยากรไม่ควรถือเอา initialisationเพราะนี้ไม่ได้ป้องกันไม่ให้การสั่งซื้อโทรอย่างชัดเจนโดยเฉพาะอย่างยิ่งเช่นนี้นำไปใช้กับDI
  • RAII หมายความว่าปรับศูนย์เล็งปืน / deinitialisation ของวัตถุต้องฟื้นฟูทำลาย + ซึ่งสามารถเป็นค่าใช้จ่ายในการเล่นเกมที่วัตถุร่วมกันเป็นที่ชื่นชอบ

การสร้างระบบวัตถุสำหรับเกม / การจำลองใน C ขอแนะนำอย่างยิ่งว่าจะทำให้เกิดข้อ จำกัด อย่างมากเกี่ยวกับข้อ จำกัด ของ RAII และรูปแบบ OO เป็นศูนย์กลางอื่น ๆ ผ่านความเข้าใจที่ลึกซึ้งยิ่งขึ้นเกี่ยวกับสมมติฐานที่ภาษา C ++ และ OO แบบดั้งเดิม (โปรดจำไว้ว่า C ++ เริ่มต้นจากการเป็นระบบ OO ที่สร้างขึ้นใน C)

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