Singleton với đối số trong Java


142

Tôi đã đọc bài viết của Singleton trên Wikipedia và tôi đã xem qua ví dụ này:

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;
    }
}

Mặc dù tôi thực sự thích cách Singleton này hoạt động, tôi không thể thấy cách điều chỉnh nó để kết hợp các đối số với hàm tạo. Cách ưa thích để làm điều này trong Java là gì? Tôi sẽ phải làm một cái gì đó như thế này?

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;
    }
}

Cảm ơn!


Chỉnh sửa: Tôi nghĩ rằng tôi đã bắt đầu một cơn bão tranh cãi với mong muốn sử dụng Singleton. Hãy để tôi giải thích động lực của tôi và hy vọng ai đó có thể đề xuất một ý tưởng tốt hơn. Tôi đang sử dụng một khung tính toán lưới để thực hiện các nhiệm vụ song song. Nói chung, tôi có một cái gì đó như thế này:

// 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).
    }
}

Điều xảy ra là mặc dù tôi chỉ chuyển một tham chiếu đến dữ liệu của mình cho tất cả các tác vụ, khi các tác vụ được tuần tự hóa, dữ liệu sẽ được sao chép lặp đi lặp lại. Những gì tôi muốn làm là chia sẻ đối tượng trong số tất cả các nhiệm vụ. Đương nhiên, tôi có thể sửa đổi lớp như vậy:

// 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).
    }
}

Như bạn có thể thấy, ngay cả ở đây tôi có một vấn đề là việc chuyển một đường dẫn tệp khác có nghĩa là không có gì sau khi cái đầu tiên được thông qua. Đây là lý do tại sao tôi thích ý tưởng cho một cửa hàng đã được đăng trong câu trả lời. Dù sao, thay vì bao gồm logic để tải tệp trong phương thức chạy, tôi muốn trừu tượng hóa logic này thành một lớp Singleton. Tôi sẽ không cung cấp một ví dụ khác, nhưng tôi hy vọng bạn có ý tưởng. Xin vui lòng cho tôi nghe ý tưởng của bạn cho một cách thanh lịch hơn để thực hiện những gì tôi đang cố gắng làm. Cám ơn bạn một lần nữa!


1
Các mô hình nhà máy là những gì bạn muốn. Lý tưởng nhất, các tác vụ lưới phải hoàn toàn độc lập với mọi thứ khác và được gửi tất cả dữ liệu họ cần để thực hiện và trả về kết quả của chúng. Tuy nhiên, đây không phải lúc nào cũng là giải pháp khả thi nhất, vì vậy việc tuần tự hóa dữ liệu vào một tệp không phải là một ý tưởng quá tệ. Tôi nghĩ rằng toàn bộ điều đơn lẻ là một chút cá trích đỏ; bạn không muốn một người độc thân.
oxbow_lakes

2
Thật đáng tiếc khi bạn sử dụng thuật ngữ Singleton đi kèm với hành lý như vậy. Thuật ngữ thích hợp cho mô hình này là Thực tế. Thực tập là một phương pháp để đảm bảo rằng các giá trị trừu tượng chỉ được đại diện bởi một thể hiện. Thực tập chuỗi là cách sử dụng phổ biến nhất: en.wikipedia.org/wiki/String_i INTERN_pool.
notnoop

Bạn có thể muốn có một cái nhìn về Terracotta. Nó duy trì danh tính đối tượng trên toàn cụm. Khi bạn gửi một tham chiếu đến dữ liệu đã có trong cụm, nó sẽ không được tuần tự hóa lại.
Taylor Gautier

21
Đặt vấn đề về việc có nên sử dụng mẫu singleton hay không, tôi lưu ý rằng hầu hết mọi câu trả lời ở đây dường như cho rằng mục đích của việc cung cấp một đối số là cho phép tạo ra nhiều "singletons" được phân biệt bởi giá trị của tham số nói trên. Tuy nhiên, một mục đích có thể là để cung cấp truy cập đến một đối tượng bên ngoài mà là chỉ đối tượng của loại hình này mà lớp singleton độc đáo dụ sẽ bao giờ cần. Vì vậy, chúng ta cần phân biệt một tham số được cung cấp cho quyền truy cập đó với một tham số nhằm tạo ra "nhiều trường hợp đơn lẻ".
Carl

2
Một kịch bản khác cho "singleton với các tham số": một ứng dụng web sẽ xây dựng singleton bất biến duy nhất dựa trên các thông tin đi kèm với yêu cầu (luồng) đầu tiên sắp tới. Ví dụ, miền yêu cầu có thể xác định hành vi của một số người độc thân
fustaki

