用于模拟多层服务层的装饰器模式

时间:2015-10-26 16:27:32

标签: java php oop model-view-controller service-layer

代码示例在PHP中,但问题与语言无关。

情况

我试图找出将服务层分成多个明确定义的图层的最佳方法。

在下面的示例中,我上传了base64编码的用户头像,并展示了它将如何通过图层。我使用装饰器模式来模拟图层。

重要: 传递到每一层的数据通常会在传递到下一层之前以某种方式更改,这正是我正在寻找的。我不喜欢的一件事是,为了更新头像,您必须先与ValidatedProfile对象交谈,而不是说Profile个对象。关于它的一些东西看起来很奇怪,但我总是有一个Profile对象可以将调用委托给ValidatedProfile

图层

  1. 验证 这是验证数据的位置。如下例所示,您可以检查$ avatar字符串的格式并确保它是有效的图像资源。在验证过程中,通常会创建实体对象和资源,然后将其传递到下一层。
  2. 验证 执行检查,例如验证提供的ID是否真实。如下例所示,我检查提供的用户ID是否实际上是用户的真实ID。
  3. 指挥官: 要执行的操作发生的位置。到达该层时,数据被认为是完全验证和验证的,不需要对其进行进一步的检查。指挥官将行动委托给其他服务(通常是实体服务),也可以让其他服务部门采取更多行动。
  4. 实体: 该层适用于要对实体和/或其关系执行的操作。
  5. ValidatedProfile

    class ValidatedProfile 
    {
        private $verifiedProfile;
    
        /**
         * @param string $avatar Example: data:image/png;base64,AAAFBfj42Pj4
         */
        public function updateAvatar($userId, $avatar)
        {
            $pattern = '/^data:image\/(png|jpeg|gif);base64,([a-zA-Z0-9=\+\/]+)$/';
            if (!preg_match($pattern, $avatar, $matches)) {
                // error
            }
    
            $type = $matches[1]; // Type of image
            $data = $matches[2]; // Base64 encoded image data
    
            $image = imagecreatefromstring(base64_decode($data));
            // Check if the image is valid etc...
    
            // Everything went okay
            $this->verifiedProfile->updateAvatar($userId, $image);
        }
    }
    

    VerifiedProfile

    class VerifiedProfile
    {
        private $profileCommander;
    
        public function updateAvatar($userId, $image)
        {
            $user = // get user from persistence
            if ($user === null) {
                // error
            }
    
            // User does exist 
            $this->profileCommander->updateAvatar($user, $image);
        }
    }
    

    ProfileCommander

    class ProfileCommander
    {
        private $userService;
    
        public function updateAvatar($user, $image)
        {
            $this->userService->updateAvatar($user, $image);
    
            // If any processes need to be run after an avatar is updated
            // you can invoke them here.
        }
    

    UserService

    class UserService
    {
        private $persistence;
    
        public function updateAvatar($user, $image)
        {
            $fileName = // generate file name
    
            // Save the image to disk.
    
            $user->setAvatar($fileName);
    
            $this->persistence->persist($user);
            $this->persistence->flush($user);
        }
    }
    

    然后您可以拥有如下所示的Profile类:

    class Profile
    {
        private $validatedProfile;
    
        public function updateAvatar($userId, $avatar)
        {
            return $this->validatedProfile->updateAvatar($userId, $avatar);
        }
    }
    

    通过这种方式,您只需与Profile而非ValidatedProfile的实例进行对话,我认为这更有意义。

    是否有更好,更广泛接受的方式来实现我在这里尝试做的事情?

1 个答案:

答案 0 :(得分:0)

我认为你的图层太多了。对于像这样的操作,两个主要对象应该足够了。您需要验证头像输入并以某种方式持久化。

由于您需要验证用户ID并且它以某种方式与持久性相关联,因此您可以将其委托给UserService对象。

interface UserService {

    /**
     * @param string $userId
     * @param resource $imageResource
     */
    public function updateAvatar($userId, $imageResource);

    /**
     * @param string $userId
     * @return bool
     */
    public function isValidId($userId);
}

检查有效用户ID应该是请求验证的一部分。我不会像验证一样单独执行。所以UserAvatarInput可以处理它(验证实现只是一个例子),还有一个小的包装器方法来保存它。

class UserAvatarInput {

    /**
     * @var UserService
     */
    private $userService;

    /**
     * @var string 
     */
    private $userId;

    /**
     * @var resource
     */
    private $imageResource;

    public function __construct(array $data, UserService $service) {
        $this->userService = $service; //we need it for save method
        $errorMessages = [];

        if (!array_key_exists('image', $data)) {
            $errorMessages['image'] = 'Mandatory field.';
        } else {
            //validate and create image and set error if not good
            $this->imageResource = imagecreatefromstring($base64);
        }

        if (!array_key_exists('userId', $data)) {
            $errorMessages['userId'] = 'Mandatory field.';
        } else {
            if ($this->userService->isValidId($data['userId'])) {
                $this->userId = $data['userId'];
            } else {
                $errorMessages['userId'] = 'Invalid user id.';
            }
        }

        if (!empty($errorMessages)) {
            throw new InputException('Input Error', 0, null, $errorMessages);
        }
    }

    public function save() {
        $this->userService->updateAvatar($this->userId, $this->imageResource);
    }

}

我使用了一个异常对象来传递验证消息。

class InputException extends Exception {

    private $inputErrors;

    public function __construct($message, $code, $previous, $inputErrors) {
        parent::__construct($message, $code, $previous);
        $this->inputErrors = $inputErrors;
    }

    public function getErrors() {
        return $this->inputErrors;
    }

}

这是客户端使用它的方式,例如:

class UserCtrl {

    public function postAvatar() {
        try {
            $input = new AvatarInput($this->requestData(), new DbUserService());
            $input->save();
        } catch (InputException $exc) {
            return new JsonResponse($exc->getErrors(), 403);
        }
    }

}