ซิงเกิลที่มีอาร์กิวเมนต์ใน Java


142

ฉันอ่านบทความ Singleton ใน Wikipedia และฉันพบตัวอย่างนี้:

public class Singleton {
    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

ในขณะที่ฉันชอบวิธีการทำงานของซิงเกิลนี้ฉันไม่เห็นวิธีการปรับให้เข้ากับการสร้างข้อโต้แย้งกับตัวสร้าง วิธีที่แนะนำในการทำเช่นนี้ใน Java คืออะไร? ฉันต้องทำอะไรแบบนี้ไหม

public class Singleton
{
    private static Singleton singleton = null;  
    private final int x;

    private Singleton(int x) {
        this.x = x;
    }

    public synchronized static Singleton getInstance(int x) {
        if(singleton == null) singleton = new Singleton(x);
        return singleton;
    }
}

ขอบคุณ!


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

// AbstractTask implements Serializable
public class Task extends AbstractTask
{
    private final ReferenceToReallyBigObject object;

    public Task(ReferenceToReallyBigObject object)
    {
        this.object = object;
    }

    public void run()
    {
        // Do some stuff with the object (which is immutable).
    }
}

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

// AbstractTask implements Serializable
public class Task extends AbstractTask
{
    private static ReferenceToReallyBigObject object = null;

    private final String filePath;

    public Task(String filePath)
    {
        this.filePath = filePath;
    }

    public void run()
    {
        synchronized(this)
        {
            if(object == null)
            {
                ObjectReader reader = new ObjectReader(filePath);
                object = reader.read();
            }
        }

        // Do some stuff with the object (which is immutable).
    }
}

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


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

2
ค่อนข้างโชคร้ายที่คุณใช้คำว่าซิงเกิลตันที่มาพร้อมกับกระเป๋าสัมภาระ คำที่เหมาะสมสำหรับรูปแบบนี้คือการฝึกงานจริง Interning เป็นวิธีการที่ทำให้มั่นใจได้ว่าค่านามธรรมถูกแสดงโดยอินสแตนซ์เดียวเท่านั้น การฝึกงานแบบสตริงเป็นการใช้งานบ่อยที่สุด: en.wikipedia.org/wiki/String_intern_pool
notnoop

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

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

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

คำตอบ:


171

ฉันจะทำให้จุดของฉันที่ชัดเจนมาก: เดี่ยวกับพารามิเตอร์ที่ไม่ได้เป็นเดี่ยว

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

คุณมีสองตัวเลือก หากคุณต้องการให้ซิงเกิลตันเริ่มต้นด้วยข้อมูลบางอย่างคุณอาจโหลดด้วยข้อมูลหลังจากเริ่มต้นเช่น:

SingletonObj singleton = SingletonObj.getInstance();
singleton.init(paramA, paramB); // init the object with data

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

SingletonObj singleton = SingletonObj.getInstance();
singleton.doSomething(paramA, paramB); // pass parameters on execution

ไม่ว่าในกรณีใดการสร้างอินสแตนซ์จะไม่มีพารามิเตอร์เสมอ มิฉะนั้นซิงเกิลของคุณจะไม่ใช่ซิงเกิล


1
+1 นี่คือวิธีที่ฉันอาจทำเมื่อเขียนโค้ด ใน C # ฉันแค่ใช้คุณสมบัติแม้ว่า Java น่าจะเป็นแบบนี้
Zack

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

1
@masi ตามที่ผู้เขียนบอกว่า - ไม่ใช่ซิงเกิล หากคุณจำเป็นต้องผ่าน dynamicaly คงที่คุณอาจต้องสร้างคลาสดังกล่าวจำนวนมากที่มีค่าคงที่ต่างกัน ดังนั้นจึงไม่มีประเด็นในซิงเกิล
Dmitry Zaytsev

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

4
"ถ้าคุณพยายามป้อนพารามิเตอร์ให้กับนวกรรมิกประเด็นของซิงเกิลคืออะไร" - หนึ่งอาจพูดว่า: "ถ้าคุณทำให้ทั้งแอปพลิเคชันของคุณเป็นอินสแตนซ์เดียวจุดของอาร์กิวเมนต์บรรทัดคำสั่งคืออะไร" และคำตอบก็คือมันสมเหตุสมผลดี ตอนนี้เราสามารถพูดได้ว่าสิ่งนี้ค่อนข้างแตกต่างจากคลาสซิงเกิลยกเว้นคลาสจริง ๆ แล้วเป็นคลาสหลักที่รับ args [] จากเมธอด main - ดังนั้นมันจึงเป็นสิ่งเดียวกัน อาร์กิวเมนต์สุดท้ายซึ่งอาจจะยืนก็คือว่านี่เป็นสถานการณ์ที่ค่อนข้างพิเศษ
ประธาน Dreamspace

41

ฉันคิดว่าคุณต้องการบางอย่างเช่นโรงงานเพื่อให้ได้วัตถุที่มีพารามิเตอร์ต่าง ๆ และนำกลับมาใช้ใหม่ มันสามารถนำมาใช้โดยใช้การซิงโครไนซ์HashMapหรือConcurrentHashMapแมปพารามิเตอร์ ( Integerตัวอย่าง) กับคลาส 'singleton' พารามิเตอร์ของคุณ

แม้ว่าคุณอาจจะไปถึงจุดที่คุณควรใช้คลาสปกติไม่ใช่ซิงเกิลแทน (ตัวอย่างเช่นต้องการ 10.000 parametrized singleton ที่แตกต่างกัน)

นี่คือตัวอย่างสำหรับร้านค้าดังกล่าว:

public final class UsefulObjFactory {

