วิธีใดดีกว่าในการเรียกใช้เมธอดที่ใช้ได้กับคลาสหนึ่งเท่านั้นที่ใช้อินเทอร์เฟซ แต่ไม่ใช่อีกคลาสหนึ่ง?


11

โดยทั่วไปฉันต้องดำเนินการกระทำที่แตกต่างกันตามเงื่อนไขที่แน่นอน รหัสที่มีอยู่ถูกเขียนด้วยวิธีนี้

อินเตอร์เฟสพื้นฐาน

// DoSomething.java
interface DoSomething {

   void letDoIt(String info);
}

การใช้งานของพนักงานระดับแรก

class DoItThisWay implements DoSomething {
  ...
}

การใช้งานของคลาสคนงานที่สอง

class DoItThatWay implements DoSomething {
   ...
}

ชั้นหลัก

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          worker = new DoItThisWay();
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

รหัสนี้ใช้ได้ดีและเข้าใจง่าย

ตอนนี้เนื่องจากข้อกำหนดใหม่ฉันต้องส่งข้อมูลใหม่ที่เหมาะสมDoItThisWayเท่านั้น

คำถามของฉันคือ: สไตล์การเข้ารหัสต่อไปนี้ดีหรือไม่ที่จะจัดการกับข้อกำหนดนี้

ใช้ตัวแปรคลาสและเมธอดใหม่

// Use new class variable and method

class DoItThisWay implements DoSomething {
  private int quality;
  DoSomething() {
    quality = 0;
  }

  public void setQuality(int quality) {
    this.quality = quality;
  };

 public void letDoIt(String info) {
   if (quality > 50) { // make use of the new information
     ...
   } else {
     ...
   }
 } ;

}

ถ้าฉันทำเช่นนี้ฉันต้องทำการเปลี่ยนแปลงที่สอดคล้องกับผู้โทร:

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          DoItThisWay tmp = new DoItThisWay();
          tmp.setQuality(quality)
          worker = tmp;

        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

มันเป็นสไตล์การเขียนโปรแกรมที่ดีหรือไม่? หรือฉันจะทิ้งมันไปก็ได้

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          worker = new DoItThisWay();
          ((DoItThisWay) worker).setQuality(quality)
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

19
ทำไมไม่เพียงส่งผ่านqualityไปยังตัวสร้างเพื่อDoItThisWay?
David Arno

ฉันอาจจำเป็นต้องทบทวนคำถามของฉัน ... เพราะเหตุผลประสิทธิภาพการก่อสร้างDoItThisWayและจะทำครั้งเดียวในคอนสตรัคของDoItThatWay เป็นชนชั้นที่มีชีวิตยืนยาวและถูกเรียกหลายต่อหลายครั้ง MainMaindoingIt
Anthony Kong

ใช่การอัปเดตคำถามของคุณจะเป็นความคิดที่ดีในกรณีนั้น
David Arno

คุณหมายถึงว่าsetQualityวิธีนี้จะถูกเรียกหลายครั้งในช่วงอายุของDoItThisWayวัตถุหรือไม่?
jpmc26

1
ไม่มีความแตกต่างที่เกี่ยวข้องระหว่างสองเวอร์ชันที่คุณนำเสนอ จากมุมมองเชิงตรรกะพวกเขาทำสิ่งเดียวกัน
เซบาสเตียน

คำตอบ:


6

ฉันสมมติว่าqualityจะต้องตั้งข้างแต่ละโทรบนletDoIt()DoItThisWay()

ปัญหาที่ฉันเห็นที่เกิดขึ้นที่นี่คือ: คุณมีการแนะนำการมีเพศสัมพันธ์ชั่ว (คือสิ่งที่ happends ถ้าคุณลืมโทรsetQuality()ก่อนที่จะเรียกletDoIt()ในDoItThisWay?) และการใช้งานสำหรับDoItThisWayและDoItThatWayจะแยก (หนึ่งต้องได้รับการsetQuality()เรียกอื่น ๆ ไม่ได้)

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


15

มันเป็นสไตล์การเขียนโปรแกรมที่ดีหรือไม่?

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

interface DoSomethingWithQuality {
   void letDoIt(String info, int quality);
}

สิ่งนี้จะทำให้Mainค่อนข้างชอบ

class Main {
    // omitting the creation
    private DoSomething doSomething;
    private DoSomethingWithQuality doSomethingWithQuality;

    public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
            int quality = obtainQualityInfo();
            doSomethingWithQuality.letDoIt(info, quality);
        } else {
            doSomething.letDoIt(info);
        }
    }
}

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

class Main {
    private DoSomethingFactory doSomethingFactory;

     public doingIt(String info) {
         int quality = obtainQualityInfo();
         DoSomething doSomethingWorker = doSomethingFactory.Create(info, quality);
         doSomethingWorker.letDoIt();
     }
}

ฉันรู้ว่าคุณเขียน

