Create one object instance per class in hierarchy
Asked Answered
V

5

6

I was asked this question in an interview.

There are 3 classes A, B extends A & C extends B. We have to design these classes conforming to these constraints

  • Client can instantiate only one instance of A, one instance of B & one instance of C using their default constructor with new keyword.
  • Trying to create another instance of any of these class will result in an exception.
  • The designer of the class have to enforce the above 2 rules, so that client will experience the above rules implicitly (i.e. it should not be the responsibility of client to conform to above rules).

I suggested an approach using an static Map<Class, Object>. So for e.g. when somebody called new B() it would check if map.contains(B.class). If yes then throw exception & if not then save instance in map & let the object be created.

But the next question was how would I enforce this approach on each class? As per my approach each constructor would have to carefully populate the map otherwise it will break the constraint.

How would I solve this problem?

Veteran answered 27/5, 2019 at 14:34 Comment(0)
V
7

But the next question was how would I enforce this approach on each class? As per my approach each constructor would have to carefully populate the map otherwise it will break the constraint.

Simply by putting that map, and the code around it into some distinct Utility class. So that each and of your classes could do something like:

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

with

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

And given the fact that C extends B extends A, you probably only need that call in the constructor of class A. On the other hand, imagine that your first call to new C() causes an exception with the C-constructor, but a second call would not. Sure, that is a problem in itself, but still, the question remains: how do you ensure an object was fully and correctly initialized before adding it to such a map?!

Thus: there is a ton of things to consider when writing such utility code. Thus my answer would focus on the impractically, almost stupid-ness of the given design point, and suggest to throw it away and go for other solutions, for example something as simple as:

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

Don't waste too much time trying to give people a technical answer, when the real point is to not shoot yourself into the foot. And make no mistake: getting that map-based "singleton" approach to work robust and correct, for all kinds of contexts, consider that really hard.

In other words: if I would ask you this question in an interview, I would want to hear an answer that challenges that horrible design point. Sure, spent 10% of your time outlining a solution for the given scenario. But spend 90% of the time explaining why it is so bad, and why other solutions would be much better.

Varmint answered 27/5, 2019 at 14:40 Comment(1)
Using Enums is a very neat alternative idea. And keeping the object creation in a synchronized block is a good idea.Veteran
H
2

This pattern doesn't make much sense, but you can use the implicit super() invocation in default constructors to your advantage here.

Given:

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

And somewhere...

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

The last invocation will throw the exception because the implicit C constructor invokes super().

Making this thread-safe would be the next step, which you can do in a number of ways.

Henryhenryetta answered 27/5, 2019 at 14:44 Comment(2)
Now assume for a second that new C() does 5 things, and the last call throws some other exception. Only the first time you call new C() ... there are many interesting twists one could think of here ;-)Varmint
@Varmint I agree, this is a very simplified solution for what I expect to be a non-realistic scenario :D I like how your answer insists on the kludge after kludge of this scenario btw.Henryhenryetta
S
1

how would I enforce this approach on each class?

Since A is extended by B and C , you could add the check inside the A constructor , so everytime A, or B, or C is created it will check if it's already present, otherwise it will throws exception, something like:

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

In this way you will get an exception if you will create a second C instance

 A testA = new A();
 B testB = new B();
 C testC = new C();
 C testC2 = new C(); //Exception here
Signatory answered 27/5, 2019 at 14:42 Comment(0)
S
1

You could create set of classes that were created then in constructor of class A check if this.getClass() was created already or not. It not then add it, otherwise throw an exception. Below simple example:

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

Note: All classes that extend A will have this behaviour!

Spicy answered 27/5, 2019 at 14:50 Comment(0)
H
1

Static state may work like so:

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: ...
    }
}
Hurd answered 27/5, 2019 at 14:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.