2

I'm currently re-designing my hobby project and I'm trying to apply what I've learned recently from design patterns. Summary for context: the project listens for TCP commands from my phone (using another project not related to this question) and controls my PC using the Win32 API. This question concerns the Windows application.

Current design of the project Current design of the project

As you can see, I have already used the Command pattern to generalize what actions the TCP listener provides. It works well, although I'm not certain whether the ToggleShaderCommand should keep a state. I decided for it to have a state because it's a toggle and it's the command's responsibility to take care of calling the right operation (ClearShader/SetShaderUpscale).

Window is not an abstract class, because some other objects may create a Window using just a handle.

Now what I'm trying to do is use the Factory Method pattern, because of the lifetime of the Window objects. They sometimes get created (a new handle is found), but sometimes returned from cache. Furthermore, more Window classes may be added in the future with new possible actions.

enter image description here My attempt at using the Factory Method pattern

As you can see, I separated the window interaction logic into different classes. The Window classes now only hold the state (the window handle and possibly other data in the future). The responsibility of the WindowInteraction classes is to interact with their respective windows and to keep track of the Window objects and their caching.

Is the Factory Method pattern useful in this situation from the information I provided? If so, do I implement it correctly or is this design creating some future problems?

Here is the link for the diagram.

4

Factory Method Pattern Overview

The Factory Method pattern as originally presented makes most sense in the context of frameworks that let you derive your own classes from framework-provided base classes. E.g. the framework provides a Window class, and you derive MyWindow from it.

Sometimes, a framework has some setup procedure, or provides some functionality, and as a part of that, it needs to instantiate an object, but it can't know its concrete type, because the object is supplied by a consumer of the framework (anyone can derive a class, and the framework must be written independently of that).

So, the idea is to provide a virtual creator method that a derived class can override and supply the appropriate instance. E.g. inside the Window base class, the framework can just say this.CreateContextMenu(), and inside MyWindow, you can create an application-specific context menu on demand by overriding that method. It's a "virtual constructor".

enter image description here

But the core of the pattern is really that virtual method: some kind of an abstract creation operation, that you can supply your own version of. Most languages today support lambdas, so if you only need one kind of creator, you can just inject a lambda as a dependency. Or, you could have public creator methods that other classes can make use of - e.g. a higher-level class can ask an object to create a related object (of a certain subclass) in an abstract way. The exact form of the pattern isn't what makes the pattern; it's all about the major elements and the roles they play to fulfill the intent behind the pattern.

The central question: Do you have clients that need abstract creators?

I can't answer this for you because I don't know enough about your system, and it's not entirely clear from your question. Figure out who calls GetWindow() and why - and how abstract the context is.

If your WindowInteractor contains high-level logic that at some point needs to create/obtain a window without knowing its concrete type, then some variation of the Factory Method seems like a good fit. It also works if some other code that only knows about the abstract WindowInteractor type needs to ask an interactor for a window, and can't know the concrete type of the window (here, factory method connects parallel hierarchies, without the client being aware of them). Also, some objects could potentially get injected with a window-providing lambda that either creates a window or supplies the cached instance. There are different options.

If there isn't an aspect to it that can be described along these lines, then there's no Factory Method in there. Your commands seem to reference the derived interactors directly, so the call to GetWindow() is not polymorphic; this is not wrong in itself, but is not exactly Factory Method - perhaps your WindowInteractor is predominantly used for implementation inheritance, while the interface inheritance aspect of it is more of a "bookkeeping" mechanism (ensures that interactors consistently implement GetWindow(), but isn't meant to be used in a polymorphic way). Again, this is all pretty general, but I hope you'll find it useful to assess your particular case.

Future problems?

Is the Factory Method pattern useful in this situation from the information I provided? If so, do I implement it correctly or is this design creating some future problems?

Every design is potentially creating a future problem; a design that's amenable to a certain set of changes is rigid for other kinds of changes. Generally, when making design decisions, try to recognize the tradeoffs you're making, and then come up with something that works well for the domain as you currently understand it, while being conservative in order to avoid building into your code too many assumptions that are hard to reverse later (in the spirit of principles like YAGNI and KISS). This is because you'll learn more about the system as you go along, and your cost/benefit analysis of the tradeoffs will change; you need to be able to react to that - and eventually arrive at a design that's better suited to your actual system.

1
  • 2
    "Every design is potentially creating a future problem" is especially perceptive. Too many programmers seem to think that the measure of a good design is how many named Design Patterns™ it contains...
    – trentcl
    Aug 19 at 20:12

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.