วิธีการ refactor โปรแกรม OO เป็นหนึ่งทำงานได้อย่างไร


26

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

สิ่งที่ฉันต้องการค้นหาจริงๆคือการสนทนาในเชิงลึกเกี่ยวกับการใช้งาน OOP ของโปรแกรมที่ไม่น่าสนใจข้อ จำกัด และวิธีการปรับโครงสร้างในลักษณะการทำงาน ไม่ใช่แค่อัลกอริธึมหรือโครงสร้างข้อมูล แต่มีบางอย่างที่มีบทบาทและแง่มุมที่แตกต่างกัน - อาจเป็นวิดีโอเกม โดยวิธีที่ฉันได้อ่าน Real-World Function Programming โดย Tomas Petricek แต่ฉันก็อยากได้มากกว่านี้


6
ฉันไม่คิดว่ามันเป็นไปได้ คุณต้องออกแบบ (และเขียนใหม่) ทุกอย่างอีกครั้ง
ไบรอันเฉิน

18
-1 โพสต์นี้มีอคติกับการสันนิษฐานที่ผิดว่า OOP และสไตล์การใช้งานตรงกันข้าม ส่วนใหญ่เป็นแนวคิดฉากและ IMHO เป็นตำนานที่พวกเขาไม่ได้ "ฟังก์ชั่น" นั้นตรงข้ามกับ "ขั้นตอน" และทั้งสองสไตล์สามารถใช้ร่วมกับ OOP
Doc Brown

11
@DocBrown, OOP ขึ้นอยู่กับสถานะที่ไม่แน่นอน ออบเจกต์ไร้สัญชาตินั้นไม่เหมาะสมกับการออกแบบ OOP ในปัจจุบัน
SK-logic

9
@ SK-logic: กุญแจไม่ใช่วัตถุไร้สัญชาติ แต่เป็นวัตถุที่ไม่เปลี่ยนรูป และแม้กระทั่งเมื่อวัตถุไม่แน่นอนพวกเขาสามารถใช้งานได้ในส่วนการทำงานของระบบตราบใดที่วัตถุเหล่านั้นไม่ได้ถูกเปลี่ยนแปลงภายในบริบทที่กำหนด นอกจากนี้ฉันคิดว่าคุณรู้ว่าวัตถุและการปิดสามารถใช้แทนกันได้ ดังนั้นทั้งหมดนี้แสดงให้เห็นว่า OOP และ "การทำงาน" ไม่ได้ขัดแย้งกัน
Doc Brown

12
@DocBrown: ฉันคิดว่าการสร้างภาษานั้นมีมุมฉากขณะที่ความคิดมักจะขัดแย้ง คน OOP มักจะถามว่า "วัตถุคืออะไรและพวกเขาทำงานร่วมกันอย่างไร"; คนที่ใช้งานได้มักจะถามว่า "ข้อมูลของฉันคืออะไรและฉันต้องการเปลี่ยนมันอย่างไร" คำถามเหล่านั้นไม่เหมือนกันและนำไปสู่คำตอบที่แตกต่างกัน ฉันคิดว่าคุณอ่านคำถามผิด ไม่ใช่ "OOP drools และ FP กฎฉันจะกำจัด OOP ได้อย่างไร" เป็น "ฉันได้รับ OOP และไม่ได้รับ FP มีวิธีแปลงโปรแกรม OOP เป็นโปรแกรมที่ใช้งานได้เพื่อให้ได้ มีบางคนเข้าใจ "
Michael Shaw

คำตอบ:


31

ความหมายของการเขียนโปรแกรมฟังก์ชั่น

การแนะนำThe Joy of Clojureกล่าวว่า:

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

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

การเขียนโปรแกรมใน Scala 2nd Edition p. 10 มีคำจำกัดความต่อไปนี้:

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

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

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

ฟังก์ชั่นชั้นหนึ่ง

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

ความจำเป็น:

for (Passenger p : Bus.getPassengers()) {
    p.debit(fare);
}

ใช้งานได้ (ใช้ฟังก์ชั่นนิรนามหรือ "lambda" ใน Scala):

myBus = myBus.forEachPassenger(p:Passenger -> { p.debit(fare) })

รุ่น Scala หวานมากขึ้น:

myBus = myBus.forEachPassenger(_.debit(fare))

ฟังก์ชั่นที่ไม่ใช่ชั้นแรก

หากภาษาของคุณไม่รองรับฟังก์ชั่นชั้นหนึ่งสิ่งนี้ก็น่าเกลียดมาก ใน Java 7 หรือรุ่นก่อนหน้าคุณต้องจัดเตรียมส่วนต่อประสาน "หน้าที่วัตถุ" ดังนี้:

