การออกแบบส่วนต่อประสานที่จำเป็นต้องเรียกใช้ฟังก์ชันในลำดับเฉพาะ


24

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

1) รวบรวมข้อมูลการกำหนดค่า สิ่งนี้สามารถเกิดขึ้นได้ในเวลาและสถานที่ต่างกัน ตัวอย่างเช่นโมดูล A และโมดูล B สามารถร้องขอ (ในเวลาที่ต่างกัน) ทรัพยากรบางอย่างจากโมดูลของฉัน 'ทรัพยากร' เหล่านั้นเป็นสิ่งที่กำหนดค่าจริง

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

3) หลังจากนั้นสามารถ (และต้อง) การกำหนดค่าโดยละเอียดของทรัพยากรดังกล่าวให้แล้วเสร็จ

4) นอกจากนี้หลังจาก 2) สามารถ (และต้อง) กำหนดเส้นทางของทรัพยากรที่เลือกไปยังผู้โทรที่ประกาศไว้


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


ขั้นที่ 1 ซึ่งจะดีกว่าเรียกว่าdiscoveryหรือhandshake?
ร. ว.

1
การมีเพศสัมพันธ์ชั่วคราวเป็นรูปแบบการต่อต้านและควรหลีกเลี่ยง

1
ชื่อของคำถามที่ทำให้ฉันคิดว่าคุณอาจจะสนใจในรูปแบบการสร้างขั้นตอน
Joshua Taylor

คำตอบ:


45

มันเป็นการออกแบบใหม่ แต่คุณสามารถป้องกันการใช้ API ในทางที่ผิดมากมาย แต่ไม่มีวิธีการใด ๆ ที่ไม่ควรเรียกใช้

ตัวอย่างเช่นแทนที่จะเป็น first you init, then you start, then you stop

ตัวสร้างของคุณinitเป็นวัตถุที่สามารถเริ่มต้นและstartสร้างเซสชันที่สามารถหยุดได้

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

ตอนนี้ใช้เทคนิคนั้นกับกรณีของคุณเอง


zlibและjpeglibเป็นสองตัวอย่างที่เป็นไปตามรูปแบบนี้สำหรับการเริ่มต้น ยังคงมีเอกสารจำนวนมากที่จำเป็นในการสอนแนวคิดให้กับนักพัฒนา
ร. ว.

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

2
นี้มีความคล้ายคลึงกับขั้นตอนการสร้างรูปแบบ ; นำเสนออินเทอร์เฟซที่สมเหตุสมผลในเฟสที่กำหนดเท่านั้น
Joshua Taylor

@JoshuaTaylor คำตอบของฉันเป็นขั้นตอนการสร้างรูปแบบการดำเนิน :)
Silviu Burcea

@SilviuBurcea คำตอบของคุณไม่ได้เป็นเครื่องมือสร้างขั้นตอน แต่ฉันจะแสดงความคิดเห็นมากกว่าที่นี่
Joshua Taylor

19

คุณสามารถให้วิธีการเริ่มต้นส่งคืนวัตถุที่เป็นพารามิเตอร์ที่จำเป็นในการกำหนดค่า:

ทรัพยากร * MyModule :: GetResource ();
MySession * MyModule :: Startup ();
void Resource :: Configure (เซสชัน MySession *);

แม้ว่าคุณMySessionจะเป็นเพียงโครงสร้างว่างเปล่าสิ่งนี้จะบังคับใช้ผ่านความปลอดภัยประเภทที่ไม่มีConfigure()วิธีการใดที่สามารถเรียกใช้ก่อนการเริ่มต้น


คนหยุดทำmodule->GetResource()->Configure(nullptr)อะไร
svick

@svick: ไม่มีอะไร แต่คุณต้องทำสิ่งนี้อย่างชัดเจน วิธีการนี้จะบอกคุณถึงสิ่งที่คาดหวังและหลีกเลี่ยงไม่ได้ว่าเป็นการตัดสินใจที่มีสติ เช่นเดียวกับภาษาโปรแกรมส่วนใหญ่ไม่มีใครขัดขวางไม่ให้คุณยิงตัวเองด้วยการเดินเท้า แต่การใช้ API จะเป็นการดีที่จะแสดงให้เห็นอย่างชัดเจนว่าคุณกำลังทำเช่นนั้น)
Michael Klement