Câu trả lời:


171

Tôi sẽ nói rõ quan điểm của mình: một singleton với các tham số không phải là một singleton .

Theo định nghĩa, một singleton là một đối tượng bạn muốn được khởi tạo không quá một lần. Nếu bạn đang cố gắng cung cấp các tham số cho hàm tạo, điểm của singleton là gì?

Bạn có hai lựa chọn. Nếu bạn muốn singleton của bạn được khởi tạo với một số dữ liệu, bạn có thể tải nó với dữ liệu sau khi khởi tạo , như vậy:

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

Nếu thao tác mà singleton của bạn đang thực hiện được lặp lại và với các tham số khác nhau mỗi lần, bạn cũng có thể chuyển các tham số cho phương thức chính đang được thực thi:

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

Trong mọi trường hợp, khởi tạo sẽ luôn luôn không có tham số. Nếu không thì singleton của bạn không phải là singleton.


1
+1 Đây là cách tôi có thể làm điều đó khi mã hóa. Trong C #, tôi chỉ sử dụng các thuộc tính. Java, có lẽ như thế này.
Zack

131
xin lỗi, điều đó không đúng có những tình huống mà bạn phải vượt qua trong các tham số được tạo động mà vẫn giữ nguyên cho thời gian chạy ứng dụng lỗ. vì vậy bạn không thể sử dụng một hằng trong singleton nhưng phải vượt qua hằng số đó khi nó được tạo. sau khi vượt qua một lần hằng số tương tự của nó cho thời gian lỗ. một setter sẽ không thực hiện công việc nếu bạn cần hằng số cụ thể đó trong hàm tạo.
Masi

1
@masi, như tác giả nói - nó không phải là một singleton. Nếu bạn cần vượt qua tính năng động liên tục, bạn có thể cần tạo rất nhiều lớp như vậy với các hằng số khác nhau. Vì vậy, không có điểm trong singleton.
Dmitry Zaytsev

53
Nếu bạn chỉ cần một thể hiện của một lớp trong toàn bộ thời gian của ứng dụng, nhưng bạn cần cung cấp cho cá thể đó một giá trị khi khởi chạy, tại sao điều này không còn là đơn lẻ?
Oscar

4
"Nếu bạn đang cố gắng cung cấp các tham số cho hàm tạo, điểm của singleton là gì?" - Người ta cũng có thể nói: "Nếu bạn tạo toàn bộ ứng dụng của mình, thì đối số dòng lệnh là gì?", Và câu trả lời là nó có nhiều ý nghĩa. Bây giờ người ta có thể nói rằng điều này khá khác biệt với một lớp đơn, ngoại trừ nếu lớp đó thực sự là lớp Chính nhận được các đối số [] từ phương thức chính - thì nó thậm chí còn giống như vậy. Lập luận cuối cùng, có thể chỉ đứng, là đây là một tình huống khá đặc biệt.
Chủ tịch Dreamspace

41

Tôi nghĩ rằng bạn cần một cái gì đó giống như một nhà máy để có các đối tượng với các tham số khác nhau được khởi tạo và tái sử dụng. Nó có thể được thực hiện bằng cách sử dụng đồng bộ hóa HashMaphoặc ConcurrentHashMapánh xạ một tham số ( Integerví dụ) cho lớp tham số 'singleton' của bạn.

Mặc dù bạn có thể đi đến điểm mà bạn nên sử dụng các lớp thông thường, không đơn lẻ thay vào đó (ví dụ cần 10.000 đơn lẻ tham số khác nhau).

Dưới đây là một ví dụ cho cửa hàng như vậy:

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;
        }
    }
}

Để đẩy mạnh hơn nữa, các Java enumcũng có thể được xem xét (hoặc được sử dụng như) các singletons tham số hóa, mặc dù chỉ cho phép một biến thể tĩnh số cố định.

Tuy nhiên, nếu bạn cần một giải pháp phân tán 1 , hãy xem xét một số giải pháp bộ nhớ đệm bên. Ví dụ: EHCache, Terracotta, v.v.

1 theo nghĩa là trải rộng nhiều VM trên nhiều máy tính.


Vâng, đây chính xác là những gì tôi cần. Cảm ơn rât nhiều! Tôi đồng ý rằng cách tôi xử lý các đối số trong ví dụ của tôi không có nhiều ý nghĩa, nhưng tôi không nghĩ về điều này. Xem giải thích của tôi trong các nhận xét về câu trả lời của oxbow_lakes.

1
Đây KHÔNG phải là một đơn lẻ; bây giờ bạn có nhiều hơn một trong số họ. LOL
oxbow_lakes

