วิธีการเก็บรักษาอาร์กิวเมนต์นับเป็นวิธีที่ต่ำและยังคงแยกการพึ่งพาของบุคคลที่สามได้อย่างไร


13

ฉันใช้ห้องสมุดบุคคลที่สาม พวกเขาส่งPOJOให้ฉันซึ่งอาจนำไปใช้ในจุดประสงค์และวัตถุประสงค์ของเราเช่นนี้:

public class OurData {
  private String foo;
  private String bar;
  private String baz;
  private String quux;
  // A lot more than this

  // IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
  OurData(/* I don't know what they do */) {
    // some stuff
  }

  public String getFoo() {
    return foo;
  }

  // etc.
}

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

public class DataTypeOne implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
  }
}

public class DataTypeTwo implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz, String quux) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
    this.quux = quux;
  }
}

แล้วนี่:

public class ThirdPartyAdapter {
  public static makeMyData(OurData data) {
    if(data.getQuux() == null) {
      return new DataTypeOne(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
      );
    } else {
      return new DataTypeTwo(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
        data.getQuux();
      );
  }
}

คลาสอะแด็ปเตอร์นี้ประกอบกับคลาสอื่น ๆ ที่ต้องรู้เกี่ยวกับ API ของบุคคลที่สามซึ่งจำกัดความแพร่หลายในระบบของฉัน อย่างไรก็ตาม ... โซลูชันนี้คือ GROSS! ในรหัสที่สะอาดหน้า 40:

มากกว่าสามข้อโต้แย้ง (polyadic) ต้องมีเหตุผลพิเศษมาก - และไม่ควรใช้ต่อไป

สิ่งที่ฉันได้พิจารณา:

  • การสร้างวัตถุจากโรงงานแทนที่จะเป็นวิธีผู้ช่วยคงที่
    • ไม่ได้แก้ปัญหาการมีข้อโต้แย้งเป็นพันล้าน
  • การสร้างคลาสย่อยของ DataTypeOne และ DataTypeTwo ที่มี Constructor ที่ต้องพึ่งพา
    • ยังมีคอนสตรัคที่มีการป้องกันแบบ polyadic
  • สร้างการใช้งานแยกกันอย่างสิ้นเชิงที่สอดคล้องกับอินเตอร์เฟซเดียวกัน
  • หลายความคิดข้างต้นพร้อมกัน

สถานการณ์นี้ควรได้รับการจัดการอย่างไร?


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

