Советы по заводскому методу - PullRequest
6 голосов
/ 07 января 2011

Используя php 5.2, я пытаюсь использовать фабрику, чтобы вернуть сервис контроллеру.Моя просьба будет иметь формат www.mydomain.com/service/method/param1/param2/etc.Мой контроллер затем вызвал бы сервисную фабрику, используя токен, отправленный в URI.Из того, что я видел, есть два основных маршрута, по которым я мог бы пойти с моей фабрикой.

Один метод:

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);
         }
    }
}

или несколько методов:

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);
    }
}

Поэтому, учитывая это, у меня будет несколько общих сервисов, в которых я передам этот токен (например, StaticPageTemplateService и DynamicPageTemplateService), который, вероятно, реализует другой метод фабрики, такой же, как этот, для захвата шаблонов, объектов домена и т. Д.будут специфические сервисы (например, UserService), которые будут 1: 1 к этому токену и не будут использоваться повторно.Таким образом, это кажется правильным подходом (пожалуйста, дайте предложения, если это не так) для небольшого количества услуг.Но что касается того, когда со временем и мой сайт разрастется, у меня появятся сотни возможностей.Это больше не похоже на хороший подход.Я просто далек от того, чтобы начать, или есть другой шаблон дизайна, который будет лучше подходить?Спасибо.

ОБНОВЛЕНИЕ: @JSprang - токен на самом деле отправляется в URI, как mydomain.com/location, для которого нужен сервис, специфичный для loction, а mydomain.com/news для сервиса, специфичного для новостей.Теперь для многих из них услуга будет общей.Например, многие страницы будут вызывать StaticTemplatePageService, в которой токен передается службе.Этот сервис, в свою очередь, будет захватывать шаблон "location" или "links" и просто выплевывать его обратно.Некоторым понадобится DynamicTemplatePageService, в который будет передан токен, например, «новости», и этот сервис получит объект NewsDomainObject, определит, как его представить, и выплюнет его обратно.Другие, такие как «пользователь», будут специфичны для UserService, в котором у него будут такие методы, как Login, Logout и т. Д. Таким образом, в основном токен будет использоваться для определения, какая служба необходима, и если это общая служба, этот токен будетперешел на эту службу.Возможно, токен не является правильной терминологией, но я надеюсь, что вы поняли цель.

Я хотел использовать фабрику, чтобы я мог легко поменять, какой Сервис мне нужен, в случае, если мои потребности изменятся.Я просто волнуюсь, что после того, как сайт станет больше (как страниц, так и функциональности), фабрика станет довольно раздутой.Но я начинаю чувствовать, что просто не могу уйти от хранения отображений в массиве (как решение Стивена).Это просто не кажется мне ООП, и я надеялся найти что-то более элегантное.

Ответы [ 6 ]

1 голос
/ 21 января 2011

У меня есть лучшее фабричное решение, которое позволит вам добавлять новые сервисы, не делая ничего, кроме создания нового класса для этого конкретного сервиса.Ниже указано:

Для фабрики:

  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();
    }
  }

Абстрактная услуга:

  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__;
    }
  }

А затем конкретная услуга:

  include('AbstractService.php');

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

Это решение делает ваши зависимости односторонними, и вы можете добавлять новые сервисы, просто расширяя AbstractService, без необходимости изменять какой-либо существующий код.Вы вызываете фабрику с помощью get_service ('news') или чего угодно, фабрика ищет связанный объект в своем массиве $ services и вызывает функцию get_new () для этого конкретного объекта, которая дает вам новый экземпляр определенногосервис для работы.

1 голос
/ 11 января 2011

Я думаю, что нет способа избежать работы по поддержанию отображения токенов, когда ваш сайт становится большим. Независимо от того, как вы реализуете этот список, блок переключателей, массив и т. Д., Этот файл когда-нибудь станет огромным. Поэтому я считаю, что нужно избегать этого списка и делать каждый токен классом обслуживания, для этих общих сервисов вы можете наследовать их, как это

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);
    }
}

таким образом, вы можете избежать редактирования файла фабричного класса каждый раз, когда токен добавляется или изменяется, вам просто нужно изменить определенный файл токена.

0 голосов
/ 17 января 2011