@Scott: Tôi muốn đề xuất một cái gì đó giống như những gì Yuval đề xuất bên dưới. Nó có ý nghĩa hơn một chút và bạn có một singleton 'thật'. chỉnh sửa
Zack

Tôi hy vọng không ai nhớ tôi chỉnh sửa tên trong mã; Tôi có thể tưởng tượng điều này thực sự khó hiểu cho người mới. Phục hồi nếu bạn không đồng ý
oxbow_lakes

Có, chúng tôi có thể gọi chúng là Multitron và vẫn đạt được mục tiêu tương tự mà OP muốn ở vị trí đầu tiên IMHO.
akarnokd

22

Bạn có thể thêm một phương thức khởi tạo có thể định cấu hình để tách khởi tạo khỏi nhận.

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;
    }

}

Sau đó, bạn có thể gọi Singleton.init(123)một lần để định cấu hình nó, ví dụ như trong ứng dụng khởi động.


13

Bạn cũng có thể sử dụng mẫu Builder nếu bạn muốn chỉ ra rằng một số tham số là bắt buộc.

    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);
        }

    }


}

Sau đó, bạn có thể tạo / khởi tạo / tham số hóa nó như sau:

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

6

" Một singleton với các tham số không phải là một singleton " không hoàn toàn chính xác . Chúng ta cần phân tích điều này từ góc độ ứng dụng hơn là từ phối cảnh mã.

Chúng tôi xây dựng lớp singleton để tạo một thể hiện của một đối tượng trong một lần chạy ứng dụng. Bằng cách có một hàm tạo với tham số, bạn có thể xây dựng tính linh hoạt trong mã của mình để thay đổi một số thuộc tính của đối tượng singleton mỗi khi bạn chạy ứng dụng. Đây không phải là vi phạm mẫu Singleton. Nó trông giống như một vi phạm nếu bạn nhìn thấy điều này từ quan điểm mã.

Các mẫu thiết kế có mặt để giúp chúng tôi viết mã linh hoạt và có thể mở rộng, không cản trở chúng tôi viết mã tốt.


12
Đây không phải là một câu trả lời cho câu hỏi OP, đây nên là một nhận xét.
Thierry J.

5

Sử dụng getters và setters để đặt biến và làm cho hàm tạo mặc định ở chế độ riêng tư. Sau đó sử dụng:

Singleton.getInstance().setX(value);

1
Đừng hiểu tại sao điều này lại bị bỏ phiếu .. Đó là một câu trả lời hợp lệ tbh. : /
Zack

13
Bởi vì đó là một câu trả lời rác rưởi. Ví dụ, hãy tưởng tượng một hệ thống trong đó tên người dùng và mật khẩu ban đầu cho quản trị viên ban đầu là các đối số của hàm tạo. Bây giờ, nếu tôi biến nó thành một singleton và làm như bạn nói, tôi sẽ nhận được getters và setters cho quản trị viên, đó không hoàn toàn là những gì bạn muốn. Vì vậy, trong khi một số tùy chọn của bạn có thể hợp lệ trong một số trường hợp, nó không thực sự trả lời cho trường hợp chung đó là câu hỏi. (vâng, tôi đang làm việc trên hệ thống mà tôi đã mô tả và không, tôi sẽ không sử dụng một mẫu đơn nếu thực tế là bài tập nói "sử dụng một mẫu đơn ở đây")
Jasper

5

Ngạc nhiên rằng không ai đề cập đến cách một logger được tạo / lấy. Ví dụ, dưới đây cho thấy cách logger log4J được lấy.

// 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)

Có một số cấp độ của các chỉ định, nhưng phần quan trọng nằm dưới phương thức , phần lớn cho biết mọi thứ về cách thức hoạt động của nó. Nó sử dụng bảng băm để lưu trữ các logger đang thoát và khóa được lấy từ tên. Nếu logger không tồn tại cho một tên cho, nó sử dụng một nhà máy để tạo logger và sau đó thêm nó vào bảng băm.

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

Sửa đổi mẫu Singleton sử dụng khởi tạo của Bill Pugh theo thành ngữ chủ sở hữu nhu cầu . Đây là luồng an toàn mà không cần chi phí của các cấu trúc ngôn ngữ chuyên ngành (nghĩa là dễ bay hơi hoặc đồng bộ hóa):

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);
    }
}

Tôi nghĩ rằng nó sẽ là một ý tưởng tốt để tham finally { RInterfaceHL.rloopHandler = null; }gia getInstance, bởi vì tham chiếu tĩnh đó có thể gây rò rỉ bộ nhớ nếu chúng ta không cẩn thận. Trong trường hợp của bạn, có vẻ như đó không phải là vấn đề, nhưng tôi có thể tưởng tượng ra một kịch bản trong đó đối tượng truyền vào là lớn và chỉ được sử dụng bởi RInterfaceHLctor để nhận một số giá trị và không giữ tham chiếu đến nó.
TWiStErRob

