มีกลยุทธ์การออกแบบเฉพาะที่สามารถนำไปใช้แก้ปัญหาไก่และไข่ส่วนใหญ่ในขณะที่ใช้วัตถุที่ไม่เปลี่ยนรูปแบบได้หรือไม่?


17

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

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

[แก้ไข] ทุกกรณีวัสดุคือ "ซิงเกิลตัน" เช่นเดียวกับ Java Enum

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

พวกเขาไม่มีทางแก้ไขปัญหานี้หรือฉันจำเป็นต้องใช้เทคนิคการเขียนโปรแกรม "ฟังก์ชั่น" หรือรูปแบบอื่น ๆ เพื่อแก้ไขหรือไม่


2
สำหรับฉันในแบบที่คุณพูดไม่มีน้ำและน้ำแข็ง มีเพียงh2oวัสดุ
ริ้น

1
ฉันรู้ว่าสิ่งนี้จะทำให้ "ความรู้สึกทางวิทยาศาสตร์" มากขึ้น แต่ในเกมมันง่ายกว่าที่จะสร้างแบบจำลองเป็นวัสดุสองชนิดที่มีคุณสมบัติ "คงที่" แทนที่จะเป็นวัสดุหนึ่งชนิดที่มีคุณสมบัติ "ตัวแปร" ขึ้นอยู่กับบริบท
Sebastien Diot

Singleton เป็นความคิดที่โง่
DeadMG

@DeadMG ก็โอเค พวกเขาไม่ใช่ Java Singletons จริง ฉันแค่หมายความว่าไม่มีประเด็นที่จะสร้างมากกว่าหนึ่งตัวอย่างของแต่ละอันเนื่องจากมันไม่เปลี่ยนรูปและจะเท่ากับซึ่งกันและกัน ที่จริงแล้วฉันจะไม่มีอินสแตนซ์คงที่ใด ๆ วัตถุ "root" ของฉันคือบริการของ OSGi
Sebastien Diot

คำตอบสำหรับคำถามอื่นนี้ดูเหมือนจะยืนยันข้อสงสัยของฉันว่าสิ่งต่าง ๆ มีความซับซ้อนอย่างรวดเร็วด้วย immutables: programmers.stackexchange.com/questions/68058/…
Sebastien Diot

คำตอบ:


2

ทางออกคือการโกงนิดหน่อย โดยเฉพาะ:

  • สร้าง A แต่ปล่อยให้การอ้างอิงถึง B ไม่มีการกำหนดค่าเริ่มต้น (เนื่องจาก B ยังไม่มีอยู่)

  • สร้าง B และให้มันชี้ไปที่ A

  • อัปเดต A เพื่อชี้ไปที่ B อย่าอัปเดต A หรือ B หลังจากนี้

สิ่งนี้สามารถทำได้อย่างชัดเจน (ตัวอย่างใน C ++):

struct List {
    int n;
    List *next;

    List(int n, List *next)
        : n(n), next(next);
};

// Return a list containing [0,1,0,1,...].
List *binary(void)
{
    List *a = new List(0, NULL);
    List *b = new List(1, a);
    a->next = b; // Evil, but necessary.
    return a;
}

หรือโดยนัย (ตัวอย่างใน Haskell):

binary :: [Int]
binary = a where
    a = 0 : b
    b = 1 : a

ตัวอย่าง Haskell ใช้การประเมินแบบสันหลังยาวเพื่อให้ได้มายาของค่าที่ไม่เปลี่ยนรูปได้ซึ่งกันและกัน ค่าเริ่มต้นจาก:

a = 0 : <thunk>
b = 1 : a

aและbเป็นทั้งแบบฟอร์ม head-normal ที่ถูกต้องโดยอิสระ ข้อเสียแต่ละข้อสามารถสร้างได้โดยไม่ต้องใช้ค่าสุดท้ายของตัวแปรอื่น เมื่อประเมินค่า thunk แล้วมันจะชี้ไปที่bจุดข้อมูลเดียวกันกับ

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


ในตัวอย่างของคุณโดยเฉพาะฉันอาจแสดงเป็น Haskell เป็น:

data Material = Water {temperature :: Double}
              | Ice   {temperature :: Double}

setTemperature :: Double -> Material -> Material
setTemperature newTemp (Water _) | newTemp <= 0.0 = Ice newTemp
                                 | otherwise      = Water newTemp
setTemperature newTemp (Ice _)   | newTemp >= 1.0 = Water newTemp
                                 | otherwise      = Ice newTemp

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


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

6

ในตัวอย่างของคุณคุณใช้การแปลงกับวัตถุดังนั้นฉันจะใช้บางอย่างเช่นApplyTransform()วิธีการส่งกลับBlockBaseแทนที่จะพยายามเปลี่ยนวัตถุปัจจุบัน

ตัวอย่างเช่นในการเปลี่ยน IceBlock เป็น WaterBlock ด้วยการใช้ความร้อนฉันจะเรียกมันว่า