  • ฉันไม่ต้องการให้โครงสร้างข้อมูลของฉันมี import com.third.party.library.SomeDataStructure;
  • ฉันไม่สามารถสร้างโครงสร้างข้อมูลในกรณีทดสอบได้
  • โซลูชันปัจจุบันของฉันส่งผลให้มีการนับอาร์กิวเมนต์สูงมาก ฉันต้องการให้การนับมีค่าน้อยโดยไม่ผ่านโครงสร้างข้อมูลของพวกเขา
  • คำถามที่เป็น " สิ่งที่เป็นชั้นป้องกันการทุจริต?" คำถามของฉันคือ " วิธีการที่ฉันสามารถใช้รูปแบบรูปแบบใด ๆ ที่จะแก้สถานการณ์นี้?"

ฉันไม่ได้ขอรหัสอย่างใดอย่างหนึ่ง (มิฉะนั้นคำถามนี้จะอยู่ใน SO) เพียงแค่ขอคำตอบที่เพียงพอเพื่อให้ฉันเขียนรหัสได้อย่างมีประสิทธิภาพ (ซึ่งคำถามนั้นไม่ได้ให้)


หากมี POJO บุคคลที่สามหลายรายอาจเป็นความพยายามในการเขียนรหัสทดสอบที่กำหนดเองซึ่งใช้แผนที่ที่มีอนุสัญญาบางฉบับ (เช่นตั้งชื่อคีย์ int_bar) เป็นอินพุตทดสอบของคุณ หรือใช้ JSON หรือ XML ด้วยรหัสตัวกลางที่กำหนดเอง มีผลให้เรียงลำดับ DSL สำหรับทดสอบ com.thirdparty
949300

อ้างเต็มจากรหัสสะอาด:The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
Lilienthal

11
การปฏิบัติตามรูปแบบหรือแนวทางการเขียนโปรแกรมแบบตาบอดนั้นเป็นการต่อต้านรูปแบบของตนเอง
Lilienthal

2
"การห่อหุ้ม API ของพวกเขาและอำนวยความสะดวกในการทดสอบหน่วย" เสียงเช่นนี้อาจเป็นกรณีของการทดสอบที่มากเกินไปและ / หรือความเสียหายในการออกแบบที่เกิดจากการทดสอบกับฉัน (หรือบ่งบอกว่าคุณสามารถออกแบบสิ่งนี้ให้แตกต่างกัน ถามตัวเองนี้นี้ไม่ได้จริงๆให้รหัสของคุณง่ายต่อการเข้าใจและการเปลี่ยนแปลงและนำมาใช้? ฉันจะเอาเงินของฉันไปที่ "ไม่" มีความเป็นไปได้จริงไหมที่คุณจะเปลี่ยนห้องสมุดนี้เป็นครั้งแรก อาจจะไม่มาก ถ้าคุณทำมันออกมาแลกเปลี่ยนนี้ไม่ได้จริงๆทำให้มันง่ายที่จะลดลงหนึ่งที่แตกต่างกันอย่างสิ้นเชิงในสถานที่? อีกครั้งฉันจะเดิมพันที่ "ไม่"
jpmc26

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

คำตอบ:


10

กลยุทธ์ที่ฉันใช้เมื่อมีพารามิเตอร์เริ่มต้นหลายตัวคือการสร้างประเภทที่เพิ่งมีพารามิเตอร์สำหรับการเริ่มต้น

public class DataTypeTwoParameters {
    public String foo;  // use getters/setters instead if it's appropriate
    public int bar;
    public double baz;
    public String quuz;
}

จากนั้นตัวสร้างสำหรับ DataTypeTwo ใช้วัตถุ DataTypeTwoParameters และ DataTypeTwo จะถูกสร้างผ่าน:

DataTypeTwoParameters p = new DataTypeTwoParameters();
p.foo = "Hello";
p.bar = 4;
p.baz = 3;
p.quuz = "World";

DataTypeTwo dtt = new DataTypeTwo(p);

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


แนวทางที่น่าสนใจ คุณจะนำเรื่องที่เกี่ยวข้องไปInteger.parseIntที่ไหน? ใน setter หรือนอกคลาสพารามิเตอร์?
durron597

5
นอกคลาสพารามิเตอร์ คลาสพารามิเตอร์ควรเป็นวัตถุ "โง่" และไม่ควรพยายามทำสิ่งใดนอกจากแสดงว่าอินพุตและประเภทที่ต้องการคืออะไร ควรทำการแยกวิเคราะห์ที่อื่นเช่น: p.bar = Integer.parseInt("4").
Erik

7
ฟังดูเหมือนรูปแบบวัตถุพารามิเตอร์
ริ้น

9
... หรือรูปแบบการต่อต้าน
Telastyn

1
... หรือคุณก็สามารถเปลี่ยนชื่อไปDataTypeTwoParameters DataTypeTwo
253751

14

คุณมีข้อกังวลสองข้อแยกกันที่นี่: การตัด API และทำให้การนับอาร์กิวเมนต์ต่ำ

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

จากนั้นเมื่อคุณมี API ที่คุณต้องการการนับพารามิเตอร์อาจไม่เป็นปัญหาอีกต่อไป หากเป็นเช่นนั้นมีหลายรูปแบบทั่วไปที่ต้องพิจารณา:

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

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

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

public class DataTypeTwo implements DataInterface {
  private OurData data;

  public DataTypeOne(OurData data) {
    this.data = data;
  }

   public String getFoo() {
    return data.getFoo();
  }

  public int getBar() {
    return Integer.parseInt(data.getBar());
  }
  ...
}

ครึ่งแรกของคำตอบนี้ดีมากมีประโยชน์มาก +1 ครึ่งหลังของคำตอบนี้: "ส่งผ่านไปยัง API พื้นฐานโดยการจัดเก็บการอ้างอิงไปยังOurDataวัตถุ" - นี่คือสิ่งที่ฉันพยายามหลีกเลี่ยงอย่างน้อยในชั้นฐานเพื่อให้แน่ใจว่าไม่มีการพึ่งพา
durron597

1
นั่นเป็นเหตุผลที่คุณทำในหนึ่งในการใช้งานของDataInterfaceคุณ คุณสร้างการใช้งานอื่นสำหรับวัตถุจำลองของคุณ
Karl Bielefeldt

@ durron597: ใช่ แต่คุณรู้วิธีแก้ปัญหาแล้วถ้ามันรบกวนจิตใจคุณจริงๆ
Doc Brown

1

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

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

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


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