วิธีใดเป็นวิธีปฏิบัติที่ดีกว่าสำหรับตัวอย่างหรือแบบสถิต?


48

คำถามนี้เป็นเรื่องส่วนตัว แต่ฉันแค่อยากรู้ว่าโปรแกรมเมอร์ส่วนใหญ่เข้าหานี้อย่างไร ตัวอย่างด้านล่างเป็นแบบหลอก -C # แต่ควรใช้กับ Java, C ++ และภาษา OOP อื่น ๆ ด้วย

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

class Foo
{
  Bar _bar;

  public void DoSomethingWithBar()
  {
    // Method Call #1.
    DoSomethingWithBarImpl();

    // Method Call #2.
    DoSomethingWithBarImpl(_bar);
  }

  private void DoSomethingWithBarImpl()
  {
    _bar.DoSomething();
  }

  private static void DoSomethingWithBarImpl(Bar bar)
  {
    bar.DoSomething();
  }
}

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

คุณต้องการทำอะไรในรหัสของคุณเองและเหตุผลของคุณในการทำเช่นนั้น?


3
ฉันจะลบ C ++ ออกจากคำถามนี้: ใน C ++ เห็นได้ชัดว่าคุณจะทำให้เป็นวิธีฟรีเนื่องจากไม่มีจุดที่มันถูก stashed โดยพลการในวัตถุ: มันปิดการใช้งาน ADL
Matthieu M.

1
@MatthieuM: วิธีฟรีหรือไม่มันไม่สำคัญ เจตนาของคำถามของฉันคือการถามว่ามีข้อได้เปรียบในการประกาศวิธีการช่วยเหลือเป็นวิธีการตัวอย่างหรือไม่ ดังนั้นใน C ++ ตัวเลือกอาจเป็นวิธีการอินสแตนซ์กับวิธีการ / ฟังก์ชันฟรี คุณมีจุดที่ดีเกี่ยวกับ ADL คุณกำลังบอกว่าคุณต้องการใช้วิธีการฟรีหรือไม่ ขอบคุณ!
Ilian

2
สำหรับ C ++ แนะนำให้ใช้วิธีฟรี พวกเขาเพิ่มการห่อหุ้ม (ทางอ้อมเป็นเพียงสามารถเข้าถึงส่วนต่อประสานสาธารณะ) และยังเล่นได้ดีเพราะ ADL (โดยเฉพาะในแม่แบบ) อย่างไรก็ตามฉันไม่ทราบว่าสิ่งนี้ใช้ได้กับ Java / C #, C ++ แตกต่างอย่างมากจาก Java / C # ดังนั้นฉันคาดว่าคำตอบจะแตกต่างกัน (และฉันไม่คิดว่าจะขอ 3 ภาษาด้วยกัน)
Matthieu M.


1
โดยไม่ต้องพยายามทำให้รุนแรงขึ้นใครฉันไม่เคยได้ยินเหตุผลที่ดีที่จะไม่ใช้วิธีการคงที่ ฉันใช้มันทุกที่ที่ทำได้เพราะพวกเขาทำให้มันชัดเจนมากขึ้นว่าวิธีการทำอะไร
Sridhar Sarnobat

คำตอบ:


23

สิ่งนี้ขึ้นอยู่กับ หากค่าที่ผู้ช่วยของคุณดำเนินการเป็นสิ่งพื้นฐานวิธีการคงที่เป็นตัวเลือกที่ดีตามที่Péterชี้ให้เห็น

หากพวกเขามีความซับซ้อนแล้วSOLIDใช้มากขึ้นโดยเฉพาะSที่ผมและD

ตัวอย่าง:

class CookieJar {
      function takeCookies(count:Int):Array<Cookie> { ... }
      function countCookies():Int { ... }
      function ressuplyCookies(cookies:Array<Cookie>
      ... // lot of stuff we don't care about now
}

class CookieFan {
      function getHunger():Float;
      function eatCookies(cookies:Array<Cookie>):Smile { ... }
}

class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieJar;
      function makeEveryBodyAsHappyAsPossible():Void {
           //perform a lot of operations on jake, jane and the cookies
      }
      public function cookieTime():Void {
           makeEveryBodyAsHappyAsPossible();
      }
}

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

interface CookieDistributor {
    function distributeCookies(to:Array<CookieFan>):Array<Smile>;
}
class HappynessMaximizingDistributor implements CookieDistributor {
    var jar:CookieJar;
    function distributeCookies(to:Array<CookieFan>):Array<Smile> {
         //put the logic of makeEveryBodyAsHappyAsPossible here
    }
}
//and make a change here
class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieDistributor;

      public function cookieTime():Void {
           cookies.distributeCookies([jake, jane]);
      }
}

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

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

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

ดังนั้นขึ้นอยู่กับปัญหาของคุณมีหลายวิธีที่จะไปเกี่ยวกับเรื่องนี้


18

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