    private static Map<Integer, UsefulObj> store =
        new HashMap<Integer, UsefulObj>();

    public static final class UsefulObj {
        private UsefulObj(int parameter) {
            // init
        }
        public void someUsefulMethod() {
            // some useful operation
        }
    }

    public static UsefulObj get(int parameter) {
        synchronized (store) {
            UsefulObj result = store.get(parameter);
            if (result == null) {
                result = new UsefulObj(parameter);
                store.put(parameter, result);
            }
            return result;
        }
    }
}

เพื่อผลักดันยิ่งไปกว่านั้น Java enums ยังสามารถพิจารณา (หรือใช้เป็น) parametrized singletons แม้ว่าจะอนุญาตให้ใช้ตัวแปรแบบคงที่จำนวนคงที่เท่านั้น

อย่างไรก็ตามหากคุณต้องการโซลูชันแบบกระจาย1ให้พิจารณาโซลูชันแคชด้านข้างบางอย่าง ตัวอย่างเช่น: EHCache, Terracotta และอื่น ๆ

1ในแง่ของการครอบคลุม VMs หลายเครื่องบนคอมพิวเตอร์หลายเครื่อง


ใช่นี่คือสิ่งที่ฉันต้องการ ขอบคุณมาก! ฉันยอมรับว่าวิธีการจัดการข้อโต้แย้งในตัวอย่างของฉันไม่สมเหตุสมผล แต่ฉันไม่คิด ดูคำอธิบายของฉันในความคิดเห็นของคำตอบ oxbow_lakes

1
นี่ไม่ใช่ซิงเกิล; ตอนนี้คุณมีมากกว่าหนึ่งในนั้น LOL
oxbow_lakes

@Scott: ฉันอยากจะแนะนำสิ่งที่ Yuval แนะนำด้านล่าง มันสมเหตุสมผลมากขึ้นและคุณมีซิงเกิล 'ของจริง' แก้ไข
Zack

ฉันหวังว่าไม่มีใครคิดจะแก้ไขชื่อในรหัส ฉันสามารถจินตนาการว่านี่เป็นความสับสนสำหรับมือใหม่ ย้อนกลับถ้าคุณไม่เห็นด้วย
oxbow_lakes

ใช่เราสามารถเรียกพวกเขาว่า Multitron และยังคงบรรลุเป้าหมายเดียวกันกับที่ OP ต้องการตั้งแต่แรก
akarnokd

22

คุณสามารถเพิ่มวิธีการกำหนดค่าเริ่มต้นเพื่อแยกอินสแตนซ์จากการรับ

public class Singleton {
    private static Singleton singleton = null;
    private final int x;

