แผนที่ของฟังก์ชั่นเทียบกับคำสั่งเปลี่ยน


21

ฉันกำลังทำงานในโครงการที่ประมวลผลคำขอและมีสององค์ประกอบในการร้องขอคือคำสั่งและพารามิเตอร์ ตัวจัดการสำหรับแต่ละคำสั่งนั้นง่ายมาก (<10 บรรทัดมัก <5) มีคำสั่งอย่างน้อย 20 คำและน่าจะมีมากกว่า 50 คำสั่ง

ฉันคิดวิธีแก้ปัญหาสองสามข้อ:

  • สวิตช์ใหญ่ / if-else หนึ่งคำสั่ง
  • แผนที่ของคำสั่งไปยังฟังก์ชั่น
  • แผนที่ของคำสั่งไปยังคลาสที่คงที่ / singletons

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

อะไรจะเป็นทางออกที่ดีที่สุดสำหรับปัญหานี้และเพราะอะไร ฉันยังเปิดรับรูปแบบการออกแบบใด ๆ ที่ฉันอาจพลาดไป

ฉันคิดรายการ pro / con ต่อไปนี้สำหรับแต่ละรายการ:

สวิตซ์

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

คำสั่งแผนที่ -> ฟังก์ชั่น

  • ข้อดี
    • ชิ้นเล็กขนาดกัด
    • สามารถเพิ่ม / ลบคำสั่งโดยทางโปรแกรม
  • ข้อเสีย
    • หากทำแบบอินไลน์เหมือนกับสวิตช์
    • ถ้าไม่ทำในบรรทัดฟังก์ชั่นมากมายใช้ในที่เดียวเท่านั้น

คำสั่งแผนที่ -> คลาสคงที่ / ซิงเกิล

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

หมายเหตุเพิ่มเติม:

ฉันกำลังเขียนสิ่งนี้ใน Go แต่ฉันไม่คิดว่าโซลูชันนี้เป็นภาษาเฉพาะ ฉันกำลังมองหาวิธีแก้ปัญหาทั่วไปมากกว่าเพราะฉันอาจต้องทำสิ่งที่คล้ายกันมากในภาษาอื่น

คำสั่งคือสตริง แต่ฉันสามารถแมปนี้กับตัวเลขได้อย่างง่ายดายถ้าสะดวก ลายเซ็นฟังก์ชั่นเป็นสิ่งที่ชอบ:

Reply Command(List<String> params)

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


8
แม็พคำสั่งกับฟังก์ชันและโหลดที่รันไทม์จากการกำหนดค่า ใช้รูปแบบคำสั่ง
Steven Evers

3
อย่ากลัวที่จะสร้างฟังก์ชั่นเล็ก ๆ มากมาย โดยปกติแล้วคอลเลกชันของฟังก์ชั่นเล็ก ๆ หลาย ๆ สามารถรักษาได้มากกว่าฟังก์ชั่นใหญ่หนึ่ง
Bart van Ingen Schenau

8
โลกที่ยุ่งเหยิงนี้คืออะไรที่ผู้คนตอบคำถามในความคิดเห็นและถามข้อมูลเพิ่มเติมในคำตอบ?
pdr

4
@SteveEvers: ถ้ามันไม่จำเป็นต้องทำอย่างละเอียดมันเป็นคำตอบไม่ว่าจะสั้นแค่ไหน ถ้าเป็นเช่นนั้นและคุณไม่มีเวลาหรืออะไรก็ตามให้ปล่อยให้คนอื่นตอบ (รู้สึกอยากโกงที่จะเขียนคำตอบเพื่อยืนยันความคิดเห็นที่มี upvotes ครึ่งโหลอยู่แล้ว) โดยส่วนตัวแล้วฉันรู้สึกว่านี่ต้องใช้รายละเอียดอย่างมาก OP ต้องการทราบว่าทำไมทางออกที่ดีที่สุดจึงเป็นทางออกที่ดีที่สุด
pdr

1
@pdr - ถูกต้อง ความชอบของฉันคือแผนที่ของคำสั่งสำหรับฟังก์ชั่น แต่ฉันเป็นโปรแกรมเมอร์รุ่นพี่ในหลักสูตรการออกแบบ CS อาจารย์ของฉันชอบชั้นเรียนจำนวนมากดังนั้นจึงมีวิธีแก้ไขปัญหาที่ถูกกฎหมายอย่างน้อย 2 ข้อ ฉันต้องการที่จะรู้ว่าชุมชนที่ชื่นชอบ
beatgammit