+1 ดูดีและเรียบง่าย อย่างไรก็ตามฉันสามารถเห็นปัญหา ถ้าฉันมีวัตถุa, b, c, dฉันก็สามารถเริ่มaและใช้มันMySessionเพื่อพยายามที่จะใช้bเป็นวัตถุที่เริ่มต้นแล้ว แต่ในความเป็นจริงมันไม่ใช่
Vorac

8

การสร้างคำตอบของ Cashcow - ทำไมคุณต้องนำเสนอ Object ใหม่ให้กับผู้โทรเมื่อคุณสามารถนำเสนอ Interface ใหม่ได้? เปลี่ยนโฉมรูปแบบ:

class IStartable     { public: virtual IRunnable      start()     = 0; };
class IRunnable      { public: virtual ITerminateable run()       = 0; };
class ITerminateable { public: virtual void           terminate() = 0; };

นอกจากนี้คุณยังสามารถปล่อยให้ ITerminateable ใช้งาน IRunnable ได้หากเซสชันสามารถรันได้หลายครั้ง

เป้าหมายของคุณ:

class Service : IStartable, IRunnable, ITerminateable
{
  public:
    IRunnable      start()     { ...; return this; }
    ITerminateable run()       { ...; return this; }
    void           terminate() { ...; }
}

// And use it like this:
IStartable myService = Service();

// Now you can only call start() via the interface
IRunnable configuredService = myService.start();

// Now you can also call run(), because it is wrapped in the new interface...

ด้วยวิธีนี้คุณสามารถเรียกใช้วิธีการที่ถูกต้องเท่านั้นเนื่องจากคุณมีเฉพาะ IStartable-Interface ในตอนแรกและจะเรียกใช้เมธอด run () ได้เฉพาะเมื่อคุณเรียกใช้ start (); จากภายนอกดูเหมือนว่ารูปแบบที่มีหลายคลาสและวัตถุ แต่คลาสที่อยู่ภายใต้ชั้นหนึ่งซึ่งจะมีการอ้างอิงเสมอ


1
อะไรคือข้อได้เปรียบของการมีชั้นเรียนเพียงชั้นเดียวแทนที่จะเป็นหลายชั้นเรียน เนื่องจากนี่เป็นข้อแตกต่างเพียงอย่างเดียวกับวิธีที่ฉันเสนอฉันจะสนใจในประเด็นนี้โดยเฉพาะ
Michael Le Barbier Grünewald

1
@ MichaelGrünewaldไม่จำเป็นต้องใช้อินเทอร์เฟซทั้งหมดกับคลาสเดียว แต่สำหรับออบเจ็กต์ประเภทการกำหนดค่าอาจเป็นเทคนิคการนำไปใช้ที่ง่ายที่สุดในการแบ่งปันข้อมูลระหว่างอินสแตนซ์ของอินเทอร์เฟซ (เช่นเนื่องจากใช้ร่วมกัน วัตถุ).
Joshua Taylor

1
นี้เป็นหลักรูปแบบการสร้างขั้นตอน
Joshua Taylor

