0

I design a common API for selected printers of different brands in Java. Each printer uses a different underlying SDK with different functions, but any hardware my code runs on will have only one printer connected, this is a known guaranteed hardware limitation. Despite dealing with different SDKs, I also have common logic for any printer, such as a single thread executor. So, my goal is to ensure that only one instance of any Printer class can exist as well as having common logic in a common parent class.

My solution is to have public abstract class (instead of an interface) for the common logic (Printer), while creating one class for each printer type to override abstract functions that require specific implementations (such as accessing the underlying SDK of the printer) (PrinterA, PrinterB, PrinterC).

Now I have built a factory class PrinterFactory with a static async (concurrent) getPrinter() method that will check the hardware to see which of the printers is connected and then cache its instance within the factory to prevent more instances to be created.

I've come to the conclusion that I have build a "singleton factory" that produces and returns a polymorphic singleton.

Here is a rough example how it looks like:

public final class PrinterFactory {

    private static Printer instance;

    public static CompletableFuture<Printer> getInstance() {
        return CompletableFuture.supplyAsync(() -> {
            if (PrinterFactory.instance == null) {
                throw new Error("Not implemented.");
                // TODO: Determine the printer and create its instance and store it in PrinterFactory.instance
                // i.e: PrinterFactory.instance = new PrinterA(); // inherits Printer
            }
            return PrinterFactory.instance;
        });
    }
}

My question is, is this pattern a good move? Also, is the class term "PrinterFactory" even applicable since it only constructs one instance in its entire existence within the process lifetime?

2

4 Answers 4

3

It's not great...

My question is, is this pattern a good move? Also, is the class term "PrinterFactory" even applicable since it only constructs one instance in its entire existence within the process lifetime?

The problem I see with this implementation is that there are two cooks in charge of making the same broth.

A factory's job is to provide a public point to generate Foo from. You use it when consumers cannot feasibly create their own Foo "in the field" (as it were), and the factory provides that ability for them.

A singleton is designed to reduce instantiation to a single instance; but it almost inherently also comes with a way to access this single instance. In effect, it also acts as a public point to "generate" Foo from. By that I mean that consumer's don't actively care whether they're getting a newly created Foo or a reused pre-existing Foo.

There is no need to develop this responsibility twice. The two cooks are battling over the same broth.


... but it's not impossible either.

That being said, it is possible that this is a meeting of what was once two separate features.

For example, let's say this is about data access. You used to use a library that required generating new connection objects, and therefore you wrote a factory that generated these objects. Your entire codebase now uses that factory.

Today, you are changing your DB provider, and the new provider requires you to reuse the same connection object (for the purposes of this example, it is done via a singleton).

You don't want to rewrite your entire codebase to no longer use the factory and use the singleton instead, so you come up with a different solution: the factory simply returns the singletone. This way, you don't need to change your old code, and this all works.


Conclusion

The conclusion here is that having a factory return a singleton is somewhat superfluous and is better avoided when you're building something from scratch, simply as a matter of not doing the same thing twice or muddying the line between the responsibilities.

However, if you're trying to not have to rewrite legacy code, it can make sense to end up making a factory return a singleton just to avoid having to remove prior dependencies to the factory.

I would try to avoid it if you can, but I wouldn't go as far as to say that it should never exist either.

