JavaRush /จาวาบล็อก /Random-TH /รูปแบบการออกแบบ: ซิงเกิลตัน

รูปแบบการออกแบบ: ซิงเกิลตัน

เผยแพร่ในกลุ่ม
สวัสดี! วันนี้เราจะมาดูรูปแบบการออกแบบต่างๆ ให้ละเอียดยิ่งขึ้น และเราจะเริ่มต้นด้วยรูปแบบซิงเกิลตัน ซึ่งเรียกอีกอย่างว่า "ซิงเกิลตัน" รูปแบบการออกแบบ: ซิงเกิลตัน - 1จำไว้ว่า: เรารู้อะไรเกี่ยวกับรูปแบบการออกแบบโดยทั่วไปบ้าง? รูปแบบการออกแบบเป็นแนวทางปฏิบัติที่ดีที่สุดที่สามารถปฏิบัติตามได้เพื่อแก้ไขปัญหาที่ทราบจำนวนหนึ่ง โดยทั่วไปรูปแบบการออกแบบจะไม่เชื่อมโยงกับภาษาการเขียนโปรแกรมใดๆ ถือเป็นชุดคำแนะนำซึ่งคุณสามารถหลีกเลี่ยงข้อผิดพลาดและไม่สร้างล้อใหม่ได้

ซิงเกิลตันคืออะไร?

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

  2. จัดเตรียมจุดเข้าใช้งานส่วนกลางให้กับอินสแตนซ์ของคลาสนี้

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

  2. วิธีการคงสาธารณะที่ส่งกลับอินสแตนซ์ของชั้นเรียน วิธีการนี้เรียกgetInstanceว่า นี่คือจุดเข้าใช้งานส่วนกลางไปยังอินสแตนซ์ของคลาส

ตัวเลือกการดำเนินงาน

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

  • ความเรียบง่ายและโปร่งใสของโค้ด: แน่นอนว่าการวัดผลเป็นเรื่องส่วนตัว แต่มีความสำคัญ

  • ความปลอดภัยของเธรด: ทำงานอย่างถูกต้องในสภาพแวดล้อมแบบมัลติเธรด

  • ประสิทธิภาพสูงในสภาพแวดล้อมแบบมัลติเธรด: เธรดจะบล็อกซึ่งกันและกันน้อยที่สุดหรือไม่บล็อกเลยเมื่อแชร์ทรัพยากร

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

  • ความซับซ้อนและการอ่านโค้ดไม่ดี การวัดยังเป็นแบบอัตนัย เราจะถือว่าถ้าเลือดไหลออกจากดวงตา การนำไปปฏิบัติก็เป็นเช่นนั้น

  • ขาดความปลอดภัยของด้าย กล่าวอีกนัยหนึ่งคือ "อันตรายจากด้าย" การดำเนินการไม่ถูกต้องในสภาพแวดล้อมแบบมัลติเธรด

  • ประสิทธิภาพต่ำในสภาพแวดล้อมแบบมัลติเธรด: เธรดบล็อกซึ่งกันและกันตลอดเวลาหรือบ่อยครั้งเมื่อมีการแบ่งปันทรัพยากร

รหัส

ตอนนี้เราพร้อมที่จะพิจารณาตัวเลือกการใช้งานต่างๆ โดยระบุข้อดีและข้อเสีย:

วิธีแก้ปัญหาง่ายๆ

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
การใช้งานที่ง่ายที่สุด ข้อดี:
  • ความเรียบง่ายและความโปร่งใสของรหัส

  • ความปลอดภัยของด้าย

  • ประสิทธิภาพสูงในสภาพแวดล้อมแบบมัลติเธรด

ข้อเสีย:
  • ไม่ใช่การเริ่มต้นแบบขี้เกียจ
ในความพยายามที่จะแก้ไขข้อบกพร่องสุดท้าย เราได้รับการใช้งานหมายเลขสอง:

การเริ่มต้นแบบ Lazy

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
ข้อดี:
  • การเริ่มต้นแบบ Lazy

ข้อเสีย:
  • ไม่ปลอดภัยต่อเธรด

การนำไปปฏิบัติมีความน่าสนใจ เราสามารถเริ่มต้นได้อย่างเกียจคร้าน แต่เราสูญเสียความปลอดภัยของเธรด ไม่มีปัญหา: ในการใช้งานหมายเลข 3 เราซิงโครไนซ์ทุกอย่าง

ตัวเข้าถึงแบบซิงโครไนซ์

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
ข้อดี:
  • การเริ่มต้นแบบ Lazy

  • ความปลอดภัยของด้าย

ข้อเสีย:
  • ประสิทธิภาพต่ำในสภาพแวดล้อมแบบมัลติเธรด

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

ตรวจสอบการล็อคสองครั้ง

public class Singleton {
    private static Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
ข้อดี:
  • การเริ่มต้นแบบ Lazy