Ý tưởng: return SingletonHolder.INSTANCEsẽ làm việc tốt như trong getInstance. Tôi không nghĩ rằng cần phải đóng gói ở đây, bởi vì lớp bên ngoài đã biết các bộ phận của lớp bên trong, họ được liên kết chặt chẽ: nó biết rloopHandlercần init trước khi gọi. Ngoài ra, hàm tạo riêng không có tác dụng, bởi vì các thứ riêng tư của lớp bên trong chỉ đơn giản là có sẵn cho lớp bên ngoài.
TWiStErRob

1
Liên kết bị hỏng. Bạn đã tham khảo en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom ?
Jorge Lavín

3

Lý do bạn không thể hiểu được cách thực hiện những gì bạn đang cố gắng có lẽ là những gì bạn đang cố gắng không thực sự có ý nghĩa. Bạn muốn gọi getInstance(x)với các đối số khác nhau, nhưng luôn trả về cùng một đối tượng? Hành vi nào bạn muốn khi bạn gọi getInstance(2)và sau đó getInstance(5)?

Nếu bạn muốn cùng một đối tượng nhưng giá trị bên trong của nó khác nhau, đó là cách duy nhất nó vẫn là một đơn, thì bạn không cần phải quan tâm đến hàm tạo; bạn chỉ cần đặt giá trị getInstance()theo cách của đối tượng. Tất nhiên, bạn hiểu rằng tất cả các tham chiếu khác của bạn đến singleton hiện có một giá trị nội bộ khác nhau.

Nếu bạn muốn getInstance(2)getInstance(5)trả lại các đối tượng khác nhau, mặt khác, bạn không sử dụng mẫu Singleton, bạn đang sử dụng mẫu Factory.


3

Trong ví dụ của bạn, bạn không sử dụng singleton. Lưu ý rằng nếu bạn làm như sau (giả sử rằng Singleton.getInstance thực sự tĩnh):

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

Sau đó, các giá trị của obj2.x là 3, không phải 4. Nếu bạn cần làm điều này, hãy biến nó thành một lớp đơn giản. Nếu số lượng giá trị nhỏ và cố định, bạn có thể xem xét sử dụng mộtenum . Nếu bạn gặp vấn đề với việc tạo đối tượng quá mức (thường không phải là trường hợp này), thì bạn có thể xem xét các giá trị bộ đệm (và kiểm tra nguồn hoặc nhận trợ giúp với điều đó, vì rõ ràng là làm thế nào để xây dựng bộ nhớ cache mà không có nguy cơ rò rỉ bộ nhớ).

Bạn cũng có thể muốn đọc bài viết này vì singletons có thể rất dễ bị lạm dụng.


3

Một lý do khác Singletons là một mô hình chống là vì nếu được viết theo khuyến nghị, với hàm tạo riêng, chúng rất khó phân lớp và cấu hình để sử dụng trong các thử nghiệm đơn vị nhất định. Sẽ được yêu cầu trong việc duy trì mã kế thừa, ví dụ.


3

Nếu bạn muốn tạo một lớp Singleton phục vụ như một Ngữ cảnh, một cách tốt là có một tệp cấu hình và đọc các tham số từ tệp bên trong thể hiện ().

Nếu các tham số cung cấp cho lớp Singleton được tự động trong quá trình chạy chương trình của bạn, chỉ cần sử dụng HashMap tĩnh lưu trữ các phiên bản khác nhau trong lớp Singleton của bạn để đảm bảo rằng cho mỗi (các) tham số, chỉ có một phiên bản được tạo.


1

Đây không phải là một singleton, nhưng có thể là một cái gì đó có thể khắc phục vấn đề của bạn.

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

Nếu chúng ta coi vấn đề là "làm thế nào để tạo singleton với trạng thái", thì không cần thiết phải chuyển trạng thái làm tham số hàm tạo. Tôi đồng ý với các bài viết khởi tạo các trạng thái hoặc sử dụng phương thức set sau khi lấy cá thể singleton.

Một câu hỏi khác là: có tốt khi có singleton với nhà nước không?


1

Chúng ta không thể làm một cái gì đó như thế này:

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

Mặc dù một số người có thể khẳng định, đây là một singleton với các tham số trong hàm tạo

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
     */
}

