รูปแบบสำหรับคลาสที่ทำสิ่งเดียวเท่านั้น


24

สมมติว่าฉันมีขั้นตอนที่ทำสิ่งต่าง ๆ :

void doStuff(initalParams) {
    ...
}

ตอนนี้ฉันค้นพบแล้วว่า ขั้นตอนที่จะมีขนาดใหญ่ผมแบ่งมันออกเป็นขั้นตอนขนาดเล็กหลาย ๆ และเร็ว ๆ นี้ฉันตระหนักดีว่ามีชนิดของรัฐจะเป็นประโยชน์ในขณะที่ทำสิ่งเพื่อที่ฉันจะต้องผ่านพารามิเตอร์น้อยระหว่างขั้นตอนเล็ก ๆ ดังนั้นฉันแยกมันออกเป็นคลาสของตัวเอง:

class StuffDoer {
    private someInternalState;

    public Start(initalParams) {
        ...
    }

    // some private helper procedures here
    ...
}

แล้วฉันจะเรียกมันว่า:

new StuffDoer().Start(initialParams);

หรือเช่นนี้

new StuffDoer(initialParams).Start();

และนี่คือสิ่งที่รู้สึกผิด เมื่อใช้. NET หรือ Java API ฉันไม่เคยโทรnew SomeApiClass().Start(...);ซึ่งทำให้ฉันสงสัยว่าฉันทำผิด แน่นอนว่าฉันสามารถทำให้คอนสตรัคเตอร์ของ StuffDoer เป็นส่วนตัวและเพิ่มวิธีช่วยแบบคงที่:

public static DoStuff(initalParams) {
    new StuffDoer().Start(initialParams);
}

แต่แล้วฉันก็มีคลาสที่อินเตอร์เฟซภายนอกประกอบด้วยวิธีคงที่เพียงวิธีเดียวซึ่งก็รู้สึกแปลก ๆ

ดังนั้นคำถามของฉัน: มีรูปแบบที่ดีขึ้นสำหรับชั้นเรียนประเภทนี้หรือไม่

  • มีจุดเข้าใช้งานเพียงจุดเดียวและ
  • ไม่มีสถานะ "ที่รู้จักภายนอก" กล่าวคือต้องมีสถานะอินสแตนซ์เฉพาะในระหว่างการดำเนินการของจุดเริ่มต้นนั้น

ฉันได้รับการรู้จักที่จะทำสิ่งต่าง ๆ เช่นbool arrayContainsSomestring = new List<string>(stringArray).Contains("somestring");เมื่อทั้งหมดที่ฉันสนใจคือข้อมูลบางส่วนและวิธีการขยาย LINQ ไม่สามารถใช้ได้ ใช้งานได้ดีและอยู่ในif()สภาพที่เหมาะสมโดยไม่จำเป็นต้องกระโดดผ่านห่วง แน่นอนคุณต้องการภาษาที่รวบรวมขยะหากคุณกำลังเขียนโค้ดเช่นนั้น
CVn

ถ้ามันเป็นผู้ไม่เชื่อเรื่องภาษาทำไมต้องเพิ่มjavaและc #ด้วย? :)
Steven Jeuris

ฉันอยากรู้อยากเห็นฟังก์ชั่นของ 'วิธีการหนึ่ง' นี้คืออะไร? มันจะช่วยได้อย่างมากในการกำหนดว่าวิธีที่ดีที่สุดในสถานการณ์เฉพาะ แต่คำถามที่น่าสนใจกระนั้น!
Steven Jeuris

@StevenJeuris: ฉันได้เจอปัญหานี้ในสถานการณ์ต่าง ๆ แต่ในกรณีปัจจุบันมันเป็นวิธีที่นำเข้าข้อมูลลงในฐานข้อมูลท้องถิ่น (โหลดจากเว็บเซิร์ฟเวอร์จัดการบางอย่างและเก็บไว้ในฐานข้อมูล)
Heinzi

1
หากคุณเรียกเมธอดเสมอเมื่อคุณสร้างอินสแตนซ์ทำไมไม่ทำให้มันเป็นตรรกะการเริ่มต้นภายในตัวสร้าง?
NoChance

คำตอบ:


29

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

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


1
ฟังดูเหมือนว่าฉันกำลังทำอะไรอยู่ ขอบคุณอย่างน้อยฉันมีชื่อตอนนี้ :-)
Heinzi

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

จิตวิญญาณน่าจะเป็นที่เหมาะสมป้องกันรูปแบบ นอกจากนี้ดูเหมือนว่าจะไม่ใช่รูปแบบ แต่เป็นวิธีการปรับโครงสร้างใหม่
Steven Jeuris