BlockBase currentBlock = new IceBlock();
currentBlock = currentBlock.ApplyTemperature(1); 
// currentBlock is now a WaterBlock 

และIceBlock.ApplyTemperature()วิธีการจะมีลักษณะเช่นนี้:

public class IceBlock() : BlockBase
{
    public BlockBase ApplyTemperature(int temp)
    {
        return (temp > 0 ? new WaterBlock((BlockBase)this) : this);
    }
}

นี่เป็นคำตอบที่ดี แต่น่าเสียดายที่เพียงเพราะฉันไม่ได้พูดถึงว่า "วัสดุ" ของฉัน "บล็อก" ของฉันแท้จริงแล้วเป็นซิงเกิลทั้งหมดดังนั้น WaterBlock () ใหม่จึงไม่ใช่ตัวเลือก นั่นคือประโยชน์หลักของการเปลี่ยนรูปคุณสามารถนำกลับมาใช้ใหม่ได้อย่างไม่ จำกัด แทนที่จะมี RAM อยู่ 500,000 บล็อกฉันมี 500,000 อ้างอิงถึง 100 บล็อก ถูกกว่ามาก!
Sebastien Diot

แล้วกลับมาBlockList.WaterBlockแทนที่จะสร้างบล็อกใหม่ล่ะ
ราเชล

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

@ เซบาสเตียนฉันคิดว่าBlockListเป็นเพียงstaticคลาสที่รับผิดชอบอินสแตนซ์เดียวของแต่ละบล็อกดังนั้นคุณไม่จำเป็นต้องสร้างอินสแตนซ์ของBlockList(ฉันคุ้นเคยกับ C #)
Rachel

@Sebastien: ถ้าคุณใช้ Singeltons คุณต้องจ่ายราคา
DeadMG

6

อีกวิธีหนึ่งในการทำลายวงจรคือการแยกความกังวลของเนื้อหาและการแปลงเสียงในบางภาษาที่ทำขึ้น:

water = new Block("water");
ice = new Block("ice");

transitions = new Transitions([
    new transitions.temperature.Below(0.0, water, ice),
    new transitions.temperature.Above(0.0, ice, water),
]);

หืมมันเป็นเรื่องยากสำหรับฉันที่จะอ่านมันในตอนแรก แต่ฉันคิดว่ามันเป็นแนวทางเดียวกับที่ฉันสนับสนุน
Aidan Cully

1

หากคุณกำลังจะใช้ภาษาที่ใช้งานได้และคุณต้องการตระหนักถึงประโยชน์ของความไม่สามารถเปลี่ยนแปลงได้คุณควรเข้าหาปัญหาโดยคำนึงถึงสิ่งนั้น คุณกำลังพยายามกำหนดประเภทของวัตถุ "น้ำแข็ง" หรือ "น้ำ" ที่สามารถรองรับช่วงอุณหภูมิ - เพื่อรองรับการเปลี่ยนแปลงไม่ได้คุณต้องสร้างวัตถุใหม่ทุกครั้งที่อุณหภูมิเปลี่ยนแปลงซึ่งสิ้นเปลือง ดังนั้นพยายามทำให้แนวคิดของประเภทบล็อกและอุณหภูมิเป็นอิสระมากขึ้น ฉันไม่รู้ Scala (อยู่ในรายการเรียนรู้ของฉัน :-)) แต่ยืมมาจาก Joey Adams คำตอบใน Haskellฉันขอแนะนำบางอย่างเช่น:

data Material = Water | Ice

blockForTemperature :: Double -> Material
blockForTemperature x = 
  if x < 0 then Ice else Water

หรืออาจจะ:

transitionForTemperature :: Material -> Double -> Material
transitionForTemperature oldMaterial newTemp = 
  case (oldMaterial, newTemp) of
    (Ice, _) | newTemp > 0 -> Water
    (Water, _) | newTemp <= 0 -> Ice

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


ฉันไม่ได้พยายามใช้ "functional language" เพราะฉันไม่เข้าใจ! สิ่งเดียวที่ฉันเก็บไว้จากตัวอย่างการเขียนโปรแกรมการทำงานที่ไม่สำคัญคือ: "ประณามฉันซึ่งฉันฉลาดกว่า!" มันเป็นเรื่องเกินกว่าฉันที่จะทำให้คนอื่นเข้าใจได้ จากวันที่นักเรียนของฉันฉันจำได้ว่า Prolog (ตามตรรกะ), Occam (ทุกอย่างทำงานในแบบขนานโดยค่าเริ่มต้น) และแม้กระทั่งผู้ประกอบทำให้รู้สึก แต่ Lisp เป็นเพียงสิ่งที่บ้า แต่ฉันขอให้คุณย้ายโค้ดที่ทำให้เกิด "การเปลี่ยนแปลงสถานะ" นอก "สถานะ"
Sebastien Diot
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.