Mẫu singleton nói:

  • đảm bảo rằng chỉ có một thể hiện của lớp singleton tồn tại
  • cung cấp quyền truy cập toàn cầu vào trường hợp đó.

được tôn trọng với ví dụ này.

Tại sao không trực tiếp thiết lập tài sản? Đó là trường hợp sách giáo khoa để cho thấy làm thế nào chúng ta có thể có được một singleton có hàm tạo với tham số nhưng nó có thể hữu ích trong một số trường hợp. Ví dụ, trong các trường hợp thừa kế để buộc singleton thiết lập một số thuộc tính siêu lớp.


0

Tôi sợ đăng bài này như một câu trả lời, nhưng tôi không hiểu tại sao không ai nghĩ về điều này, có lẽ câu trả lời này cũng đã được đưa ra nên tôi chỉ không hiểu nó.

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()trả về cùng một Instance mọi lúc, tôi nghĩ rằng điều này có thể làm việc. Nếu điều này là sai với nhiều tôi sẽ xóa nó, tôi chỉ quan tâm đến chủ đề này.


-1

Tôi nghĩ rằng đây là một vấn đề phổ biến. Việc tách "khởi tạo" của singleton khỏi "get" của singleton có thể hoạt động (ví dụ này sử dụng một biến thể của khóa được kiểm tra kép).

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;
    }

    ...
}

Tôi luôn có thể trả về "kết quả" trong phương thức khởi tạo.
Michael Andrew

-2

Singleton, tất nhiên, là một "chống mẫu" (giả sử một định nghĩa của một tĩnh với trạng thái biến).

Nếu bạn muốn có một tập hợp cố định các đối tượng giá trị bất biến, thì enums là cách để đi. Đối với một tập hợp giá trị lớn, có thể kết thúc mở, bạn có thể sử dụng Kho lưu trữ của một số biểu mẫu - thường dựa trên Mapviệc triển khai. Tất nhiên, khi bạn đang xử lý các số liệu thống kê, hãy cẩn thận với việc phân luồng (đồng bộ hóa đủ rộng rãi hoặc sử dụng ConcurrentMapkiểm tra xem một luồng khác không đánh bại bạn hay sử dụng một số hình thức tương lai).


4
Chỉ một mẫu chống nếu được sử dụng không chính xác, mặc dù đó là định nghĩa của mẫu chống. Chỉ vì bạn đã thấy họ nơi họ không thuộc về quá khứ không có nghĩa là họ không có địa điểm.
geowa4

Việc sử dụng đúng của một singleton là để chứng minh mã không đủ năng lực.
Tom Hawtin - tackline

-6

Singletons thường được coi là chống mẫu và không nên được sử dụng. Họ không làm cho mã dễ kiểm tra.

Một singleton với một đối số không có ý nghĩa nào dù sao - điều gì sẽ xảy ra nếu bạn viết:

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

Singleton của bạn cũng không an toàn cho chuỗi vì nhiều luồng có thể thực hiện các cuộc gọi đồng thời getInstancedẫn đến nhiều hơn một thể hiện được tạo ra (có thể với các giá trị khác nhau x).


Điều đó khá gây tranh cãi.
AlbertoPL

1
Vâng, đó là tranh cãi; do đó tôi sử dụng từ "nói chung". Tôi nghĩ thật công bằng khi nói rằng họ thường bị coi là một ý tưởng tồi
oxbow_lakes

Điều gây tranh cãi - một số người cho rằng những gì được gọi là "chống mẫu" phù hợp với định nghĩa của các mẫu, chỉ là chúng là những mẫu xấu.
Tom Hawtin - tackline

Tôi hiểu rằng họ là xấu. Tôi đang làm điện toán phân tán và cần chia sẻ một đối tượng giữa nhiều tác vụ. Thay vì xác định khởi tạo một biến tĩnh, tôi muốn trừu tượng hóa logic thành một Singleton. Tôi tưởng tượng tôi có thể làm cho getInstance được đồng bộ hóa. Điều này sẽ làm việc? Những gì tôi cần làm là tải một tệp một lần cho nhiều tác vụ và chỉ sau khi tác vụ đầu tiên được gửi. (Tôi không muốn dữ liệu của mình được tuần tự hóa.) Tôi nghĩ rằng tôi sẽ biến AbstractFileReader của mình thành một đối số cho phương thức getInstance để làm cho Singleton linh hoạt hơn. Tôi đánh giá đầu vào của bạn.

Tôi nghĩ bạn có thể hiểu nhầm "phân phối" nghĩa là gì? Có nhiều cách khác để đạt được những gì bạn muốn: bạn đã xem xét tiêm phụ thuộc chưa? Hay JNDI?
oxbow_lakes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.