Создать один экземпляр объекта на класс в иерархии - PullRequest
6 голосов
/ 27 мая 2019

Мне задали этот вопрос в интервью.

Есть 3 класса A, B extends A и C extends B. Мы должны спроектировать эти классы в соответствии с этими ограничениями

  • Клиент может создать только один экземпляр A, один экземпляр B и один экземпляр C, используя конструктор по умолчанию с ключевым словом new.
  • Попытка создать другой экземпляр любого из этих классов приведет к исключению.
  • Разработчик класса должен применять приведенные выше 2 правила, чтобы клиент неявно испытывал вышеуказанные правила (т. Е. Клиент не должен отвечать за соблюдение вышеуказанных правил).

Я предложил подход с использованием static Map<Class, Object>. Так, например, когда кто-то звонил new B(), он проверял бы map.contains(B.class) Если да, тогда выведите исключение, а если нет, сохраните экземпляр в карте и позвольте объекту быть созданным.

Но следующий вопрос был как бы я применил этот подход в каждом классе ? Согласно моему подходу каждый конструктор должен был бы тщательно заполнить карту, иначе это нарушит ограничение.

Как бы я решил эту проблему?

Ответы [ 5 ]

7 голосов
/ 27 мая 2019

Но следующий вопрос состоял в том, как бы я применял этот подход в каждом классе? Согласно моему подходу каждый конструктор должен был бы тщательно заполнить карту, иначе это нарушит ограничение.

Просто поместив эту карту и код вокруг нее в какой-то отдельный класс Utility. Чтобы каждый из ваших классов мог делать что-то вроде:

public WhateverClassConstructor() {
  synchroized(someLock) {
     SingeltonChecker.ensureUnique(this);

с

 public static void ensureUnique(Object objectToAdd) {
   Class<?> classToAdd = o.getClass();
   ...

А учитывая тот факт, что C расширяет B, расширяет A, вам, вероятно, нужен только этот вызов в конструкторе класса A. С другой стороны, представьте, что ваш первый вызов new C() вызывает исключение в C-конструкторе, но второй звонок не будет. Конечно, это проблема сама по себе, но все же остается вопрос: как вы гарантируете, что объект был полностью и правильно инициализирован перед добавлением его в такую ​​карту?!

Таким образом: при написании такого служебного кода необходимо учитывать тонну . Таким образом, мой ответ будет сосредоточен на непрактичной, почти глупой заданной точке проектирования, и предложит отбросить его и перейти к другим решениям, например, к такому простому как:

public enum SingletonObjectHolder {
  private final Object hold;
  A_INSTANCE(new A()), B_INSTANCE(new B())...

  public Object getSingleton() { return hold; }
  private SingletonObjectHolder(Object o) { hold = o };

Не тратьте слишком много времени, пытаясь дать людям технический ответ, когда real указывает на , а не , стреляйте себе в ногу. И не заблуждайтесь: заставить этот основанный на карте подход «синглтон» работать надежно и корректно для всех видов контекста, считайте, что действительно трудно .

Другими словами: если бы я задал вам этот вопрос в интервью, я бы хотел услышать ответ, который оспаривает эту ужасную идею. Конечно, вы потратили 10% своего времени на обдумывание решения для данного сценария. Но потратьте 90% времени, объясняя, почему это так плохо, и почему другие решения будут намного лучше.

2 голосов
/ 27 мая 2019

Этот шаблон не имеет особого смысла, но вы можете использовать неявный вызов super() в конструкторах по умолчанию для вашего преимущества здесь.

Дано:

class A{
        static final Set<Class<?>> classes = new HashSet<>();
        public A() {
            if (!classes.add(getClass())) 
               throw new IllegalStateException(
                  "One instance of " + getClass() + " already created."
               );
        }
    }
class B extends A{}
class C extends B{}

И где-то ...

new A();
new B();
new C();
new C();

Последний вызов вызовет исключение, потому что неявный конструктор C вызывает super().

Следующим шагом будет сделать этот потокобезопасным, что вы можете сделать несколькими способами.

1 голос
/ 27 мая 2019

Статическое состояние может работать так:

import java.util.HashSet;
import java.util.Set;

class Aa {
    private static Set<Class> set = new HashSet();

    static void validateAndAdd(Class c) {
        if (set.contains(c) ) {
            throw new IllegalStateException("...");
        }
        set.add(c);
        System.out.println(set);
    }

    public Aa() {
        validateAndAdd(getClass());
    }
}

class Bb extends Aa {
}

class Cc extends Bb {
}

public class Main {
    public static void main(String[] args) throws Exception {
        new Cc(); // [class Cc]
        new Aa(); // [class Cc, class Aa]
        new Bb(); // [class Bb, class Cc, class Aa]
        new Bb(); // IllegalStateException: ...
    }
}
1 голос
/ 27 мая 2019

Вы можете создать набор классов, которые были созданы затем в конструкторе класса A, проверить, был ли создан this.getClass () уже или нет.Не добавляйте его, иначе выведите исключение.Ниже приведен простой пример:

static class A {
    private static Set< Class< ? > > createdClasses = new HashSet<>();
    {
        if( !createdClasses.add( this.getClass() ) ) {
            throw new RuntimeException( "Cannot create 2nd class" );
        }
    }
}
static class B extends A {}
static class C extends B {}

Примечание. Все классы, расширяющие класс A, будут иметь такое поведение!

1 голос
/ 27 мая 2019

как бы применить этот подход к каждому классу?

Поскольку A расширяется на B и C, вы можете добавить проверку внутри Aконструктор, поэтому каждый раз, когда создается A, B или C, он проверяет, присутствует ли он, в противном случае он вызывает исключение, что-то вроде:

public static class A{
    static List<Class<? extends A>> createdClasses = new ArrayList<>();
    public A() {
        if (createdClasses.contains(getClass())) {
            throw new InvalidParameterException("Class already present!");
        }else{
            createdClasses.add(getClass());
        }
    }
}

Таким образом, вы получите исключение, если будетесоздать второй C экземпляр

 A testA = new A();
 B testB = new B();
 C testC = new C();
 C testC2 = new C(); //Exception here
...