【问题标题】:AbstractFactory in PHP without Method Overload没有方法重载的 PHP 中的 AbstractFactory
【发布时间】:2015-01-23 20:04:23
【问题描述】:

情况

我目前有 4 种类型的用户,我们预计未来至少还会增加 3 种。目前他们是:

  • 管理员(店铺管理员组)
  • 员工(店长)
  • 员工(店员)
  • 客户

在不久的将来,我必须让两名员工同时成为客户。我还会有支持中心和记者。

问题

创造。创建。创建。我不担心访问控制、权限等……我现在拥有的代码可以在这方面创造奇迹。我的问题只是关于创作。似乎抽象工厂对我来说可能是一个,但事实是所有那些使用书籍和汽车教授设计模式的“抽象教程”并没有帮助我把它带到我的情况。要么我使用了错误的设计模式,要么我不理解它。

我的尝试

在 UserFactory 类中,我们可以看到我的问题的根源:abstract public function signUp();。这是不好的做法,甚至会导致 PHP 5.4+ 上的严格标准错误不尊重方法签名。在 Java 中,我会使用方法重载来解决这个问题。在 PHP 中,方法重载的工作方式不同,并且不允许我以这种方式工作。

<?php

abstract class UserFactory {

    const ADMIN = 'AdminRecord';
    const MANAGER = 'ManagerRecord';
    const SALESMAN = 'SalesmanRecord';
    const CUSTOMER = 'CustomerRecord';

    public static function manufacture($type) {
        return new $type;
    }

    protected $accountController;
    protected $emailController;
    protected $toolMailer;

    function __construct() {
        $this->accountController = new AccountController();
        $this->emailController = new EmailController();
        $this->toolMailer = new ToolMailer();
    }

    abstract public function signUp();
}

这是我的第一个用例:创建新管理员。

class AdminRecord extends UserFactory {

    protected $accountCompanyController;

    function __construct() {
        parent::__construct();
        $this->accountCompanyController = new AccountCompanyController();
    }

    public function signUp($name, $email, $password, $companyId, $access) {
        $accountId = $this->accountController->add($name, $password);
        $this->emailController->add($email, $accountId);
        $this->accountCompanyController->add($accountId, $companyId, $access);

        $this->toolMailer->adminWelcome($name, $email, $password);
    }

}

在这里我创建了一个新的抽象类,因为我的两个用例属于同一个实体(销售员和经理都是具有不同访问级别的员工)。

abstract class StaffRecord extends UserFactory {

    protected $staffController;

    function __construct() {
        parent::__construct();
        $this->staffController = new staffController();
    }

}

在这里,SignUp 的签名与管理员的签名相同,这排除了使用func_num_args()func_get_args()。等等,但是在 Java 中,您将无法使用方法重载来解决这个问题。没错,但在 Java 中,我可以将 int $shopId 替换为 Shop shop,将 int $companyId 替换为 Company company

class ManagerRecord extends StaffRecord {

    public function signUp($name, $email, $password, $shopId, $access) {
        $accountId = $this->accountController->add($name, $password);
        $this->emailController->add($email, $accountId);
        $this->staffController->add($accountId, $shopId, $access);
        $this->toolMailer->managerWelcome($name, $email, $password);
    }

}

这里的 SignUp 方法与之前看到的两种情况都不同。

class SalesmanRecord extends StaffRecord {

    public function signUp($name, $email, $password, $cpf, $shopId, $access) {
        $accountId = $this->accountController->addSeller($name, $password, $cpf);
        $this->emailController->add($email, $accountId);
        $this->staffController->add($accountId, $shopId, $access);
        $this->toolMailer->salesmanWelcome($name, $email, $password);
    }

}

这里的 SignUp 方法比以前更不一样了。

class CustomerRecord extends UserFactory {

    protected $customerController;

    function __construct() {
        parent::__construct();
        $this->customerController = customerController();
    }

    public function signUp($name, $email, $password, $cpf, $phone, $birthday, $gender) {
        $accountId = $this->accountController->addCustomer($name, $password, $cpf, $phone, $birthday, $gender);
        $this->emailController->add($email, $accountId);
        $this->toolMailer->customerWelcome($name, $email, $password);
    }

}

【问题讨论】:

  • 您可能想查看Builder Pattern
  • 另外,我个人不会在这里使用继承。我更喜欢与角色对象有关系的用户对象(或几个角色对象以同时支持您的“员工”和“客户”用例)。
  • 我在这段代码中没有看到抽象工厂范式。我看到很多继承。本质上,您应该有一个使用具体工厂的抽象工厂。至于signUp() 方法的签名问题,您可以只使用一个数组作为参数来接受多个值,然后使用array_key_exists() 创建适当的变量。您甚至可以使用类型提示将传递给 signUp() 的参数强制为数组。

标签: php design-patterns abstract-factory


【解决方案1】:

这是我的实现:

我利用 Interface 来让 signUp 函数为每个用户类型接受不同类型的参数;

创建的接口:

namespace main;
interface UserInterface { }

您可以添加每个类需要实现的方法。目前,仅将其用作 signUp() 的类型提示对象;

通过在 signUp(User $user) 上使用类型提示,它将解决您在注册时传递的不同类型签名的问题。它可以是用户类型管理员、经理、销售员和客户。每个 {User}Record 都扩展并实现了抽象工厂,但实现方式不同。

我假设每种用户类型都有相应/独特的行为。我添加了名为:AbstractUser.php、UserAdmin.php、UserManager.php、UserSalesman.php 和 UserCustomer.php 的额外类。每个类将包含不同类型的用户和属性,但扩展了每个类通用的抽象类用户(电子邮件、姓名、密码);

AbstractUser.php - 我注意到一个用户的共同属性,所以我创建了一个抽象用户。通用属性(电子邮件、姓名、密码)

<?php

namespace main;

abstract class AbstractUser {
    public $email;
    public $name;
    public $password;

    public function __construct($email, $name, $password) {
        $this->email = $email;
        $this->name = $name;
        $this->password = $password;
    }
}

让我们重写你的 UserFactory.php。但是这一次,它包含了我们作为User构建的UserInterface.php的接口;

namespace main;

use main\UserInterface as User;

abstract class UserFactory {
    const ADMIN = 'AdminRecord';
    const MANAGER = 'ManagerRecord';
    const SALESMAN = 'SalesmanRecord';
    const CUSTOMER = 'CustomerRecord';

    public static function manufacture($type) {
        return new $type;
    }

    protected $accountController;
    protected $emailController;
    protected $toolMailer;

    function __construct() {
        $this->accountController = new \stdClass();
        $this->emailController = new \stdClass();
        $this->toolMailer = new \stdClass();
    }

    abstract public function signUp(User $user);
}

注意方法signUp();我在创建的界面中键入提示它,这意味着它将只接受具有 User 实例的对象用户(实现用户界面)。

我假设接下来的代码集是不言自明的:

用户管理员:

<?php
namespace main;

use main\AbstractUser;

class UserAdmin extends AbstractUser implements UserInterface {
    public $companyId;
    public $access;

    public function __construct($email, $name, $password, $companyId) {
        parent::__construct($email, $name, $password);
        $this->companyId = $companyId;
        $this->access = UserFactory::ADMIN;
    }
}

AdminRecord: signUp(User $user) 应该只接受 UserAdmin.php 的实例

<?php

namespace main;

use main\UserFactory;
use main\UserInterface as User;

class AdminRecord extends UserFactory {
    protected $accountCompanyController;

    function __construct() {
        parent::__construct();
        $this->accountCompanyController = new \stdClass(); //new AccountCompanyController();
    }

    public function signUp(User $user) {
        $accountId = $this->accountController->add($user->name, $user->password);
        $this->emailController->add($user->email, $accountId);
        $this->accountCompanyController->add($accountId, $user->companyId, $user->access);
        $this->toolMailer->adminWelcome($user->name, $user->email, $user->password);
    }
}

让我们重写您的抽象 StaffRecord.php:(我认为没有变化)

<?php
namespace main;

use main\UserFactory;

abstract class StaffRecord extends UserFactory {
    protected $staffController;

    function __construct() {
        parent::__construct();
        $this->staffController = new \stdClass(); //staffController
    }
}

用户管理器:

<?php

namespace main;

use main\AbstractUser;

class UserManager extends AbstractUser implements UserInterface {
    public $shopId;
    public $access;

    public function __construct($email, $name, $password, $shopId) {
        parent::__construct($email, $name, $password);
        $this->shopId = $shopId;
        $this->access = UserFactory::MANAGER;
    }
}

经理记录:

<?php

namespace main;

use main\StaffRecord;
use main\UserInterface as User;

class ManagerRecord extends StaffRecord {
    public function signUp(User $user) {
      $accountId = $this->accountController->add($user->name, $user->password);
      $this->emailController->add($user->email, $accountId);
      $this->staffController->add($accountId, $user->shopId, $user->access);
      $this->toolMailer->managerWelcome($user->name, $user->email, $user->password);
    }
}

用户销售员:

<?php

namespace main;

use main\AbstractUser;

class UserSalesman extends AbstractUser implements UserInterface {
    public $cpf;
    public $access;
    public $shopId;

    public function __construct($email, $name, $password, $cpf, $shopId) {
        parent::__construct($email, $name, $password);
        $this->shopId = $shopId;
        $this->cpf = $cpf;
        $this->access = UserFactory::SALESMAN;
    }
}

销售员记录:

<?php

namespace main;

use main\StaffRecord;
use main\UserInterface as User;

class SalesmanRecord extends StaffRecord {
    public function signUp(User $user) {
      $accountId = $this->accountController->addSeller($user->name, $user->password, $user->cpf);
      $this->emailController->add($user->email, $accountId);
      $this->staffController->add($accountId, $user->shopId, $user->access);
      $this->toolMailer->salesmanWelcome($user->name, $user->email, $user->password);
    }
}

用户客户:

<?php

namespace main;

use main\AbstractUser;