มีข้อ จำกัด ที่ร้ายแรงสำหรับวิธีนี้แม้ว่า: วิธีการ / คลาสนั้นไม่สามารถล้อเลียนได้ง่าย (แม้ว่า AFAIK อย่างน้อยสำหรับ C # จะมีกรอบการเยาะเย้ยซึ่งสามารถทำได้แม้จะเป็นเช่นนี้ แต่ก็ไม่ใช่เรื่องธรรมดาและอย่างน้อยก็เป็น พาณิชย์) ดังนั้นถ้าวิธีการช่วยเหลือมีการพึ่งพาภายนอกใด ๆ (เช่น DB) ซึ่งทำให้มัน - จึงโทรติดต่อของ - ยากที่จะทดสอบหน่วยมันจะดีกว่าที่จะประกาศว่ามันไม่คงที่ วิธีนี้ช่วยให้สามารถทำการฉีดแบบพึ่งพาได้ซึ่งทำให้ผู้เรียกใช้วิธีทดสอบหน่วยง่ายขึ้น

ปรับปรุง

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

ในกรณีนี้ฉันรู้สึกถึงกลิ่นของรหัส: วิธีการของคลาส A ซึ่งทำงานบนอินสแตนซ์ของคลาส B อาจมีสถานที่ที่ดีกว่าในคลาส B แต่จริง ๆ แล้วถ้าฉันตัดสินใจที่จะเก็บไว้ในที่ที่มันอยู่ฉันชอบตัวเลือก 1 มันง่ายกว่าและอ่านง่ายกว่า


คุณไม่เพียงแค่ผ่านการพึ่งพาวิธีการผู้ช่วยคงที่?
Ilian

1
@JackOfAllTrades หากจุดประสงค์เดียวของวิธีนี้คือการจัดการกับทรัพยากรภายนอกแทบจะไม่มีจุดใด ๆ ที่ฉีดการพึ่งพานั้น มิฉะนั้นอาจเป็นวิธีการแก้ปัญหา (แม้ว่าในกรณีหลังวิธีการอาจแบ่งหลักการความรับผิดชอบเดี่ยวดังนั้นจึงเป็นตัวเลือกสำหรับการปรับโครงสร้างใหม่)
PéterTörök

1
สถิตศาสตร์ 'mocked' ได้ง่าย - Microsoft Moles หรือ Fakes (ขึ้นอยู่กับ VS2010 หรือ VS2012 +) ช่วยให้คุณสามารถแทนที่วิธีคงที่ด้วยการใช้งานของคุณเองในการทดสอบของคุณ ทำให้กรอบการเยาะเย้ยแบบเก่าล้าสมัย (มันยังทำ mocks แบบดั้งเดิมด้วยและสามารถเลียนแบบคลาสคอนกรีตได้เช่นกัน) มันเป็นอิสระจาก MS ผ่านผู้จัดการส่วนขยาย
gbjbaanb

4

คุณต้องการทำอะไรในรหัสของคุณเอง

ฉันชอบที่ 1 ( this->DoSomethingWithBarImpl();) static t_image OpenDefaultImage()เว้นแต่ของหลักสูตรวิธีการช่วยเหลือไม่จำเป็นต้องเข้าถึงข้อมูลของอินสแตนซ์

และเหตุผลของคุณในการทำเช่นนั้นคืออะไร?

ส่วนต่อประสานสาธารณะและการใช้งานส่วนตัวและสมาชิกแยกกัน โปรแกรมจะอ่านยากกว่านี้หากฉันเลือก # 2 ด้วย # 1 ฉันจะมีสมาชิกและอินสแตนซ์ที่พร้อมใช้งานเสมอ เมื่อเทียบกับการใช้งานตาม OO มากมายฉันมักจะใช้ encapsualtion และสมาชิกน้อยมากต่อชั้น เท่าที่มีผลข้างเคียง - ฉันโอเคกับที่มักจะมีการตรวจสอบของรัฐที่ขยายการพึ่งพา (เช่นข้อโต้แย้งหนึ่งจะต้องผ่าน)

# 1 นั้นง่ายกว่าในการอ่านบำรุงรักษาและมีลักษณะการทำงานที่ดี แน่นอนว่าจะมีกรณีที่คุณสามารถแบ่งปันการใช้งานได้:

static void ValidateImage(const t_image& image);
void validateImages() const {
    ValidateImage(this->innerImage());
    ValidateImage(this->outerImage());
}

ถ้า # 2 เป็นวิธีแก้ปัญหาทั่วไปที่ดี (เช่นค่าเริ่มต้น) ใน codebase ฉันจะกังวลเกี่ยวกับโครงสร้างของการออกแบบ: ชั้นเรียนทำมากเกินไปหรือไม่ มีแค็ปซูลไม่เพียงพอหรือนำมาใช้ซ้ำได้ไม่เพียงพอ? ระดับนามธรรมสูงเพียงพอหรือไม่

สุดท้าย # 2 ดูเหมือนซ้ำซ้อนหากคุณใช้ภาษา OO ที่รองรับการกลายพันธุ์ (เช่นconstวิธีการ)

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