เพราะด้วยเหตุผลด้านประสิทธิภาพการก่อสร้าง DoItThisWay และ DoItThatWay นั้นเสร็จสิ้นเพียงครั้งเดียวในตัวสร้างของ Main

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


โอ๊ะ, คุณกำลังสอดแนมฉันในขณะที่ฉันพิมพ์คำตอบไหม? เราทำประเด็นที่คล้ายกันนี้ (แต่คุณมีความเข้าใจที่ครอบคลุมและมีคารมคมคายมากขึ้น);
CharonX

1
@DocBrown ใช่มันเป็นที่ถกเถียงกันอยู่ แต่เนื่องจากDoItThisWayต้องการคุณภาพที่จะตั้งค่าก่อนที่จะเรียกletDoItมันจะทำงานแตกต่างกัน ฉันคาดหวังว่า a DoSomethingจะทำงานในลักษณะเดียวกัน (ไม่ได้ตั้งค่าคุณภาพก่อนหน้า) สำหรับตราสารอนุพันธ์ทั้งหมด มันสมเหตุสมผลหรือไม่ แน่นอนว่านี่เป็นพร่ามัวเพราะถ้าเราเรียกว่าsetQualityจากวิธีการจากโรงงานลูกค้าจะไม่เชื่อเรื่องพระเจ้าว่าควรจะตั้งคุณภาพหรือไม่
Paul Kertscher

2
@DocBrown ฉันจะถือว่าสัญญาletDoIt()เป็น (ขาดข้อมูลเพิ่มเติมใด ๆ ) "ฉันสามารถดำเนินการอย่างถูกต้อง (ทำสิ่งที่ฉันทำ) ถ้าคุณให้ฉันด้วยString info" การร้องขอที่setQuality()ถูกเรียกไว้ล่วงหน้าจะช่วยเสริมความแข็งแกร่งให้แก่เงื่อนไขการโทรletDoIt()ดังนั้นจึงเป็นการละเมิด LSP
CharonX

2
@ CharlieX: ถ้ามีตัวอย่างจะมีสัญญาที่บ่งบอกว่า " letDoIt" จะไม่ส่งข้อยกเว้นใด ๆ และลืมเรียกว่า "setQuality" จะทำให้letDoItเกิดข้อยกเว้นจากนั้นฉันก็เห็นด้วยการดำเนินการดังกล่าวจะละเมิด LSP แต่มันดูเหมือนสมมติฐานที่ประดิษฐ์ขึ้นสำหรับฉัน
Doc Brown

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

10

สมมติว่าqualityไม่สามารถส่งผ่านไปยังตัวสร้างและการเรียกไปยังsetQualityจำเป็น

ขณะนี้ข้อมูลโค้ดต้องการ

    int quality = obtainQualityInfo();
    worker = new DoItThisWay();
    ((DoItThisWay) worker).setQuality(quality);

มีขนาดเล็กเกินไปที่จะลงทุนกับความคิดที่มากเกินไป IMHO มันดูน่าเกลียดนิดหน่อย แต่ก็ไม่ยากที่จะเข้าใจ

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

    int quality = obtainQualityInfo();
    worker = CreateAWorkerForThisInfo();
    ((DoItThisWay) worker).setQuality(quality);

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

อย่างไรก็ตามฉันจะให้tmpชื่อที่ดีกว่า:

      int quality = obtainQualityInfo();
      DoItThisWay workerThisWay = new DoItThisWay();
      workerThisWay.setQuality(quality)
      worker = workerThisWay;

การตั้งชื่อดังกล่าวช่วยให้รหัสผิดดูผิด


tmp อาจชื่อที่ดียิ่งกว่าเช่น "คนงานที่มีคุณภาพ"
949300

1
@ user949300: IMHO นั้นไม่ใช่ความคิดที่ดี: สมมติว่าDoItThisWayได้รับพารามิเตอร์ที่เฉพาะเจาะจงมากขึ้น - โดยสิ่งที่คุณแนะนำชื่อจะต้องมีการเปลี่ยนแปลงทุกครั้ง จะเป็นการดีกว่าถ้าใช้ชื่อตัวแปรหรือตัวแปรซึ่งทำให้ชนิดของวัตถุชัดเจนไม่ใช่ชื่อของวิธีการใดที่ใช้ได้
Doc Brown

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

2

อินเทอร์เฟซทั่วไปที่การเตรียมใช้งานแตกต่างกันไปตามข้อมูลรันไทม์มักจะยืมตัวเองไปยังรูปแบบโรงงาน

DoThingsFactory factory = new DoThingsFactory(thingThatProvidesQuality);
DoSomething doSomething = factory.getDoer(info);

doSomething.letDoIt();

จากนั้น DoThingsFactory สามารถกังวลเกี่ยวกับการรับและการตั้งค่าข้อมูลคุณภาพภายในgetDoer(info)วิธีการก่อนที่จะส่งคืนวัตถุ DoThingsWithQuality ที่เป็นรูปธรรมให้กับส่วนติดต่อ DoSomething

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