คำตอบ:


14

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

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

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

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


4

ปัญหาของคุณยืมอย่างมากกับรูปแบบการออกแบบคำสั่ง โดยพื้นฐานแล้วคุณจะมีCommandอินเตอร์เฟสพื้นฐานแล้วจะมีหลายCommandImplคลาสที่จะใช้อินเตอร์เฟสนั้น อินเทอร์เฟซหลักจำเป็นต้องมีวิธีเดียวdoCommand(Args args)เท่านั้น คุณสามารถส่งผ่านข้อโต้แย้งผ่านตัวอย่างของArgsคลาส วิธีนี้คุณใช้ประโยชน์จากพลังของความหลากหลายแทนที่จะเป็นคำสั่ง if / clunky นอกจากนี้การออกแบบนี้สามารถขยายได้อย่างง่ายดาย


3

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

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

ในทางกลับกันถ้าคุณใช้ OO-style polymorphism ง่ายต่อการเพิ่ม case ใหม่ (แค่สร้าง class ใหม่) แต่ยากที่จะเพิ่มเมธอดให้กับส่วนต่อประสาน (เพราะคุณต้องย้อนกลับไปแก้ไขคลาสต่างๆ)

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


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

ตัวอย่างเช่นคุณสามารถใช้การมอบหมายเพื่อทำส่วนการตรวจสอบข้อผิดพลาดแทนการใช้การสืบทอด (ตัวอย่างของฉันอยู่ใน Javascript เพราะ O ไม่ทราบไวยากรณ์ของ Go ฉันหวังว่าคุณจะไม่สนใจ)

function make_command(real_command){
    return function(x){
        if(check_arguments(x)){
            return real_command(x);
        }else{
            //handle error here
        }
    }
 }

 command1 = make_command(function(x){ 
     //do something
 })

 command2 = make_command(function(x){
     //do something else
 })

 command1(17);
 commnad2(42);

แน่นอนตัวอย่างนี้สมมติว่ามีวิธีที่เหมาะสมในการใช้ฟังก์ชัน wrapper หรืออาร์กิวเมนต์ตรวจสอบคลาสแม่สำหรับทุกกรณี ขึ้นอยู่กับว่าสิ่งต่าง ๆ เป็นไปได้ง่ายกว่าการวางการเรียกไปที่ check_arguments ภายในคำสั่งด้วยตนเอง (เนื่องจากแต่ละคำสั่งอาจจำเป็นต้องเรียกใช้ฟังก์ชันการตรวจสอบด้วยอาร์กิวเมนต์ที่ต่างกันเนื่องจากจำนวนอาร์กิวเมนต์ที่แตกต่างกันประเภทคำสั่งต่าง ๆ ฯลฯ )

tl; dr:ไม่มีวิธีที่ดีที่สุดในการแก้ปัญหาทั้งหมด จากมุมมอง "การทำงานในสิ่งต่าง ๆ " ให้เน้นที่การสร้างนามธรรมของคุณในวิธีการบังคับใช้ค่าคงที่ที่สำคัญและปกป้องคุณจากความผิดพลาด จากมุมมอง "การพิสูจน์ในอนาคต" โปรดจำไว้ว่าส่วนใดของรหัสมีแนวโน้มที่จะขยายเพิ่ม


1

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

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

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

ดังนั้นจึงทำให้มันเป็นโมดูลสำหรับการทดสอบและควรทำให้โค้ดดีและเล็กซึ่งง่ายต่อการบำรุงรักษาในภายหลัง


0

คุณจะเลือกคำสั่ง / ฟังก์ชั่นของคุณได้อย่างไร?

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

นอกจากนี้การทดสอบแต่ละฟังก์ชั่นนั้นง่ายกว่าการแยกคำสั่งสวิตช์ขนาดใหญ่

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


คำสั่งเป็นสตริงที่ไม่ซ้ำกัน ฉันสามารถแมปเหล่านี้กับจำนวนเต็มถ้าจำเป็น
beatgammit

0

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

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

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

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