// Java 8 has java.util.function.Consumer, but in earlier
// versions you have to roll your own:
public interface Consumer<T> {
    public void accept(T t);
}

ดังนั้นคลาส Bus จะมีตัววนซ้ำภายใน:

public void forEachPassenger(Consumer<Passenger> c) {
    for (Passenger p : passengers) {
        c.accept(p);
    }
}

ในที่สุดคุณก็ผ่านวัตถุฟังก์ชั่นที่ไม่ระบุชื่อไปที่รถบัส:

// Java 8 has syntactic sugar to make this look more like
// the Scala solution, but earlier versions require manually
// instantiating a "Function Object," in this case, a
// Consumer:
Bus.forEachPassenger(new Consumer<Passenger>() {
    @Override
    public void accept(final Passenger p) {
        p.debit(fare);
    }
}

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

public static class MutableIntWrapper {
    private int i;
    private MutableIntWrapper(int in) { i = in; }
    public static MutableIntWrapper ofZero() {
        return new MutableIntWrapper(0);
    }
    public int value() { return i; }
    public void increment() { i++; }
}

final MutableIntWrapper count = MutableIntWrapper.ofZero();
Bus.forEachPassenger(new Consumer<Passenger>() {
    @Override
    public void accept(final Passenger p) {
        p.debit(fare);
        count.increment();
    }
}

System.out.println(count.value());

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

ความอัปลักษณ์นี้ได้รับการแก้ไขใน Java 8 แต่การจัดการข้อยกเว้นที่ตรวจสอบภายในฟังก์ชั่นชั้นหนึ่งยังคงน่าเกลียดมากและ Java ยังคงมีข้อสันนิษฐานของความไม่แน่นอนในคอลเลกชันทั้งหมด ซึ่งนำเราไปสู่เป้าหมายอื่น ๆ ที่มักเกี่ยวข้องกับ FP:

การเปลี่ยนไม่ได้

รายการของ Josh Bloch ที่ 13 คือ "ชอบไม่ได้" แม้จะมีถังขยะทั่วไปพูดคุยกัน OOP สามารถทำได้ด้วยวัตถุที่ไม่เปลี่ยนรูปแบบและการทำเช่นนั้นทำให้ดีขึ้นมาก ตัวอย่างเช่น String ใน Java ไม่เปลี่ยนรูป StringBuffer, OTOH จะต้องไม่แน่นอนเพื่อสร้างสตริงที่ไม่เปลี่ยนรูป งานบางอย่างเช่นการทำงานกับบัฟเฟอร์ต้องการความไม่แน่นอน

ความบริสุทธิ์

แต่ละฟังก์ชั่นควรจะสามารถจดจำได้อย่างน้อย - ถ้าคุณให้พารามิเตอร์อินพุตเดียวกัน (และไม่ควรมีอินพุตนอกเหนือจากอาร์กิวเมนต์ที่เกิดขึ้นจริง) มันควรสร้างเอาต์พุตเดียวกันทุกครั้งโดยไม่ก่อให้เกิด "ผลข้างเคียง" เช่นการเปลี่ยนสถานะโลก / O หรือโยนข้อยกเว้น

มีคนกล่าวว่าในการเขียนโปรแกรมฟังก์ชั่น "มักจะมีความชั่วร้ายบางอย่างเพื่อให้งานสำเร็จ" โดยทั่วไปความบริสุทธิ์ 100% ไม่ใช่เป้าหมาย การลดผลข้างเคียงคือ

ข้อสรุป

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

ถ้าคุณชอบ JVM ฉันขอแนะนำให้คุณดูที่ Scala และ Clojure ทั้งสองเป็นการตีความเชิงฟังก์ชันของ Programming Programming อย่างลึกซึ้ง Scala เป็นประเภทที่ปลอดภัยที่มีไวยากรณ์คล้าย C แต่จริงๆแล้วมันมีไวยากรณ์ที่เหมือนกันมากกับ Haskell เช่นเดียวกับ C. Clojure ไม่ได้เป็นประเภทที่ปลอดภัยและมันเป็นเสียงกระเพื่อม ฉันเพิ่งโพสต์การเปรียบเทียบ Java, Scala และ Clojureเกี่ยวกับปัญหา refactoring เฉพาะหนึ่ง การเปรียบเทียบของ Logan Campbellโดยใช้ Game of Life นั้นรวมถึง Haskell และพิมพ์ Clojure ด้วย

PS

จิมมี่ฮอฟฟาชี้ให้เห็นว่าคลาสรถบัสของฉันไม่แน่นอน แทนที่จะแก้ไขต้นฉบับฉันคิดว่าสิ่งนี้จะแสดงให้เห็นอย่างชัดเจนถึงประเภทของการปรับโครงสร้างคำถามนี้ที่เกี่ยวกับ สิ่งนี้สามารถแก้ไขได้โดยการทำแต่ละวิธีบนบัสโรงงานเพื่อผลิตรถบัสใหม่แต่ละวิธีสำหรับผู้โดยสารที่เป็นโรงงานเพื่อผลิตผู้โดยสารใหม่ ดังนั้นฉันได้เพิ่มประเภทกลับไปทุกสิ่งซึ่งหมายความว่าฉันจะคัดลอก java.util.function.Function ของ Java 8 แทนส่วนต่อประสานผู้บริโภค

public interface Function<T,R> {
    public R apply(T t);
    // Note: I'm leaving out Java 8's compose() method here for simplicity
}

จากนั้นบนรถบัส:

public Bus mapPassengers(Function<Passenger,Passenger> c) {
    // I have to use a mutable collection internally because Java
    // does not have immutable collections that return modified copies
    // of themselves the way the Clojure and Scala collections do.
    List<Passenger> newPassengers = new ArrayList(passengers.size());
    for (Passenger p : passengers) {
        newPassengers.add(c.apply(p));
    }
    return Bus.of(driver, Collections.unmodifiableList(passengers));
}

ในที่สุดวัตถุฟังก์ชั่นที่ไม่ระบุชื่อจะคืนค่าสถานะของสิ่งต่าง ๆ (รถบัสใหม่พร้อมผู้โดยสารใหม่) สิ่งนี้ถือว่า p.debit () ส่งคืนผู้โดยสารที่ไม่เปลี่ยนรูปแบบใหม่ด้วยเงินน้อยกว่าเดิม:

Bus b = b.mapPassengers(new Function<Passenger,Passenger>() {
    @Override
    public Passenger apply(final Passenger p) {
        return p.debit(fare);
    }
}

หวังว่าตอนนี้คุณสามารถตัดสินใจด้วยตัวเองว่าคุณต้องการฟังก์ชั่นการใช้ภาษาที่จำเป็นและตัดสินใจได้อย่างไรว่าจะเป็นการดีกว่าที่จะออกแบบโครงการของคุณใหม่โดยใช้ภาษาที่ใช้งานได้ ใน Scala หรือ Clojure คอลเล็กชันและ API อื่น ๆ ได้รับการออกแบบมาเพื่อให้การเขียนโปรแกรมใช้งานง่าย ทั้งคู่มีการทำงานร่วมกันของ Java ที่ดีมากดังนั้นคุณสามารถผสมผสานและจับคู่ภาษาได้ ในความเป็นจริงสำหรับการทำงานร่วมกันของ Java, Scala รวบรวมฟังก์ชั่นชั้นหนึ่งของคลาสที่ไม่ระบุชื่อที่เกือบเข้ากันได้กับอินเตอร์เฟสการทำงานของ Java 8 คุณสามารถอ่านรายละเอียดในScala ในส่วนลึก 1.3.2


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

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

1
เหตุผลที่คุณสรุปสิ่งที่ต้องทำแผนที่และย่อและฟังก์ชันเหล่านี้ไม่ได้มีวัตถุที่บรรจุอยู่เพื่อให้สามารถนำไปใช้กับรูปแบบมากมายผ่านตัวแปรหลากหลายรูปแบบ มันเป็นความสับสนของนามธรรมที่หลากหลายที่คุณไม่พบในภาษา OOP ที่กำหนด FP และผลักดันให้มีค่า มันไม่ได้เป็นความขี้เกียจที่โปร่งใสอ้างอิงเปลี่ยนไม่ได้หรือแม้กระทั่งระบบการพิมพ์ HM จะจำเป็นในการสร้าง FP สิ่งเหล่านี้จะมีผลข้างเคียงค่อนข้างของการสร้างภาษาวัตถุประสงค์สำหรับองค์ประกอบของการทำงานที่ฟังก์ชั่นสามารถนามธรรมมากกว่าประเภททั่วไป
จิมมี่ฮอฟฟา

@JimmyHoffa คุณวิจารณ์ตัวอย่างของฉันอย่างยุติธรรม ฉันถูกล่อลวงเข้าสู่ความไม่แน่นอนโดยส่วนต่อประสาน Java8 Consumer นอกจากนี้คำจำกัดความของ chouser / fogus ของ FP ไม่รวมถึงการเปลี่ยนแปลงไม่ได้และฉันได้เพิ่มคำจำกัดความของ Odersky / Spoon / Venners ในภายหลัง ฉันออกจากตัวอย่างดั้งเดิม แต่เพิ่มรุ่นใหม่ที่ไม่เปลี่ยนรูปภายใต้ส่วน "PS" ที่ด้านล่าง มันน่าเกลียด. แต่ฉันคิดว่ามันแสดงให้เห็นถึงฟังก์ชั่นที่ทำงานกับวัตถุเพื่อสร้างวัตถุใหม่แทนที่จะเปลี่ยนภายในของต้นฉบับ ความคิดเห็นยอดเยี่ยม!
GlenPeterson

1
การสนทนานี้ดำเนินต่อไปบน The Whiteboard: chat.stackexchange.com/transcript/message/11702383#11702383
GlenPeterson

12

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

  • แปลงสถานะภายนอกทั้งหมดเป็นพารามิเตอร์ของฟังก์ชัน EG: ถ้าวัตถุปรับเปลี่ยนวิธีการxให้มันดังนั้นวิธีการที่ได้รับผ่านการแทนการโทรxthis.x
  • ลบพฤติกรรมจากวัตถุ
    1. ทำให้ข้อมูลของวัตถุสามารถเข้าถึงได้แบบสาธารณะ
    2. แปลงวิธีการทั้งหมดเป็นฟังก์ชั่นที่วัตถุเรียก
    3. มีรหัสไคลเอนต์ที่เรียกอ็อบเจ็กต์เรียกใช้ฟังก์ชันใหม่โดยส่งผ่านข้อมูลวัตถุ EG: แปลง x.methodThatModifiesTheFooVar()เป็นfooFn(x.foo)
    4. ลบวิธีการดั้งเดิมออกจากวัตถุ
  • เปลี่ยนเป็นลูปซ้ำมากที่สุดเท่าที่คุณสามารถมีฟังก์ชั่นที่สูงขึ้นเพื่อต้องการmap, reduce, filterฯลฯ

ฉันไม่สามารถกำจัดสถานะที่เปลี่ยนแปลงไม่ได้ มันไม่ใช่สำนวนในภาษาของฉัน (JavaScript) เกินไป แต่ด้วยการทำให้ทุกรัฐผ่านและ / หรือกลับมาทุกฟังก์ชั่นเป็นไปได้ที่จะทดสอบ สิ่งนี้แตกต่างจาก OOP ซึ่งการตั้งค่าสถานะจะใช้เวลานานเกินไปหรือการแยกการขึ้นต่อกันมักจะต้องแก้ไขรหัสการผลิตก่อน

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

แก้ไข

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

แต่ถ้าคุณใช้ Java คุณสามารถใช้เทคนิคเหล่านี้เพื่อทำให้คลาสของคุณไม่เปลี่ยนรูป


+1 ขึ้นอยู่กับสิ่งที่คุณพยายามทำสิ่งนี้อาจเป็นไปได้เท่าที่คุณสามารถไปได้โดยไม่ต้องทำการเปลี่ยนแปลงการออกแบบที่จะไปได้มากกว่าแค่ "การเปลี่ยนโครงสร้าง"
Evicatos

@Evicatos: ฉันไม่รู้ถ้า JavaScript ได้รับการสนับสนุนที่ดีขึ้นสำหรับสถานะที่ไม่เปลี่ยนรูปฉันคิดว่าวิธีแก้ปัญหาของฉันจะทำงานได้เหมือนที่คุณได้รับในภาษาที่ใช้งานได้ดีเช่น Clojure ตัวอย่างของสิ่งที่ต้องการบางสิ่งนอกเหนือจากการปรับโครงสร้างใหม่คืออะไร
Daniel Kaplan

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

@Evicatos เห็นการแก้ไขของฉัน
Daniel Kaplan

1
@tietyT ใช่มันเศร้าเกี่ยวกับการที่ JS เปลี่ยนแปลงไม่ได้ แต่อย่างน้อย Clojure สามารถคอมไพล์ JavaScript ได้: github.com/clojure/clojurescript
GlenPeterson

3

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

ฉันเคยเห็นการปรับโครงสร้างโค้ดใหม่ซึ่งกำหนดไว้ว่าเป็น "เทคนิคที่มีระเบียบวินัยสำหรับการปรับโครงสร้างโค้ดที่มีอยู่เดิมโดยเปลี่ยนโครงสร้างภายในโดยไม่เปลี่ยนพฤติกรรมภายนอก"

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


ฉันจะเพิ่มว่าเครื่องหมายแรกที่ดีคือการมุ่งมั่นเพื่อความโปร่งใสในการอ้างอิง เมื่อคุณมีสิ่งนี้คุณจะได้รับประโยชน์จากการเขียนโปรแกรมการทำงานประมาณ 50%
Daniel Gratzer

3

ฉันคิดว่าบทความชุดนี้เป็นสิ่งที่คุณต้องการ:

Retrogames ที่ทำงานได้อย่างหมดจด

http://prog21.dadgum.com/23.html ส่วนที่ 1

http://prog21.dadgum.com/24.html ส่วนที่ 2

http://prog21.dadgum.com/25.html ส่วนที่ 3

http://prog21.dadgum.com/26.html ส่วนที่ 4

http://prog21.dadgum.com/37.html การ ติดตามผล

สรุปคือ:

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

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


2

คุณอาจต้องเปิดใช้งานโค้ดทั้งหมดภายใน OOP และ FP มีสองวิธีในการจัดระเบียบโค้ดที่ตรงกันข้าม

OOP จัดรหัสรอบ ๆ ประเภท (คลาส): คลาสที่ต่างกันสามารถใช้การดำเนินการเดียวกันได้ (เมธอดที่มีลายเซ็นเดียวกัน) เป็นผลให้ OOP เหมาะสมกว่าเมื่อชุดของการดำเนินการไม่เปลี่ยนแปลงมากนักในขณะที่ชนิดใหม่สามารถเพิ่มได้บ่อย ตัวอย่างเช่นพิจารณาห้องสมุด GUI ซึ่งในแต่ละเครื่องมือมีชุดถาวรของวิธีการ ( hide(), show(), paint(), move(), และอื่น ๆ ) แต่เครื่องมือใหม่อาจจะเพิ่มเป็นห้องสมุดที่มีการขยาย ใน OOP มันเป็นเรื่องง่ายที่จะเพิ่มประเภทใหม่ (สำหรับอินเทอร์เฟซที่กำหนด): คุณจะต้องเพิ่มคลาสใหม่และใช้วิธีการทั้งหมด (เปลี่ยนรหัสท้องถิ่น) ในทางกลับกันการเพิ่มการดำเนินการใหม่ (วิธีการ) ไปยังอินเทอร์เฟซอาจต้องเปลี่ยนคลาสทั้งหมดที่ใช้อินเทอร์เฟซนั้น (แม้ว่าการสืบทอดสามารถลดจำนวนงาน)

FP จัดระเบียบรหัสรอบการปฏิบัติงาน (ฟังก์ชั่น): แต่ละฟังก์ชั่นใช้การดำเนินการบางอย่างที่สามารถรักษาประเภทที่แตกต่างในรูปแบบที่แตกต่างกัน นี่คือความสำเร็จมักจะทำได้โดยการเลือกประเภทผ่านการจับคู่รูปแบบหรือกลไกอื่น ๆ ดังนั้น FP จึงเหมาะสมกว่าเมื่อชุดของประเภทมีความเสถียรและมีการเพิ่มการดำเนินงานใหม่บ่อยขึ้น ยกตัวอย่างชุดรูปแบบภาพคงที่ (GIF, JPEG, ฯลฯ ) และอัลกอริทึมบางอย่างที่คุณต้องการนำไปใช้ แต่ละอัลกอริทึมสามารถใช้งานได้โดยฟังก์ชั่นที่ทำงานแตกต่างกันไปตามประเภทของภาพ การเพิ่มอัลกอริธึมใหม่นั้นง่ายเพราะคุณเพียงแค่ต้องใช้ฟังก์ชั่นใหม่ (การเปลี่ยนรหัสท้องถิ่น) การเพิ่มรูปแบบใหม่ (ประเภท) จำเป็นต้องแก้ไขฟังก์ชั่นทั้งหมดที่คุณนำมาใช้เพื่อรองรับ (การเปลี่ยนแปลงที่ไม่ใช่ในเครื่อง)

บรรทัดล่าง: OOP และ FP แตกต่างกันโดยพื้นฐานในวิธีที่พวกเขาจัดระเบียบรหัสและการเปลี่ยนการออกแบบ OOP เป็นการออกแบบ FP จะเกี่ยวข้องกับการเปลี่ยนรหัสทั้งหมดของคุณเพื่อสะท้อนสิ่งนี้ มันอาจเป็นการออกกำลังกายที่น่าสนใจ ดูบันทึกการบรรยายเหล่านี้ในหนังสือ SICP ที่อ้างถึงโดย mikemay โดยเฉพาะสไลด์ 13.1.5 ถึง 13.1.10

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