Advice on Factory Method
Asked Answered
T

6

6

Using php 5.2, I'm trying to use a factory to return a service to the controller. My request uri would be of the format www.mydomain.com/service/method/param1/param2/etc. My controller would then call a service factory using the token sent in the uri. From what I've seen, there are two main routes I could go with my factory.

Single method:

class ServiceFactory { 
    public static function getInstance($token) { 
        switch($token) { 
            case 'location':
                return new StaticPageTemplateService('location');
                break;
            case 'product':
                return new DynamicPageTemplateService('product');
                break;
            case 'user'
                return new UserService();
                break;
            default:
                return new StaticPageTemplateService($token);
         }
    }
}

or multiple methods:

class ServiceFactory { 
    public static function getLocationService() { 
        return new StaticPageTemplateService('location');
    }
    public static function getProductService() { 
        return new DynamicPageTemplateService('product');
    }
    public static function getUserService() { 
        return new UserService();
    }
    public static function getDefaultService($token) { 
        return new StaticPageTemplateService($token);
    }
}

So, given this, I will have a handful of generic services in which I will pass that token (for example, StaticPageTemplateService and DynamicPageTemplateService) that will probably implement another factory method just like this to grab templates, domain objects, etc. And some that will be specific services (for example, UserService) which will be 1:1 to that token and not reused. So, this seems to be an ok approach (please give suggestions if it is not) for a small amount of services. But what about when, over time and my site grows, I end up with 100s of possibilities. This no longer seems like a good approach. Am I just way off to begin with or is there another design pattern that would be a better fit? Thanks.

UPDATE: @JSprang - the token is actually sent in the uri like mydomain.com/location would want a service specific to loction and mydomain.com/news would want a service specific to news. Now, for a lot of these, the service will be generic. For instance, a lot of pages will call a StaticTemplatePageService in which the token is passed in to the service. That service in turn will grab the "location" template or "links" template and just spit it back out. Some will need DynamicTemplatePageService in which the token gets passed in, like "news" and that service will grab a NewsDomainObject, determine how to present it and spit that back out. Others, like "user" will be specific to a UserService in which it will have methods like Login, Logout, etc. So basically, the token will be used to determine which service is needed AND if it is generic service, that token will be passed to that service. Maybe token isn't the correct terminology but I hope you get the purpose.