class UserCustomer extends AbstractUser implements UserInterface {
    public $cpf;
    public $phone;
    public $birthday;
    public $gender;

    public function __construct($email, $name, $password, $phone, $birthday, $gender) {
        parent::__construct($email, $name, $password);
        $this->phone = $phone;
        $this->birthday = $birthday;
        $this->gender = $gender;
        $this->access = UserFactory::CUSTOMER;
    }
}

客户记录:

<?php

namespace main;

use main\UserInterface;
use main\UserInterface as User;

class CustomerRecord extends UserFactory {
  protected $customerController;

  function __construct() {
    parent::__construct();
    $this->customerController = new \stdClass(); //customerController
  }

  public function signUp(User $user) {
    $accountId = $this->accountController->addCustomer($user->name, $user->password, $user->cpf, $user->phone, $user->birthday, $user->gender);
    $this->emailController->add($user->email, $accountId);
    $this->toolMailer->customerWelcome($user->name, $user->email, $user->password);
  }
}

我就是这样用的;

使用 loader.php:

<?php

function __autoload($class)
{
    $parts = explode('\\', $class);
    require end($parts) . '.php';
}

php main.php

<?php
namespace main;

include_once "loader.php";

use main\AdminRecord;
use main\UserAdmin;
use main\UserFactory;
use main\ManagerRecord;
use main\UserSalesman;
use main\CustomerRecord;


$userAdmin = new UserAdmin('francis@email.com', 'francis', 'test', 1);
$adminRecord = new AdminRecord($userAdmin);

$userManager = new UserManager('francis@email.com', 'francis', 'test', 1);
$managerRecord = new ManagerRecord($userManager);

$salesMan = new UserSalesman('francis@email.com', 'francis',  'test', 2, 1);
$salesmanRecord = new SalesmanRecord($salesMan);

//$email, $name, $password, $phone, $birthday, $gender
$customer = new UserCustomer('francis@email.com', 'francis', 'test', '0988-2293', '01-01-1984', 'Male');
$customerRecord = new CustomerRecord($customer);

print_r($adminRecord);
print_r($userManager);
print_r($salesMan);
print_r($salesmanRecord);
print_r($customer);
print_r($customerRecord);

下载文件:https://www.dropbox.com/sh/ggnplthw9tk1ms6/AACXa6-HyNXfJ_fw2vsLKhkIa?dl=0

我创建的解决方案并不完美,仍然需要重构和改进。

我希望这能解决你的问题。

谢谢。

【讨论】:

    【解决方案2】:

    我认为你错过了工厂的重点。您从工厂生成的类不会扩展工厂,工厂会大量生产并将对象实例化为适当的类或子类。您可以扩展工厂以创建更具体的工厂,但这是另一个主题。

    AdminRecordManagerRecord 等应该扩展公共基础抽象类 UserRecord,而不是 UserRecordFactory。工厂只是建立适当的用户记录。

    绕过不同签名的最简单方法是传入包含所需属性的选项对象或数组。下面我展示了数组,但您可能需要一个 UserSignupConfig 类,该类可以针对特定用途进行扩展,例如 AdminSignupConfig 并传入而不是通用数组。

    abstract class UserRecord {
    public static function manufacture($type) {
        return new $type;
    }
    
    protected $accountController;
    protected $emailController;
    protected $toolMailer;
    
    function __construct() {
        $this->accountController = new AccountController();
        $this->emailController = new EmailController();
        $this->toolMailer = new ToolMailer();
    }
    
    abstract public function signUp(array $config = array());
    
     //or via optional object of type UserSignupConfig
    // "abstract public function signUp(UserSignupConfig $config = null);"
    

    }

    在最基本的例子中,UserRecordFactory 可以有一个构建器方法来构造UserRecords(或任何子类)。

     //Create the factory
     $userRecordFactory = new UserRecordFactory();
    
     //Grab me something that extends UserRecord
     $adminRecord = $userRecordFactory->churnOutUserRecord("AdminRecord");
    

    churnOutUserRecord 方法可以是一个简单的开关:

    public function churnOutUserRecord($type){
          $record = null;
          switch($type){
             case "AdminRecord": $record = new AdminRecord(); break;
             case "ManagerRecord": $record = new ManagerRecord(); break;
              ///...
          }
          return $record;
    
          // ...Or just "return new $type;" 
          // if you are 100% sure all $types are valid classes
    
        }  
    

    最后一点:抽象类的所有这些使用并不是我喜欢的次要代码重用方式。相反,我建议尽可能使用接口,但这是一个更深层次的话题。

    【讨论】:

    • 我理解我在扩展工厂而不是通用 UserRecord 类方面的缺陷,但您的回答并没有解决我的主要问题:SignUp 方法不能有不同的签名。
    • 是的。使用 php 中的接口和抽象类,您必须匹配签名。您必须要有创意,请参阅我对答案的更改,以便轻松解决。
    • @MarcoAurélioDeleu 您是否尝试在我的答案中进行更新,将一组设置或配置对象传递给您的signup() 方法而不是显式参数?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多