  • ความปลอดภัยของด้าย

  • ประสิทธิภาพสูงในสภาพแวดล้อมแบบมัลติเธรด

ข้อเสีย:
  • ไม่รองรับ Java เวอร์ชันต่ำกว่า 1.5 (คำหลักระเหยได้รับการแก้ไขในเวอร์ชัน 1.5)

ฉันทราบว่าเพื่อให้ตัวเลือกการใช้งานนี้ทำงานได้อย่างถูกต้อง จำเป็นต้องมีเงื่อนไขข้อใดข้อหนึ่งจากสองข้อ ตัวแปรINSTANCEต้องเป็นอย่างใดอย่างหนึ่งfinalหรือ volatileการใช้งานครั้งสุดท้ายที่เราจะพูดถึงในวันนี้Class Holder Singletonคือ

คลาสโฮลเดอร์ ซิงเกิลตัน

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
ข้อดี:
  • การเริ่มต้นแบบ Lazy

  • ความปลอดภัยของด้าย

  • ประสิทธิภาพสูงในสภาพแวดล้อมแบบมัลติเธรด

ข้อเสีย:
  • เพื่อการดำเนินการที่ถูกต้อง จำเป็นต้องรับประกันว่าคลาสอ็อบเจ็กต์Singletonจะเริ่มต้นได้โดยไม่มีข้อผิดพลาด มิฉะนั้น การเรียกเมธอดแรกgetInstanceจะสิ้นสุดด้วยข้อผิดพลาดExceptionInInitializerErrorและการเรียกเมธอดครั้งต่อๆ ไปทั้งหมดจะล้มNoClassDefFoundErrorเหลว

การนำไปปฏิบัติเกือบจะสมบูรณ์แบบ และขี้เกียจและปลอดภัยต่อเธรดและรวดเร็ว แต่มีความแตกต่างกันนิดหน่อยที่อธิบายไว้ในเครื่องหมายลบ ตารางเปรียบเทียบการใช้งานรูปแบบซิงเกิลตันต่างๆ:
การนำไปปฏิบัติ การเริ่มต้นแบบ Lazy ความปลอดภัยของด้าย ความเร็วมัลติเธรด ควรใช้เมื่อใด?
วิธีแก้ปัญหาง่ายๆ - - + เร็ว ไม่เคย. หรือเมื่อการเริ่มต้นแบบขี้เกียจไม่สำคัญ แต่ไม่เคยดีกว่า
การเริ่มต้นแบบ Lazy + - - ไม่สามารถใช้ได้ เสมอเมื่อไม่จำเป็นต้องใช้มัลติเธรด
ตัวเข้าถึงแบบซิงโครไนซ์ + + ช้า ไม่เคย. หรือเมื่อความเร็วในการทำงานกับมัลติเธรดไม่สำคัญ แต่ไม่เคยดีกว่า
ตรวจสอบการล็อคสองครั้ง + + เร็ว ในบางกรณีที่เกิดขึ้นไม่บ่อยนักเมื่อคุณจำเป็นต้องจัดการกับข้อยกเว้นเมื่อสร้างซิงเกิลตัน (เมื่อไม่สามารถใช้ Class Holder Singleton ได้)
คลาสโฮลเดอร์ ซิงเกิลตัน + + เร็ว ทุกครั้งเมื่อจำเป็นต้องใช้มัลติเธรดและมีการรับประกันว่าออบเจ็กต์คลาสซิงเกิลจะถูกสร้างขึ้นโดยไม่มีปัญหา

ข้อดีและข้อเสียของรูปแบบซิงเกิลตัน

โดยทั่วไปแล้ว singleton จะทำสิ่งที่คาดหวังไว้อย่างแน่นอน:
  1. ให้การรับประกันว่าคลาสจะมีเพียงอินสแตนซ์เดียวของคลาส

  2. จัดเตรียมจุดเข้าใช้งานส่วนกลางให้กับอินสแตนซ์ของคลาสนี้

อย่างไรก็ตาม รูปแบบนี้มีข้อเสีย:
  1. Singleton ละเมิด SRP (Single Responsibility Principle) - คลาส Singleton นอกเหนือจากความรับผิดชอบในทันทีแล้ว ยังควบคุมจำนวนสำเนาด้วย

  2. การพึ่งพาของคลาสปกติหรือเมธอดในซิงเกิลตันไม่สามารถมองเห็นได้ในสัญญาสาธารณะของคลาส

  3. ตัวแปรทั่วโลกไม่ดี ในที่สุดซิงเกิลตันก็กลายเป็นตัวแปรสำคัญระดับโลกตัวหนึ่ง

  4. การมีซิงเกิลตันจะช่วยลดความสามารถในการทดสอบของแอปพลิเคชันโดยทั่วไปและคลาสที่ใช้ซิงเกิลตันโดยเฉพาะ

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

อ่านเพิ่มเติม:

ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION