A SOLID Basis for the Factory Pattern

Benefits of building your code with a Factory Pattern

In an effort to keep our work DRY (Don’t repeat yourself), it is a best practice to use design patterns. The Factory pattern is often used in the creation of objects. A developer can do all of the work of creating the object in the factory, instead of repeating it every time you want to create a new instance.

If you later need to change, rename, or replace the class responsible for the object building…


…you can do so and you will only have to modify the code in the factory. Not having to to hunt down every place in your project that uses the class to change the code can be a real benefit.

Using the factory pattern is not always needed or the “right thing to do”. At times a factory will simply be adding unneeded complexity.

However when making a fairly large or complex project you may save a lot of trouble down the road by using factories.

Why Factory Method Pattern

  • Encapsulate the logic for instantiating complex objects.
  • Reduce tight coupling between our application classes.
  • Makes it easier to add additional types of creation classes.

How SOLID is the Factory Pattern?

[S] The Factory Pattern permits each subclass to focus on the single responsibility of building a specific object type.

[O] With the Factory Method Pattern, Well tested base classes are easily extended without breaking the existing object building logic. Building with TTD (Test Driven Design) or at least a reasonable degree of Unit testing, the Factory Method Pattern is open to extension while remaining closed to modification.

[L] The Factory Method Pattern employs subclasses, implementing the base class’s methods and properties. The subclasses conform to the base class’s implied behavior. By this we can see that the Factory Method Pattern satisfies the Liskov Substitution Principle.

[ I ] When designing a Factory Method Pattern, the developer should promote the Interface Segregation Principle by making sure that abstractions such as abstract base classes and interfaces do not unnecessarily impose methods that could be added by concrete classes.

[D] The Factory Method Pattern is designed to decouple the lower lever concerns associated with the construction of specific objects from the higher level logic involved in making the decision to build objects.

How to implement the Factory Method Pattern

While not a requirement, it is good to start with a class having a name appended with “Factory” to make the pattern obvious. In this example, the class is making products so the name is ProductFactory. Making your pattern obvious is a way of documenting your code and making it easier to find and extend the pattern.

The factory class should also have an obvious build method name such as “build” or “create” that takes parameters. The parameters passed into the build method will be used to make determinations about which concrete classes to use in constructing new objects. Design the parameters with extensibility in mind. Often, is it best to package parameters into arrays so more or less parameters may be used without altering the basic structure of the original builder method. It often makes sense for the factory method pattern to be static or available as a service to other classes.

Create a Factory class

 $sku, 
                          'type' => $type, 
                          'data' => $data];
            $namespace = 'Pkl\\Factories\\Product\\';
            // Build the concreate classname based on type
            $className = $namespace . 'Product'. ucfirst($type);
            //Attempt to call the appropriate concreate class
            if(class_exists($className)) {
                return new $className($prod_data);
            } else {
                throw new \Exception($className . ' not found.');
            }

        }
    }

   // ... 
   
}
}

In this example, the concrete classes will be built based on abstraction in order to keep them consistent with the “contracted” properties and methods for the type of objects being built.

The abstraction of product defaults

sku = $sku;
      $this->data = $data;
    }

    public function getSku()
    {
      return $this->sku;
    }
    
    public function getData()
    {
      return $this->data;
    }
    
    public function getType()
    {
      return $this->type;
    }

}

The variations in the objects are built in the various concrete classes. Additional concrete classes may easily be built to satisfy future requirements without breaking the existing logic.

The concrete maker classes

data = $data['data'];
        $this->sku  = $data['sku'];
        $this->type = $data['type'];
    }
    
    public function getPrintSizeVertical(){
        return $this->data['data']['size_vertical'];
    }

    public function getPrintSizeHorizontal(){
        return $this->data['data']['size_horizontal'];
    }

}

data = $data['data'];
        $this->sku  = $data['sku'];
        $this->type = $data['type'];
    }
    
    public function getFrameInnerSizeVertical(){
        return $this->data['data']['frame_inner_size_vertical'];
    }

    public function getFrameInnerSizeHorizontal(){
        return $this->data['data']['frame_inner_size_horizontal'];
    }

    public function getFrameOuterSizeVertical(){
        return $this->data['data']['frame_outer_size_vertical'];
    }

    public function getFrameOuterSizeHorizontal(){
        return $this->data['data']['frame_outer_size_horizontal'];
    }

}

Additional concrete classes may easily be built to satisfy future requirements without breaking the existing logic.

Leave a Reply

Your email address will not be published. Required fields are marked *