... needs to use the services of a singleton ... which depends on which of A's subclasses is used:
That means the Singleton is not really your problem, it is the acquisition of the correct class based on the type asking!
Your design is too tightly coupled the way you are trying to do it. You need to completely decouple the Service from the Consumers of the service, Singleton is not important in this exercise.
What you need is some form of dependency injection.
This is exactly the type of problem that Guice was created to solve by being able to provide what classes get injected based on another classes type in a binding. That said ...
Most people do not realize that Java has always supported DI via the Constructor. Guice makes this less hard coded, but it is still a dependency that is injected to an instance.
Guice would make this trivial by injecting the correct service based on the class type. But it can be done without any DI framework/library. If using Guice is considered to heavy handed for your case then it can still be done easily.
Below is one way to do it without a framework/library:
public class Solution
{
static class Singleton
{
public static final Singleton INSTANCE;
static { INSTANCE = new Singleton(); }
private Singleton() { /* this is important */ }
public void doWhatever(@Nonnull final B b) { /* whatever */ }
public void doWhatever(@Nonnull final C c) { /* whatever */ }
}
static abstract class A
{
private final Singleton s;
public A(final Singleton s) { this.s = s; }
public abstract String getFilename();
}
static class B extends A
{
public B(final Singleton s) { super(s); }
@Override
public String getFilename() { /* code goes here */ }
}
static class C extends A
{
public C(final Singleton s) { super(s); }
@Override
public String getFilename() { /* code goes here */ }
}
}
The singleton anti-patterns you mention are just that:
The Singleton pattern should by hidden behind a Factory pattern. Your consumers of what needs to have 1 and only 1 should not care if there is 1 and only 1. They should only care that that object conforms to some contract of some interface.
My implementation is a naive Factory to create in static block. Most are create on first use which is not any better.
Using Enum to create Singleton objects is a misuse of the semantics of Enum and an anti-pattern and impossible to properly unit test.
Same with the all static utility class approach, impossible to unit test or replace with a different implementation. A combination of the two is a complete abomination that is impossible to unit test and a complete nightmare to maintain!
How you determine which subclass of A the Singleton works on is easy:
That is what overloading is for as shown in the code above.
Anything else is not doing it right. instanceof fail, reflection bigger fail.
Selecting logic based on Type can be done with overloading methods, or generics or with the appropriate design pattern.
Strategy Pattern would account for that easily and make N number of subclasses manageable and extensible at runtime.