    private Singleton(int x) {
        this.x = x;
    }

    public static Singleton getInstance() {
        if(singleton == null) {
            throw new AssertionError("You have to call init first");
        }

        return singleton;
    }

    public synchronized static Singleton init(int x) {
        if (singleton != null)
        {
            // in my opinion this is optional, but for the purists it ensures
            // that you only ever get the same instance when you call getInstance
            throw new AssertionError("You already initialized me");
        }

        singleton = new Singleton(x);
        return singleton;
    }

}

จากนั้นคุณสามารถโทรSingleton.init(123)หนึ่งครั้งเพื่อกำหนดค่าตัวอย่างเช่นในการเริ่มต้นแอปของคุณ


13

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

    public enum EnumSingleton {

    INSTANCE;

    private String name; // Mandatory
    private Double age = null; // Not Mandatory

    private void build(SingletonBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    // Static getter
    public static EnumSingleton getSingleton() {
        return INSTANCE;
    }

    public void print() {
        System.out.println("Name "+name + ", age: "+age);
    }


    public static class SingletonBuilder {

        private final String name; // Mandatory
        private Double age = null; // Not Mandatory

        private SingletonBuilder(){
          name = null;
        }

        SingletonBuilder(String name) {
            this.name = name;
        }

        public SingletonBuilder age(double age) {
            this.age = age;
            return this;
        }

        public void build(){
            EnumSingleton.INSTANCE.build(this);
        }

    }


}

จากนั้นคุณสามารถสร้าง / instantiate / parametrizedได้ดังนี้:

public static void main(String[] args) {
    new EnumSingleton.SingletonBuilder("nico").age(41).build();
    EnumSingleton.getSingleton().print();
}

6

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

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

รูปแบบการออกแบบมีไว้เพื่อช่วยให้เราเขียนโค้ดที่ยืดหยุ่นและขยายได้ไม่ขัดขวางเราในการเขียนโค้ดที่ดี


12
นี่ไม่ใช่คำตอบสำหรับคำถาม OP นี่ควรเป็นความคิดเห็น
ธีรี่ร์เจ

5

ใช้ getters และ setters เพื่อตั้งค่าตัวแปรและทำให้คอนสตรัคเตอร์เริ่มต้นเป็นแบบส่วนตัว จากนั้นใช้:

Singleton.getInstance().setX(value);

1
อย่าเข้าใจว่าทำไมสิ่งนี้ถึงได้ลงมติ .. มันเป็นคำตอบที่ถูกต้อง tbh : /
Zack

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

5

แปลกใจที่ไม่มีใครพูดถึงวิธีการสร้าง / เรียกคืนคนตัดไม้ ตัวอย่างเช่นด้านล่างแสดงวิธีดึงตัวบันทึก Log4J

// Retrieve a logger named according to the value of the name parameter. If the named logger already exists, then the existing instance will be returned. Otherwise, a new instance is created.
public static Logger getLogger(String name)

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

69   Hashtable ht;
...
258  public
259  Logger getLogger(String name, LoggerFactory factory) {
260    //System.out.println("getInstance("+name+") called.");
261    CategoryKey key = new CategoryKey(name);
262    // Synchronize to prevent write conflicts. Read conflicts (in
263    // getChainedLevel method) are possible only if variable
264    // assignments are non-atomic.
265    Logger logger;
266
267    synchronized(ht) {
268      Object o = ht.get(key);
269      if(o == null) {
270        logger = factory.makeNewLoggerInstance(name);
271        logger.setHierarchy(this);
272        ht.put(key, logger);
273        updateParents(logger);
274        return logger;
275      } else if(o instanceof Logger) {
276        return (Logger) o;
277      } 
...

4

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

public final class RInterfaceHL {

    /**
     * Private constructor prevents instantiation from other classes.
     */
    private RInterfaceHL() { }

    /**
     * R REPL (read-evaluate-parse loop) handler.
     */
    private static RMainLoopCallbacks rloopHandler = null;

    /**
     * SingletonHolder is loaded, and the static initializer executed, 
     * on the first execution of Singleton.getInstance() or the first 
     * access to SingletonHolder.INSTANCE, not before.
     */
    private static final class SingletonHolder {

        /**
         * Singleton instance, with static initializer.
         */
        private static final RInterfaceHL INSTANCE = initRInterfaceHL();

        /**
         * Initialize RInterfaceHL singleton instance using rLoopHandler from
         * outer class.
         * 
         * @return RInterfaceHL instance
         */
        private static RInterfaceHL initRInterfaceHL() {
            try {
                return new RInterfaceHL(rloopHandler);
            } catch (REngineException e) {
                // a static initializer cannot throw exceptions
                // but it can throw an ExceptionInInitializerError
                throw new ExceptionInInitializerError(e);
            }
        }

        /**
         * Prevent instantiation.
         */
        private SingletonHolder() {
        }

        /**
         * Get singleton RInterfaceHL.
         * 
         * @return RInterfaceHL singleton.
         */
        public static RInterfaceHL getInstance() {
            return SingletonHolder.INSTANCE;
        }

    }

    /**
     * Return the singleton instance of RInterfaceHL. Only the first call to
     * this will establish the rloopHandler.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @return RInterfaceHL singleton instance
     * @throws REngineException
     *             if REngine cannot be created
     */
    public static RInterfaceHL getInstance(RMainLoopCallbacks rloopHandler)
            throws REngineException {
        RInterfaceHL.rloopHandler = rloopHandler;

        RInterfaceHL instance = null;

        try {
            instance = SingletonHolder.getInstance();
        } catch (ExceptionInInitializerError e) {

            // rethrow exception that occurred in the initializer
            // so our caller can deal with it
            Throwable exceptionInInit = e.getCause();
            throw new REngineException(null, exceptionInInit.getMessage());
        }

        return instance;
    }

    /**
     * org.rosuda.REngine.REngine high level R interface.
     */
    private REngine rosudaEngine = null;

    /**
     * Construct new RInterfaceHL. Only ever gets called once by
     * {@link SingletonHolder.initRInterfaceHL}.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @throws REngineException
     *             if R cannot be loaded.
     */
    private RInterfaceHL(RMainLoopCallbacks rloopHandler)
            throws REngineException {

        // tell Rengine code not to die if it can't
        // load the JRI native DLLs. This allows
        // us to catch the UnsatisfiedLinkError
        // ourselves
        System.setProperty("jri.ignore.ule", "yes");

        rosudaEngine = new JRIEngine(new String[] { "--no-save" }, rloopHandler);
    }
}

ฉันคิดว่ามันจะเป็นความคิดที่ดีfinally { RInterfaceHL.rloopHandler = null; }ในgetInstanceเพราะการอ้างอิงแบบคงที่อาจทำให้หน่วยความจำรั่วถ้าเราไม่ระวัง ในกรณีของคุณดูเหมือนว่าจะไม่เป็นปัญหา แต่ฉันสามารถจินตนาการถึงสถานการณ์ที่วัตถุที่ส่งผ่านมีขนาดใหญ่และใช้RInterfaceHLctor เพียงเพื่อรับค่าบางอย่างและไม่ให้อ้างอิงกับมัน
TWiStErRob

ความคิด: return SingletonHolder.INSTANCEทำงานได้ดีเหมือนgetInstanceเดิม ฉันไม่คิดว่ามีความจำเป็นในการห่อหุ้มที่นี่เพราะชั้นนอกรู้ถึงอวัยวะภายในของชั้นในอยู่แล้วพวกมันเชื่อมโยงกันอย่างแน่นหนา: รู้ถึงrloopHandlerความต้องการก่อนที่จะโทร คอนสตรัคเตอร์ส่วนตัวก็ไม่มีผลกระทบเพราะของส่วนตัวของคนชั้นในนั้นมีให้เฉพาะกับคนชั้นนอก
TWiStErRob

1
ลิงก์เสีย คุณกำลังอ้างถึง en.wikipedia.org/wiki/Initialization-on-demand_holder_idiomหรือไม่?
Jorge Lavín

3

เหตุผลที่คุณไม่สามารถเข้าใจได้ว่าจะทำอย่างไรในสิ่งที่คุณพยายามทำอาจเป็นเพราะสิ่งที่คุณพยายามทำนั้นไม่สมเหตุสมผล คุณต้องการโทรgetInstance(x)ด้วยอาร์กิวเมนต์ที่ต่างกัน แต่จะคืนค่าวัตถุเดิมเสมอหรือไม่ พฤติกรรมมันคืออะไรที่คุณต้องการเมื่อคุณเรียกgetInstance(2)แล้วgetInstance(5)?

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

หากคุณต้องการgetInstance(2)และgetInstance(5)ส่งคืนวัตถุต่าง ๆ ในทางกลับกันคุณไม่ได้ใช้รูปแบบซิงเกิลคุณกำลังใช้รูปแบบโรงงาน


3

ในตัวอย่างของคุณคุณไม่ได้ใช้ซิงเกิล โปรดสังเกตว่าถ้าคุณทำสิ่งต่อไปนี้ (สมมติว่า Singleton.getInstance เป็นสแตติกจริง ๆ ):

Singleton obj1 = Singleton.getInstance(3);
Singleton obj2 = Singleton.getInstance(4);

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

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


3

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


3

หากคุณต้องการสร้างคลาส Singleton ที่ให้บริการเป็นบริบทวิธีที่ดีคือการมีไฟล์การกำหนดค่าและอ่านพารามิเตอร์จากไฟล์ภายในอินสแตนซ์ ()

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


1

นี่ไม่ใช่ซิงเกิล แต่อาจเป็นสิ่งที่สามารถแก้ไขปัญหาของคุณได้

public class KamilManager {

  private static KamilManager sharedInstance;

  /**
   * This method cannot be called before calling KamilManager constructor or else
   * it will bomb out.
   * @return
   */
  public static KamilManager getInstanceAfterInitialized() {
    if(sharedInstance == null)
        throw new RuntimeException("You must instantiate KamilManager once, before calling this method");

    return sharedInstance;
}

  public KamilManager(Context context, KamilConfig KamilConfig) {
    //Set whatever you need to set here then call:
  s  haredInstance = this;
  }
}

1

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

อีกคำถามคือ: ดีไหมถ้ามีรัฐเดี่ยว?


1

เราไม่สามารถทำสิ่งนี้:

public class Singleton {

    private int x;

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(int x) {
        Singleton instance = SingletonHolder.INSTANCE;
        instance.x = x;
        return instance;
    }
}

1

ทั้งๆที่บางคนอาจยืนยันนี่เป็นซิงเกิลตันพร้อมพารามิเตอร์ในตัวสร้าง

public class Singleton {

    private static String aParameterStored;

    private static final Singleton instance = new Singleton("Param to set");

    private Singleton() {
        // do nothing
    }

    private Singleton(String param) {
        aParameterStored = param;
    }

    public static Singleton getInstance() {
        return instance;
    }

    /*
     * ... stuff you would like the singleton do
     */
}

รูปแบบซิงเกิลพูดว่า:

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

ซึ่งเคารพตัวอย่างนี้

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


0

ฉันกลัวที่จะโพสต์คำตอบนี้ แต่ฉันไม่เข้าใจว่าทำไมไม่มีใครคิดเกี่ยวกับเรื่องนี้บางทีคำตอบนี้อาจได้รับแล้วฉันก็ไม่เข้าใจ

public class example  {
    private volatile static example instance;

    private String string;
    private int iInt = -1; //any number you know you don't want to use here

  private example() {

    //In case someone uses the private method to create a new Instance
    if (instance != null){
      throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
    }
  }

  public synchronized static example getIsntance(){
    if(instance == null){
      instance = new example();
    }
    return instance;
  }

public void methodDoingWork(){
    if(checkInit()){
      //DoSome
    }
  }

  private boolean checkInit(){
    boolean filled = (this.string != null) && (this.iInt != -1);
    return filled;
  }

  public void setString(String string) {
    if(this.string == null){
      this.string = string;
    }else{
      throw new RuntimeException("You try to override an already setValue"); 
    }
  }

  public void setiInt(int iInt) {
    if(this.iInt == -1){
      this.iInt = iInt;
    }else{
      throw new RuntimeException("You try to override an already setValue");
    }
  }
}

เนื่องจากการgetInstance()คืนค่าอินสแตนซ์เดียวกันทุกครั้งฉันคิดว่าสิ่งนี้สามารถทำงานได้ หากนี่ผิดฉันจะลบมันมากฉันแค่สนใจในหัวข้อนี้


-1

ฉันคิดว่านี่เป็นปัญหาที่พบบ่อย การแยก "การเตรียมใช้งาน" ของ singleton ออกจาก "get" ของ singleton อาจใช้งานได้ (ตัวอย่างนี้ใช้รูปแบบของการล็อกที่เลือกสองครั้ง)

public class MySingleton {

    private static volatile MySingleton INSTANCE;

    @SuppressWarnings("UnusedAssignment")
    public static void initialize(
            final SomeDependency someDependency) {

        MySingleton result = INSTANCE;

        if (result != null) {
            throw new IllegalStateException("The singleton has already "
                    + "been initialized.");
        }

        synchronized (MySingleton.class) {
            result = INSTANCE;

            if (result == null) {
                INSTANCE = result = new MySingleton(someDependency);
            } 
        }
    }

    public static MySingleton get() {
        MySingleton  result = INSTANCE;

        if (result == null) {
            throw new IllegalStateException("The singleton has not been "
                    + "initialized. You must call initialize(...) before "
                    + "calling get()");
        }

       return result;
    }

    ...
}

สามารถกลับ "ผล" ในวิธีการเริ่มต้นได้เช่นกันฉันคิดว่า
Michael Andrews

-2

แน่นอนว่า Singleton นั้นเป็น "anti-pattern" (สมมติว่านิยามของสแตติกที่มีสถานะตัวแปร)

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


4
เฉพาะรูปแบบการต่อต้านหากใช้ไม่ถูกต้องแม้ว่าจะเป็นคำจำกัดความของรูปแบบการต่อต้าน เพียงเพราะคุณเคยเห็นพวกเขาที่พวกเขาไม่ได้อยู่ในอดีตไม่ได้หมายความว่าพวกเขาไม่มีที่
geowa4

การใช้งานซิงเกิลตันที่ถูกต้องคือการแสดงรหัสที่ไร้ความสามารถ
Tom Hawtin - tackline

-6

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

ซิงเกิลที่มีอาร์กิวเมนต์ไม่สมเหตุสมผล - จะเกิดอะไรขึ้นถ้าคุณเขียน:

Singleton s = SingletonHolder.getInstance(1);
Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException

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


นั่นเป็นที่ถกเถียงกันค่อนข้าง
AlbertoPL

1
ใช่มันเป็นที่ถกเถียงกัน; ดังนั้นการใช้คำว่า "โดยทั่วไป" ของฉัน ฉันคิดว่ามันไม่ยุติธรรมที่จะบอกว่าพวกเขาได้รับการพิจารณาโดยทั่วไปความคิดที่ดี
oxbow_lakes

เป็นที่ถกเถียงกันอยู่ - บางคนอ้างว่าสิ่งที่เรียกว่า "รูปแบบต่อต้าน" เหมาะสมกับคำจำกัดความของรูปแบบมันเป็นเพียงว่าพวกเขาเป็นรูปแบบที่ไม่ดี
Tom Hawtin - tackline

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

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