1
@ สตีเฟ่น Jeuris ฉันไม่คิดว่ามันเป็นรูปแบบการต่อต้าน แอนตี้ - แพทเทิร์นจะทำให้เกิดความยุ่งเหยิงกับวิธีการปรับเปลี่ยนพารามิเตอร์มากแทนที่จะเก็บสถานะนี้ซึ่งเป็นเรื่องธรรมดาระหว่างวิธีการเหล่านี้ในตัวแปรอินสแตนซ์
Alfredo Osorio

@AlfredoOsorio: ก็จะทำให้วิธีการ refactored มีจำนวนมากของพารามิเตอร์ พวกมันอาศัยอยู่ในวัตถุดังนั้นจึงเป็นการดีกว่าที่จะผ่านพวกมันทั้งหมดไปรอบ ๆ แต่มันแย่กว่า refactoring ไปยังส่วนประกอบที่สามารถนำมาใช้ซ้ำได้ซึ่งจำเป็นต้องมีพารามิเตอร์ย่อย ๆ ที่ จำกัด แต่ละตัวเท่านั้น
Jan Hudec

13

ฉันจะบอกว่าชั้นเรียนที่มีปัญหา (ใช้จุดเข้าเดียว) ได้รับการออกแบบมาเป็นอย่างดี มันใช้งานง่ายและขยาย ค่อนข้างทึบ

no "externally recognizable" stateสิ่งเดียวกันสำหรับ มันเป็น encapsulation ที่ดี

ฉันไม่เห็นปัญหาเกี่ยวกับรหัสเช่น:

var doer = new StuffDoer(initialParams);
var result = doer.Calculate(extraParams);

1
และแน่นอนถ้าคุณไม่จำเป็นต้องใช้เหมือนกันอีกครั้งที่ช่วยลดการdoer var result = new StuffDoer(initialParams).Calculate(extraParams);
CVn

คำนวณควรเปลี่ยนชื่อเป็น doStuff!
shabunc

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

2
คำตอบนี้ง่ายเกินไป เป็นStringComparerระดับที่คุณใช้เป็นnew StringComparer().Compare( "bleh", "blah" )ออกแบบมาอย่างดี? การสร้างรัฐเมื่อไม่มีความจำเป็นทั้งหมดล่ะ? เอาล่ะพฤติกรรมนั้นได้รับการห่อหุ้มอย่างดี (อย่างน้อยก็ให้กับผู้ใช้ของชั้นเรียนมากกว่านั้นในคำตอบของฉัน ) แต่นั่นไม่ได้ทำให้ 'การออกแบบที่ดี' เป็นค่าเริ่มต้น ตามตัวอย่าง 'ผู้กระทำผลลัพธ์ของคุณ สิ่งนี้จะสมเหตุสมผลถ้าคุณเคยใช้doerแยกจากresultกัน
Steven Jeuris

1
one reason to changeมันไม่ได้เป็นหนึ่งในเหตุผลที่จะมีชีวิตอยู่ก็ ความแตกต่างอาจดูบอบบาง คุณต้องเข้าใจความหมายของมัน มันจะไม่สร้างชั้นเรียนวิธีหนึ่ง
jgauffin

8

จากมุมมองหลักการ SOLID คำตอบ jgauffinเหมาะสม อย่างไรก็ตามคุณไม่ควรลืมเกี่ยวกับหลักการออกแบบทั่วไปเช่นที่หลบซ่อนข้อมูล

ฉันเห็นปัญหาหลายประการด้วยวิธีการที่กำหนด:

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

ข้อมูลอ้างอิงบางส่วนที่เกี่ยวข้อง

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

การอ้างอิงที่เกี่ยวข้องอื่นที่เกี่ยวข้องกับหัวข้อนี้: "แบ่งโปรแกรมของคุณเป็นวิธีการที่ทำงานหนึ่งที่สามารถระบุตัวได้เก็บการดำเนินการทั้งหมดไว้ในวิธีการในระดับเดียวกันของนามธรรม " - Kent Beck Key นี่คือ "ระดับที่เป็นนามธรรมของสิ่งเดียวกัน" นั่นไม่ได้หมายความว่า "สิ่งหนึ่ง" เนื่องจากมักถูกตีความ ระดับของสิ่งที่เป็นนามธรรมนี้ขึ้นอยู่กับบริบทที่คุณออกแบบ

ดังนั้นวิธีการที่เหมาะสมคืออะไร?

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

new TupleList<Key, int>
{
    { Key.NumPad1, 1 },
            ...
    { Key.NumPad3, 16 },
    { Key.NumPad4, 17 },
}
    .ForEach( t =>
    {
        var trigger = new IC.Trigger.EventTrigger(
                        new KeyInputCondition( t.Item1, KeyInputCondition.KeyState.Down ) );
        trigger.ConditionsMet += () => AddMarker( t.Item2 );
        _inputController.AddTrigger( trigger );
    } );

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

ทางเลือกที่เป็นไปได้

  • ใน C # คุณสามารถใช้วิธีการขยายแทน ดังนั้นดำเนินการกับข้อโต้แย้งโดยตรงที่คุณผ่านวิธีการ 'สิ่งหนึ่ง' นี้
  • ดูว่าฟังก์ชั่นนี้ไม่ได้อยู่ในคลาสอื่นจริงหรือไม่
  • ทำให้มันเป็นฟังก์ชั่นแบบคงที่ในระดับคงที่ สิ่งนี้น่าจะเป็นแนวทางที่เหมาะสมที่สุดตามที่ปรากฏใน API ทั่วไปที่คุณอ้างถึง

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

4

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


ว้าว! คุณสามารถที่จะเข้าใจมันได้มากกว่าที่ฉันคิดไว้ :) ขอบคุณ (แน่นอนฉันไปไกลกว่าที่เราอาจไม่เห็นด้วย แต่ฉันเห็นด้วยกับสถานที่ทั่วไป)
Steven Jeuris

2
new StuffDoer().Start(initialParams);

มีปัญหากับนักพัฒนาที่ไม่มีส่วนร่วมที่สามารถใช้อินสแตนซ์ที่แชร์และใช้งานได้

  1. หลาย ๆ ครั้ง (ตกลงถ้าการดำเนินการก่อนหน้านี้ (อาจเป็นบางส่วน) ไม่ทำให้การทำงานในภายหลังยุ่งเหยิง)
  2. จากหลายกระทู้ (ไม่ตกลงคุณบอกอย่างชัดเจนว่ามันมีสถานะภายใน)

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

การซ่อนไว้ในเมธอดแบบสแตติกช่วยในเรื่องนี้เพราะการใช้อินสแตนซ์ซ้ำจะไม่เกิดขึ้นอีก

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

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


จุดที่ดี หวังว่าคุณจะไม่รังเกียจการล้างข้อมูล
Steven Jeuris

2

นอกจากคำตอบอื่น ๆ ที่มีความซับซ้อนและเป็นส่วนตัวของฉันแล้วฉันรู้สึกว่าการสังเกตต่อไปนี้สมควรได้รับคำตอบแยกต่างหาก

มีข้อบ่งชี้ว่าคุณอาจจะมีต่อไปนี้เป็นจิตวิญญาณต่อต้านรูปแบบ

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

อาการและผลที่ตามมา

  • เส้นทางการนำทางที่ซ้ำซ้อน
  • ความสัมพันธ์ชั่วคราว
  • ชนชั้นไร้สัญชาติ
  • วัตถุและคลาสชั่วคราวระยะสั้น
  • คลาสการดำเนินการเดี่ยวที่มีอยู่เฉพาะกับ "seed" หรือ "เรียกใช้" คลาสอื่นผ่านการเชื่อมโยงชั่วคราว
  • คลาสที่มีชื่อการดำเนินการ“ เหมือนกับการควบคุม” เช่น start_process_alpha

โซลูชันที่ปรับโครงสร้างใหม่

Ghostbusters แก้ปัญหา Poltergeists โดยลบออกจากลำดับชั้นของคลาสทั้งหมด อย่างไรก็ตามหลังจากการลบออกแล้วฟังก์ชันการทำงานที่“ จัดเตรียมไว้” โดย poltergeist จะต้องถูกแทนที่ สิ่งนี้เป็นเรื่องง่ายด้วยการปรับแต่งอย่างง่ายเพื่อแก้ไขสถาปัตยกรรม


0

มีหลายรูปแบบที่สามารถพบได้ในแคตตาล็อก P of EAA ของ Martin Fowler ;

  1. สคริปต์ธุรกรรม : ค่อนข้างคล้ายกับวิธีที่คุณกล่าวถึง
  2. แทนที่เมธอด w / เมธอดอ็อบเจ็กต์ : ดูเหมือนว่าจะเป็นวิธีที่ดีเช่นเดียวกับ @anon ที่กล่าวถึง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.