И некоторые из них будут определенными службами (например, UserService), которые будут 1: 1 для этого токена и не будут повторно использоваться. Итак, это Кажется, это хороший подход (пожалуйста, дайте предложения, если это не так) для небольшого количества услуг. Но как насчет того, когда, более время и мой сайт растут, я в конечном итоге с сотнями возможностей. Это больше не похоже на хороший подход. Я просто далеко для начала или есть другой шаблон дизайна, который будет лучше подходить? Благодаря.

Извините, но я думаю, что вы сейчас пытаетесь решить проблему, которую создали для себя.

токен на самом деле отправляется в URI, как mydomain.com/location хочет, чтобы служба относилась к loction и mydomain.com/news захотят услугу, специфичную для новостей. Теперь для многих из них Сервис будет общим. Например, многие страницы будут вызывать StaticTemplatePageService, в котором токен передается службе. Этот сервис, в свою очередь, будет захватывать шаблон "location" или шаблон "ссылки" и просто выплюнуть его обратно.

Некоторые уже предложили использовать контейнер для инъекций зависимости для решения всей проблемы фабрики, но мне интересно, зачем вообще нужна фабрика? Кажется, вы пишете контроллер (я полагаю), который может генерировать ответ для множества различных типов запросов, и вы пытаетесь решить все это в одном классе . Вместо этого я бы удостоверился, что различные запросы (/ location, / news) отображаются на выделенные небольшие читаемые контроллеры (LocationController, NewsController). Поскольку одному контроллеру нужен только один сервис, его будет гораздо проще писать, поддерживать и расширять.

Таким образом, вы решаете зависимости в специализированных, кратких, читаемых классах вместо одного гигантского класса Бога. Это означает, что у вас не возникнет проблем с переключением сотен строк, вам просто нужно сопоставить «location» с LocationController, «news» с NewsController и т. Д. Многие PHP-фреймворки сегодня используют для этого FrontController, и я представьте себе, что это путь для вас.

PS: чтобы убедиться, что NewsService действительно превращается в NewsController, я бы предложил использовать контейнер внедрения зависимостей. Это делает вашу жизнь проще;)

0 голосов
/ 08 января 2011

Реализация фабрики сервисов (с интерфейсом, который мы будем использовать в конкретных классах):

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');
    }
}

Итак, по сути, мы делаем саморегистрацию всех токенов для любой конкретной реализации класса. Пока ваши классы находятся в $ BASE_PATH, они будут автоматически загружаться ServiceFactory при его создании (конечно, вы можете изменить ServiceFactory, чтобы обеспечить это статическими методами, если хотите).

Нет необходимости в большом операторе switch, предоставляющем доступ к конкретным реализациям, поскольку все они помогают во внутренней карте, созданной функцией get_tokens (), которая реализована на уровне конкретного класса. Отношения token-> class хранятся на карте 1: 1 в фабрике сервисов, поэтому вам придется ее реорганизовать, если вы по какой-либо причине объединяете токены.

0 голосов
/ 07 января 2011

Я не разработчик PHP, поэтому я не буду пытаться показать какой-либо код, но вот что я хотел бы сделать.Я бы реализовал шаблон стратегии и создал бы интерфейс IServiceProvider.Этот интерфейс может иметь метод GetService ().Затем вы должны создать четыре новых объекта: LocationService, ProductService, UserService и DefaultService, каждый из которых будет реализовывать интерфейс IServiceProvider.

Теперь на вашей фабрике конструктор будет принимать IServiceProvider и иметь один открытый метод GetService ().Когда метод вызывается, он будет использовать стратегию внедренного IServiceProvider.Это улучшает расширяемость, поскольку вам не придется открывать Factory каждый раз, когда у вас появляется новый сервис, вы просто создаете новый класс, реализующий IServiceProvider.

Я решил быстро смоделировать это в C #, чтобы выесть пример.Я понимаю, что это не тот язык, который вы используете, но, возможно, это поможет уточнить, что я говорю.Код показан ниже.

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();
    }
}
0 голосов
/ 07 января 2011

Вот как я делаю синглтон-фабрику (для краткости комментарии удалены):

Обновлено, чтобы лучше соответствовать вашим целям.

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;
        }
    }
}

В простейшей форме, подобной этой, просто передайте строковое имя службы:

function whatever() {
    $ServiceFactory = ServiceFactory::init();
    $new_service = $ServiceFactory->get_service('StaticPage', 'location');
    return $new_service;
}
...