Question

An increasingly popular definition of factory method is: a static method of a class that returns an object of that class' type. But unlike a constructor, the actual object it returns might be an instance of a subclass. enter image description here

From : https://sourcemaking.com/design_patterns/factory_method

Doesn't it violate OCP?

I have also seen this in production code.

What would be the correct implementation?

I can think of a separate Creator class with static method, which returns subclass objects. My proposed solution is clearly not abstract factory as it doesn't create families of products, also it doesn't fit any of the GoF creational pattern. So I am skeptical about my solution.

Was it helpful?

Solution

According to the Open/Close Principe (OCP):

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

Let's examine your static factory architecture under this perspective:

  • Imagine that we want to extend our design horizontally with a subclass ProductThree. How could the makeProduct() return an object that could be one of the subclass without knowing the constructor of the subclasses ? I can think of two approaches:
    • You might have to change the method if it is implemented as a huge switch or chained if. This would infringe OCP.
    • You could think of a self registration mechanism that decouples Product from its subclasses (e.g. using a map / associative container mapping a class name / identifier with a class maker method- Java example). This would meet extensibility requirement and remain compliant with OCP.
  • Imagine that we want to extend our design vertically, by specializing ProductOne into ProductOneA and ProductOneB. To achieve this, you could not rely on sub class constructors in makeProduct(). You would have to use a makeProductOne(). This requires that the self registration is designed so to enable cascading through the hierachy. Although this could be challenging, it doesn't seem impossible.
  • Suppose that we could find a way to implement the self-registration requirement in the design. In this case you could completely seal the factory method, in compliance with OCP.

In conclusion, the concept of the static factory is compliant with OCP. Of course, specific implementation might infringe OCP, but only if they wouldn't take into consideration the additional requirements I mentioned above.

Note that using a factory class is a cleaner approach : it enforces separation of concerns (i.e. avoid that the factory knows about internals of the Product). But with regard to OCP, using a static method as explained above is an acceptable alternative.

I can't tell for other languages, but Andrei Alexandrescu demonstrated the implementation of a factory method in "Modern C++ design - Generic Programming and design patterns applied". It's full OCP using a map and registering/unregistering functions. The only difficulty he mentions it the type identifier that has to be provided to the factory. Of course, an enum would be an OCP issue, but he explored several alternatives using RTTI or a unique ID generator to circumvent this issue.

OTHER TIPS

The reason you don't see static factory methods listed in the GoF book is because this pattern doesn't use polymorphism in any interesting way. Your diagram suggests this, but most languages do not support the structure it shows. Specifically, a static method cannot also be virtual. There is no instance object to dispatch on. It is not possible to override a static method in the way you could override an instance method (some languages have class methods though, where we can dispatch on the class object). While the GoF book is only concerned about showcasing object-oriented patterns, static factory methods are still a common pattern in many languages.

What the page you linked to describes are two distinct patterns.

First we have the common pattern that rather than exposing a constructor of our class, we provide a static method. Many languages allow us to define multiple constructors and resolves them through method overloading, but static methods can have different names which is far more programmer-friendly. The linked page uses Color objects as an example. new Color(float, float, float) might be obvious, but can't be overloaded with different color models (RGB vs. HSV). Static methods can disambiguate through the name: Color::make_rgb(float, float, float) versus Color::make_hsv(float, float, float). This is about good API design, but has nothing to do with object orientation.

Using (static) factory methods also offers us more freedom in the implementation. We could map the HSV colors to RGB internally. Or Color could be abstract and used different subclasses for each color space, without exposing this difference to the user. This implementation detail is entirely encapsulated:

public static Color makeRGB(float r, float g, float b) {
  return new ColorRGB(r, g, b);
}

public static Color makeHSV(float h, float s, float v) {
  return new ColorHSV(h, s, v);
}

In my experience, such methods make code less ambiguous and make an API more approachable and maintainable. In C++, there's the additional benefit that static methods can use template argument deduction, whereas class template parameters have to be provided explicitly in a constructor call.

Factory methods have another interesting feature: We can decide in our code which concrete type to instantiate. I have used this in some parsers:

Expression makeBinaryExpression(Expression left, String operator, Expression right) {
  if (operator == "+") return new Addition(left, right);
  if (operator == "-") return new Subtraction(left, right);
  if (operator == "*") return new Multiplication(left, right);
  if (operator == "/") return new Division(left, right);
  throw new ParseException("Expected +, -, *, /, but got " + operator);
}

You are right that such static methods pose an extensibility problem. How could I add the CMYK color model to the Color class? I can't, without editing the Color class, or replacing it with a different but compatible class (by changing imports, or by C++ templates). If extensibility is required, the API should be made virtual – we'll come back to that in a minute.

However, not all code needs to be polymorphic. Especially if the behaviour is “pure” and doesn't interact with any outside state and when the behaviour is nor likely to change, non-virtual methods still have a place.

Also, the open-closed principle is only relevant when you can't change the source and release a new version. This is the case with published APIs where the publisher and consumer are in different organizations. In such a case, there must be a clear API and this interface must be carefully designed to allow consumers to adapt it to their needs – often by requiring dependency injection and consistently expressing the API in terms of user-implementable interfaces. But if the code is only used within the same project, the OCP is not as relevant (relying on interfaces can still make it easier to refactor, though).

Now let's get back to polymorphism. The GoF book does list the Factory Method Pattern and likens it to a “virtual constructor”. The whole point here is that the factory method is virtual and overrideable. This of course means that a factory method on the Product class is useless, since we would need to have an existing Product in order to create it. Instead, there is a different Creator class. The factory method is then an example of the Template Method Pattern which can be overridden by Creator-subclasses to provide a specific Product-subclass. The Abstract Factory Pattern is an example of a creator with multiple related factory methods.

Occasionally, factory methods are also used as a dependency injection mechanism. The base class defines some template methods to construct the dependencies, which can then be changed through subclassing. However, my experience shows that using small Strategy objects to open up dependency construction is a more flexible and simpler approach rather than requiring the whole target class to be inherited.

Licensed under: CC-BY-SA with attribution
scroll top