5
  • "There is no need to develop this responsibility twice." - I'm not going to. My idea is to have the constructor of PrinterA, PrinterB and PrinterC package-private and no singleton logic there, so that I end up with a hybrid factory without redundancy that takes care of that all by itself. If I get you right though, you say I should separate it, so I should implement the singleton in every child-class (or even parent class) and let the factory only return PrinterX.getInstance(); for the sake of not mixing up both concepts into one "superfluous" class. Am I correct in my understanding? Jul 25 at 8:51
  • 1
    @MartinBraun: The general point is more that it's a weird approach that I wouldn't take without an explicit reason to do so. There can be reasons to do so, but I would (in my role as tech lead) require a justification why it's a good idea to do it. In absence of such a justification, I would avoid it.
    – Flater
    Jul 25 at 8:59
  • My explicit reason would be: The printer recognition costs time, so my sub-class should be cached within the factory. If I move the singleton into the sub-classes I would need to create an additional enum field in the factory to remember the result of my printer recognition logic. I would also end up with redundant code. I got the opportunity to eliminate redundancy between the sub-classes by moving the singleton logic into my factory. I will definitely think this through another time, so please don't hesitate to give some feedback for my explicit reason to do it. I value your input. Jul 25 at 16:37
  • @MartinBraun: There is a difference between having a factory that caches its produced results, and a class that is made to be a singleton. It sounds like you're in need of the former. The latter is overkill and tends to have significant side effects in the long run.
    – Flater
    Jul 26 at 11:15
  • Since the parent Printer class has a dedicated executor for the jobs, a singleton is still great to prevent print collisions and memory redundancy, but your answer triggered re-thinking on my end. I decided to split-off singleton and factory logic to avoid a superfluously concept at least. Hopefully others will benefit from my question and all the answers incl. yours in the future as well. Jul 26 at 12:55
3

Having a Singleton for a printer is dubious. You state that only one printer can be connected at a time, but that is a very dubious assumption. It's wrong on every single computer that I own. Plus it's wrong once you accept things like a PDF or Postscript file generator, that is used identical to a printer.

It's double dubious when the application can be launched and someone tries to print while the printer is unplugged or turned off. Singletons are supposed to live from the moment they are first requested to the moment the application dies. If a printer can be turned off and on, how do you handle this? If a printer can be unplugged and another plugged in, how do you handle this.

It's triple dubious to make even the individual printer subclasses Singletons. If I can plug in two different printers, what would keep me from plugging in two identical printers?

Rule #1 when not to use Singletons: If you need more than one.

3
  • "If you need more or fewer than one."
    – Caleth
    Jul 25 at 16:12
  • Thank you for your answer and sorry, because I think I should've give more information to avoid such answer in the first place. I agree on your points, but they don't apply in my case: The application is an Android app that is designed to run on a set of POS handheld devices and each handheld has a dedicated low-level SDK and different access methods. Each device has only one printer built in. This is a controlled managed environment, there is no second printer at a time, period. Disconnection states will be reported by the SDK and handled properly by attempting reconnects and error reporting. Jul 25 at 16:29
  • @MartinBraun note that even in such cases, it's good to avoid relying on the singleton as much as possible. E.g. the printReceipt method should have a Printer parameter and not just access the singleton. Unit testing is one reason. Another reason is so you can reuse the code in the next version, which might run on a server connected to all the printers in the restaurant.
    – user253751
    Jul 27 at 14:54
1

A singleton is a class of that only one instance can ever exist. If you have a computer, to that only a single printer can be connected, e.g. because there is only a single port to connect a printer to, then printer would still not be a singleton, as there can be more than one printer, there can just only be one printer connected at a time. The port would then be a singleton, as there is only one port a printer can be connected to but theoretically that printer can be changed over time.

And for code testing purposes, it may make sense to use "a virtual printer port" and so you can have a different printer connected to that virtual port than you have connected to the physical port, which again speaks against printer being a singleton, so I can test multiple printer drivers in parallel.

I think I would rather have a single printer port representation in my app and on start up create a printer driver based upon one of the SDKs and assign it to that port representation, representing the printer currently connected to that port or none of no printer is connected. That way even changing the printer would be possible at runtime (or just adding one later on), even if that is not requirement to begin with and as I said, it makes it way easier to write unit tests.

0

It's good in the sense that you've separated the concerns of object lifetime management from the object itself (i.e. you didn't make the PrinterA class a singleton). However, with DI frameworks and practicing IoC, having a factory at all may be unnecessary. Let the framework do object lifetime management rather than your factories. However, if you are not going to use a standard framework, then I absolutely think your approach is the right one.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.