@JoshuaTaylor การแบ่งปันข้อมูลระหว่างอินสแตนซ์ของอินเทอร์เฟซนั้นทวีคูณ: ในขณะที่มันอาจจะง่ายกว่าในการนำไปใช้เราต้องระวังไม่ให้เข้าถึง“ สถานะที่ไม่ได้กำหนด” (เช่นการเข้าถึงที่อยู่ไคลเอนต์ของเซิร์ฟเวอร์ เนื่องจาก OP กำหนดให้เน้นการใช้งานอินเทอร์เฟซเราจึงสามารถตัดสินได้ว่าทั้งสองวิธีนั้นเท่ากัน ขอบคุณที่อ้างอิง "รูปแบบการสร้างขั้นตอน" BTW
Michael Le Barbier Grünewald

1
@ MichaelGrünewaldหากคุณโต้ตอบกับวัตถุผ่านอินเทอร์เฟซเฉพาะที่ระบุไว้ ณ จุดที่กำหนดไม่ควรมีวิธีใด ๆ (โดยไม่ต้องส่งข้อความ ฯลฯ ) เพื่อเข้าถึงสถานะนั้น
Joshua Taylor

2

มีวิธีการที่ถูกต้องมากมายในการแก้ปัญหาของคุณ Basile Starynkevitch เสนอวิธีการแบบ“ zero-bureaucracy” ซึ่งจะทำให้คุณมีส่วนต่อประสานที่เรียบง่ายและอาศัยโปรแกรมเมอร์ที่ใช้อินเทอร์เฟซที่เหมาะสม ในขณะที่ฉันชอบวิธีการนี้ฉันจะนำเสนออีกวิธีหนึ่งซึ่งมีประโยชน์มากกว่า แต่ให้คอมไพเลอร์พบข้อผิดพลาดบางอย่าง

  1. ระบุรัฐต่างๆอุปกรณ์ของคุณได้ในขณะที่Uninitialised, Started, Configuredและอื่น ๆ รายการจะต้องมีการ จำกัด ¹

  2. สำหรับแต่ละรัฐกำหนดstructถือข้อมูลเพิ่มเติมที่จำเป็นที่เกี่ยวข้องกับรัฐเช่นDeviceUninitialised, DeviceStartedและอื่น ๆ

  3. แพ็คทรีทเม้นต์ทั้งหมดในวัตถุเดียวDeviceStrategyที่เมธอดใช้โครงสร้างที่กำหนดใน 2 เป็นอินพุตและเอาต์พุต ดังนั้นคุณอาจมีDeviceStarted DeviceStrategy::start (DeviceUninitalised dev)วิธี (หรือสิ่งที่เทียบเท่าอาจเป็นไปตามการประชุมโครงการของคุณ)

ด้วยวิธีนี้โปรแกรมที่ถูกต้องจะต้องเรียกวิธีการบางอย่างในลำดับที่บังคับใช้โดยต้นแบบวิธีการ

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

ในขณะที่ฉันอธิบายใน 3. DeviceStrategyคลาสที่ไม่ซ้ำกันมีสถานการณ์ที่คุณอาจต้องการแยกการทำงานที่ให้ไว้ในหลาย ๆ คลาส

เพื่อสรุปประเด็นสำคัญของการออกแบบที่ฉันอธิบายคือ:

  1. เนื่องจากหลักการการทดแทนวัตถุที่เป็นตัวแทนของสถานะอุปกรณ์ควรชัดเจนและไม่มีความสัมพันธ์ในการสืบทอดพิเศษ

  2. แพ็คอุปกรณ์การรักษาในวัตถุ startegy มากกว่าในวัตถุที่เป็นตัวแทนของอุปกรณ์เพื่อให้แต่ละอุปกรณ์หรือสถานะอุปกรณ์มองเห็นตัวเองเท่านั้นและกลยุทธ์ที่เห็นทั้งหมดของพวกเขาและแสดงการเปลี่ยนแปลงที่เป็นไปได้ระหว่างพวกเขา

ฉันจะสาบานว่าฉันเห็นครั้งหนึ่งมีคำอธิบายเกี่ยวกับการติดตั้งไคลเอ็นต์ telnet ตามบรรทัดเหล่านี้ แต่ฉันไม่สามารถค้นหาได้อีก มันจะเป็นข้อมูลอ้างอิงที่มีประโยชน์มาก!

¹: สำหรับสิ่งนี้ให้ทำตามสัญชาตญาณของคุณหรือค้นหาคลาสที่เทียบเท่าของวิธีการในการใช้งานจริงของคุณสำหรับความสัมพันธ์“ method₁ ~ method₂” iff มันใช้งานได้กับวัตถุเดียวกัน” - สมมติว่าคุณมีวัตถุขนาดใหญ่ห่อหุ้มการรักษาทั้งหมดบนอุปกรณ์ของคุณ ทั้งสองวิธีในการระบุสถานะให้ผลลัพธ์ที่ยอดเยี่ยม


1
แทนที่จะนิยาม structs แยกกันมันอาจพอเพียงที่จะกำหนดอินเทอร์เฟซที่จำเป็นที่วัตถุในแต่ละขั้นตอนควรมีอยู่ จากนั้นก็จะเป็นรูปแบบการสร้างขั้นตอน
Joshua Taylor

2

ใช้รูปแบบการสร้าง

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

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


1

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

นอกจากนี้คุณยังอาจมีตัวแปรไลบรารีการดีบักซึ่งทำการตรวจสอบรันไทม์บางอย่าง

บางทีการกำหนดและจัดเก็บเอกสารอย่างถูกต้องการตั้งชื่อ (เช่นpreconfigure*, startup*, postconfigure*, run*.... )

BTW อินเทอร์เฟซที่มีอยู่จำนวนมากตามรูปแบบที่คล้ายกัน (เช่นชุดเครื่องมือ X11)


แผนภาพการเปลี่ยนสถานะคล้ายกับวงจรชีวิตของแอปพลิเคชัน Androidอาจมีความจำเป็นในการถ่ายทอดข้อมูล
ร. ว.

1

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

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


คุณหมายถึงอะไรเช่นนี้ ?
Vorac

1
public class Executor {

private Executor() {} // helper class

  public void execute(MyStepsRunnable r) {
    r.step1();
    r.step2();
    r.step3();
  }
}

interface MyStepsRunnable {

  void step1();
  void step2();
  void step3();
}

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


ในความคิดเห็นอื่นคุณเรียกสิ่งนี้ว่าการใช้เครื่องมือสร้างขั้นตอน แต่ไม่ใช่ หากคุณมีอินสแตนซ์ของ MyStepsRunnable คุณสามารถเรียกขั้นตอนที่ 3 ก่อนขั้นตอนที่ 1 การดำเนินการสร้างขั้นตอนที่จะมีมากขึ้นตามสายของideone.com/UDECgY แนวคิดนี้จะได้รับสิ่งที่มีขั้นตอน 2 โดยเรียกใช้ขั้นตอนที่ 1 ดังนั้นคุณถูกบังคับให้เรียกวิธีการในลำดับที่ถูกต้อง เช่นดูstackoverflow.com/q/17256627/1281433
Joshua Taylor

คุณสามารถแปลงเป็นคลาสนามธรรมด้วยวิธีการป้องกัน (หรือแม้แต่ค่าเริ่มต้น) เพื่อ จำกัด วิธีการใช้ คุณจะถูกบังคับให้ใช้ผู้ปฏิบัติการ แต่ฉันมีว่าอาจมีข้อบกพร่องหรือสองกับการใช้งานปัจจุบัน
Silviu Burcea

ที่ยังไม่ได้สร้างมันเป็นขั้นตอน ในรหัสของคุณไม่มีสิ่งใดที่ผู้ใช้สามารถทำได้เพื่อเรียกใช้รหัสระหว่างขั้นตอนต่าง ๆ แนวคิดนี้ไม่ได้เป็นเพียงแค่การเรียงลำดับโค้ด (ไม่ว่าจะเป็นสาธารณะหรือส่วนตัวหรือไม่ก็ตาม) step1(); step2(); step3();การแสดงรหัสของคุณที่ว่าพอเรื่องง่ายที่จะทำอย่างไรกับเพียง ตัวสร้างขั้นตอนคือการให้ API ที่เปิดเผยขั้นตอนบางอย่างและบังคับใช้ลำดับที่พวกเขาถูกเรียกใช้ ไม่ควรป้องกันโปรแกรมเมอร์จากการทำสิ่งอื่น ๆ ระหว่างขั้นตอน
Joshua Taylor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.