[java] Java에서 인수가있는 싱글 톤

나는 Wikipedia에서 Singleton 기사를 읽고 있었고이 예제를 보았습니다.

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

감사!


편집 : 나는 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).
    }
}

보시다시피, 여기에서도 다른 파일 경로를 전달하면 첫 번째 경로가 전달 된 후에 아무것도 의미가 없다는 문제가 있습니다. 이것이 내가 아이디어를 좋아하는 이유입니다. 답변에 게시 된 상점에 입니다. 어쨌든 run 메소드에 파일을로드하는 논리를 포함시키는 대신이 논리를 Singleton 클래스로 추상화하고 싶었습니다. 나는 또 다른 예를 제공하지는 않지만 아이디어를 얻길 바랍니다. 내가하려는 일을 더 우아하게 수행 할 수있는 방법에 대한 귀하의 아이디어를 들려주세요. 다시 감사합니다!



답변

내 요점을 매우 명확하게 설명하겠습니다 . 매개 변수가있는 싱글 톤은 싱글 톤이 아닙니다 .

정의에 따르면 싱글 톤은 한 번만 인스턴스화하려는 객체입니다. 생성자에 매개 변수를 공급하려는 경우 싱글 톤의 요점은 무엇입니까?

두 가지 옵션이 있습니다. 단일 데이터를 일부 데이터로 초기화하려면 인스턴스화 후 다음 과 같이 데이터로로드 할 수 있습니다 .

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

어쨌든 인스턴스화는 항상 매개 변수가 없습니다. 그렇지 않으면 싱글 톤이 싱글 톤이 아닙니다.


답변

다양한 매개 변수가있는 객체를 인스턴스화하고 재사용하려면 팩토리 와 같은 것이 필요하다고 생각합니다 . 동기화를 사용하여 구현 HashMap하거나 ConcurrentHashMap매개 변수 ( Integer예를 들어)를 ‘singleton’매개 변수화 가능 클래스에 매핑 하여 구현할 수 있습니다 .

대신 싱글 톤이 아닌 일반 클래스를 사용해야하는 시점에 도달 할 수 있습니다 (예 : 10.000 개의 다르게 매개 변수화 된 싱글 톤이 필요함).

이러한 상점의 예는 다음과 같습니다.

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

더 나아가서, enum고정 수의 정적 변형 만 허용하더라도 Java 는 매개 변수가있는 싱글 톤으로 간주되거나 사용 될 수 있습니다.

그러나 분산 1 솔루션 이 필요한 경우 측면 캐싱 솔루션을 고려하십시오. 예 : EHCache, Terracotta 등

1 아마 여러 대의 컴퓨터에 여러 개의 VM을 스패닝 의미한다.


답변

인스턴스화와 가져 오기를 분리하기 위해 구성 가능한 초기화 방법을 추가 할 수 있습니다.

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)앱 시작과 같이 한 번만 호출 하여 구성 할 수 있습니다 .


답변

일부 매개 변수가 필수임을 표시하려는 경우 빌더 패턴을 사용할 수도 있습니다.

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

    }


}

그런 다음 다음과 같이 생성 / 인스턴스화 / 매개 변수화 할 수 있습니다 .

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


답변

매개 변수가있는 싱글 톤은 싱글 톤아닙니다 “문이 완전히 올바르지 않습니다 . 코드 관점이 아닌 응용 프로그램 관점에서이를 분석해야합니다.

단일 응용 프로그램 실행에서 단일 객체 클래스를 만들기 위해 단일 클래스를 빌드합니다. 매개 변수가있는 생성자를 사용하면 코드를 유연하게 만들어 응용 프로그램을 실행할 때마다 싱글 톤 객체의 일부 속성을 변경할 수 있습니다. 이것은 싱글 톤 패턴을 위반하지 않습니다. 코드 관점에서 볼 때 위반으로 보입니다.

디자인 패턴은 유연하고 확장 가능한 코드를 작성하는 데 도움이되고 좋은 코드를 작성하는 데 방해가되지 않습니다.


답변

getter 및 setter를 사용하여 변수를 설정하고 기본 생성자를 비공개로 설정하십시오. 그런 다음 사용하십시오.

Singleton.getInstance().setX(value);


답변

로거 작성 / 검색 방법을 언급 한 사람이 아무도 없습니다. 예를 들어, 아래는 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)

이 indirections의 몇 가지 수준이있다, 그러나 중요한 부분은 아래 방법을 거의 작동 방법에 대한 모든 것을 알려줍니다. 해시 테이블을 사용하여 종료 로거를 저장하고 키는 이름에서 파생됩니다. 이름으로 로거가 존재하지 않으면 팩토리를 사용하여 로거를 작성한 다음 해시 테이블에 추가합니다.

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