I wanted to use the factory so I can easily swap out which Service I need in case my needs change. I just worry that after the site grows larger (both pages and functionality) that the factory will become rather bloated. But I'm starting to feel like I just can't get away from storing the mappings in an array (like Stephen's solution). That just doesn't feel OOP to me and I was hoping to find something more elegant.

Tobacconist answered 7/1, 2011 at 16:4 Comment(2)
You are looking for Symfony Components Dependency Injection ContainerLeftover
+1 for the Component Dependency Injection Container suggestion, exactly what the author needs.Empirical
A
1

I think there is no way to avoid this token-service-mapping maintaining work when your site growing large. No matter how you implement this list, switch block, array, etc. this file will become huge someday. So my opinion is to avoid this list and make each token a service class, for those generic services, you can inherit them, like this

class LocationService extends StaticPageTemplateService { 
    public function __construct(){
        parent::__construct('location');
    }
}

class ServiceFactory { 
    public static function getInstance($token) { 
        $className = $token.'Service';
        if(class_exist($className)) return new $className();
        else return new StaticPageTemplateService($token);
    }
}

in this way, you can avoid to edit the factory class file each time a token added or changed, you just need to change the specific token file.

Antidote answered 7/1, 2011 at 16:4 Comment(0)
G
1

I have a better factory pattern solution that will allow you to add new services without doing anything more than creating a new class for that specific service. Outlined below:

For the Factory:

  class ServiceFactory{
    private static $instance = null;
    private static $services = array();
    private function __construct(){
      // Do setup
      // Maybe you want to add your default service to the $services array here
    }

    public function get_instance(){
      if($this->instance){
        return $this->instance;
      }
      return $this->__construct();
    }

    public function register_service($serviceName, $service){
      $this->services[$serviceName] = $service;
    }

    public function get_service($serviceName){
      return $this->services[$serviceName]->get_new();
    }
  }

An Abstract Service:

  include('ServiceFactory.php');

  class AbstractService{
    public function __construct($serviceName){
      $factory = ServiceFactory::get_instance();
      $factory->register_service($serviceName, $this);
    }

    public function get_new(){
      return new __CLASS__;
    }
  }

And then a concrete service:

  include('AbstractService.php');

  class ConcreteService extends AbstractService{
    // All your service specific code.
  }

This solution makes your dependencies one way and you can add new services by simply extending the AbstractService, no need to modify any existing code. You call into the factory with the get_service('news') or whichever you want, the factory looks up the associated object in its $services array and calls the get_new() function on that particular object which gets you a new instance of the specific service to work with.

Georgeta answered 20/1, 2011 at 21:12 Comment(1)
The problem with this is you can't ever know what code is using what factory/object, ie in IDE etc. Seems over-engineered and there are simpler approachesUracil
D
0

Here's how I do a singleton factory (comments removed for brevity):

Updated to better serve your purpose.

class ServiceFactory {
    private static $instance;
    private function __construct() {
        // private constructor
    }
    public function __clone() {
        trigger_error('Clone is not allowed.', E_USER_ERROR);
    }
    public static function init() {
        if (!isset(self::$instance)) {
            $c = __CLASS__;
            self::$instance = new $c;
        }
        return self::$instance;
    }
    public function get_service($name, $parameter) {
        $name .= 'TemplateService';
        return $this->make_service($name, $parameter);
    }

    private function make_service($name, $parameter) {
        if (class_exists($name)) {
            return new $name($parameter);
        } else {
            throw new LogicException('Could not create requested service');
            return false;
        }
    }
}

In it's simplest form like this, just pass a string name of the service:

function whatever() {
    $ServiceFactory = ServiceFactory::init();
    $new_service = $ServiceFactory->get_service('StaticPage', 'location');
    return $new_service;
}
Decentralization answered 7/1, 2011 at 17:1 Comment(5)
I updated it to include the parameter, and I had to remove the library caching to do so.Decentralization
@Decentralization Factory pattern != Singleton pattern... I get that there is a significant overlap, but dependency injection is probably the way to go here.Goldoni
So I would need to initialize $library with all of the tokens mapped to their respective services before I actually call get_service()?Tobacconist
No. Take a look at the make_service method. It makes the service and stores it in the library. So, when you first ask for the service, it makes it and returns it. Honestly, we can simplify this to remove the library. I'll update the method.Decentralization
Updated the make_service method to just spit out a new class instead of storing it in the library.Decentralization
O
0

I'm not a PHP developer, so I'm not going to attempt to show any code, but here's what I would do. I would implement the Strategy Pattern and create an IServiceProvider interface. That interface could have a GetService() method. Then you would create four new objects: LocationService, ProductService, UserService, and DefaultService, all of which would implement the IServiceProvider interface.

Now, in your factory, the constructor would take in a IServiceProvider and have one public GetService() method. When the method is called, it will use the strategy of the injected IServiceProvider. This improves extensability as you won't have to open the Factory everytime you have a new service, you would just create a new class that implements IServiceProvider.

I decided quick to mock this up quick in C# so you would have an example. I understand this isn't the language you are using, but maybe it will help clarify what I'm saying. Code shown below.

public interface IServiceProvider
{
    Service GetService();    
}

public class UserServiceProvider : IServiceProvider
{
    public Service GetService()
    {
        //perform code to get & return the service
    }
}

public class StaticPageTemplateServiceProvider : IServiceProvider
{
    public Service GetService()
    {
        //perform code to get & return the service
    }
}

public class DynamicPageTemplateServiceProvider : IServiceProvider
{
    public Service GetService()
    {
        //perform code to get & return the service
    }
}

public class DefaultServiceProvider : IServiceProvider
{
    public Service GetService()
    {
        //perform code to get & return the service
    }
}

public class ServiceFactory
{
    public ServiceFactory(IServiceProvider serviceProvider)
    {
        provider = serviceProvider;
    }
    private IServiceProvider provider;

    public Service GetService()
    {
        return provider.GetService();
    }
}
Orten answered 7/1, 2011 at 17:22 Comment(3)
At first glance, I really like this solution. But, I'm left with two questions. One, why do I need to send the ServiceProvider to a ServiceFactory when the ServiceProvider actually returns my implementation? Can't I just have the ServiceProvider give the controller the implementation directly? And two, I'm still left with a token in the controller (location, products, news, user, etc) in the controller in which I now need to get a ServiceProvider for. Which seems like a job for a factory and now I'm back to where I started, right?Tobacconist
You are correct with your first question, you probably wouldn't need the factory. The idea is that the factory could look at a configuration setting and determine which IServiceProvider to give back, or something like that. For your second question, it seems like each specific instance of IServiceProvider should be able to hold any token that would be needed. However, I don't completely understand what you are trying to do with the token. If you could explain the token a little more, maybe I could help you out. Good luck :)Orten
You could set up your interface to take in a token and handle it that way. Maybe you would need two different interfaces, one that uses a token, and one that doesn't?Orten
H
0

Service factory implementation (with an interface that we'll use in concrete classes):

class ServiceFactory
{
    private static $BASE_PATH = './dirname/';
    private $m_aServices;

    function __construct()
    {
        $this->m_aServices = array();

        $h = opendir(ServiceFactory::$BASE_PATH);
        while(false !== ($file = readdir($h)))
        {
            if($file != '.' && $file != '..')
            {
                require_once(ServiceFactory::$BASE_PATH.$file);
                $class_name = substr($file, 0, strrpos($file, '.'));

                $tokens = call_user_func(array($class_name, 'get_tokens'));
                foreach($tokens as &$token)
                {
                    $this->m_aServices[$token] = $class_name;
                }
            }
        }
    }

    public function getInstance($token)
    {
        if(isset($this->m_aServices[$token]))
        {
            return new $this->m_aServices[$token]();
        }
        return null;
    }
}

interface IService
{
    public static function get_tokens();
}

$BASE_PATH.'UserService.php':

class UserService implements IService
{
    function __construct()
    {
        echo '__construct()';
    }

    public static function get_tokens()
    {
        return array('user', 'some_other');
    }
}

So, what we're doing essentially is self-registering all tokens for any concrete class implementation. As long as your classes reside in $BASE_PATH, they'll automatically be loaded by the ServiceFactory when it's instantiated (of course, you could change ServiceFactory to provide this via static methods if you wanted to).

No need to have a big switch statement providing access to concrete implementations as they're all help in an internal map that's built by the get_tokens() function that are implemented at the concrete class level. The token->class relationship is stored in a 1:1 map within the service factory, so you'd need to refactor this if you're chaining tokens for whatever reason.

Hymenium answered 7/1, 2011 at 21:0 Comment(2)
Don't you think prefixing instance variables with m_ is superfluous in php, since you have to prefix them with $this-> anyway?Cuthbert
Sure, when you're reading through function implementations. However, I think it increases readability when you're just glancing through the variable declarations. And, anything that increases readability and understanding is a +1 for me ;) (m_ for member vars, s_ for statics, etc).Hymenium
A
0

And some that will be specific services (for example, UserService) which will be 1:1 to that token and not reused. So, this seems to be an ok approach (please give suggestions if it is not) for a small amount of services. But what about when, over time and my site grows, I end up with 100s of possibilities. This no longer seems like a good approach. Am I just way off to begin with or is there another design pattern that would be a better fit? Thanks.

Sorry to say, but I think you're now trying to solving a problem that you've created for yourself.

the token is actually sent in the uri like mydomain.com/location would want a service specific to loction and mydomain.com/news would want a service specific to news. Now, for a lot of these, the service will be generic. For instance, a lot of pages will call a StaticTemplatePageService in which the token is passed in to the service. That service in turn will grab the "location" template or "links" template and just spit it back out.

Some have already suggested using a Dependency Injection Container to solve the whole factory issue, but I wonder why there is a need for a factory in the first place? You seem to be writing a Controller (I guess), that can generate a response for a multitude of different types of request, and you're trying to solve it all in one class. I'd instead make sure that the different requests (/location, /news) map to dedicated, small, readable controllers (LocationController, NewsController). As one controller needs only one service this should be much easier to write, maintain and expand.

That way, you solve the dependencies in dedicated, concise, readable classes instead of one giant God class. That means you'll have no issues with a switch of hundreds of lines either, you should just map "location" to LocationController, "news" to NewsController, etc. A lot of PHP frameworks these days use a FrontController for that, and I imagine that is the way to go for you as well.

PS: to make sure the NewsService actually makes into the NewsController, I would suggest using a dependency injection container. It makes your life easier ;)

Asinine answered 17/1, 2011 at 8:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.