工厂设计模式实现疑惑

Factory design pattern implementation doubts

我正在尝试为 Telegram Messenger 创建一个机器人,同时尝试学习 OOP。我真的不知道如何解决这个问题。我有一个 Message 实体,带有所有的 getter 和 setter,我认为这非常简单。我的问题是我想创建两种(或更多)类型的工厂

1) 一个 简单的消息 你只需向工厂提供你想要发送消息的 chat_id 并且文本,可能是这样的:

<?php

namespace Telegram\Domain\Factory;

use Telegram\Domain\Entity\Message;

class MessageRaw extends MessageAbstract {
    public function createMessage($chat_id, $text) {
        $message = new Message();
        $message->setChatId($chat_id);
        $message->setText($text);

        return $message;
    }
}

MessageAbstract 在哪里

<?php

namespace Telegram\Domain\Factory;

abstract class MessageAbstract {
    abstract public function createMessage($chat_id, $text);
}

2) 带有键盘的 消息 (Telegram 让您可以在发送消息时包含自定义键盘)。我这里有问题,键盘是作为数组给出的,所以这将是 createMessage 的另一个参数。

所以我的问题是,无论是简单消息还是带键盘的消息,我都应该始终提供 $keyboard 参数吗?或者这两种类型的消息是否足够不同,以至于它们应该从不同的 类 创建(我认为不是)?或者也许我不应该在工厂里做,而是用 setter 和 getter?

TLDR:如何以奇特的方式创建具有不同数量参数的对象,就像这样

$MessageRaw = new MessageRaw($chat_id, $text);
$MessageNumericKeyboard = new MessageNumericKeyboard($chat_id, $text); //numeric keyboard is standard so can be set in the createMessage Function
$MessageCustomKeyboard = new MessageCustomKeyboard($chat_id, $text, ['A', 'B']); //should it be done like this?

这看起来更适合 common 或 garden 子类型而不是使用工厂模式,例如:

class Message {

    // e.g.
    protected $chatId;
    protected $text;

    // constructor
    public function __construct($chatId, $text) {
        $this->setChatId($chatId);
        $this->setText($text);
    }

    // ... stuff
}

final class MessageCustomKeyboard extends Message {

    // e.g.
    private $_keyboard;

    // constructor overrides parent
    public function __construct($chatId, $text, array $keyboard) {
        parent::__construct($chatId, $text);
        $this->setKeyboard($keyboard);
    }

    // ... stuff
}

$message = new Message(1, "Hello World");
$messageCustomKeybaord = new MessageCustomKeyboard(2, "Hello again", array("a", "b"));

我同意@CD001 关于子类型化/扩展的观点,所以我不会重复他的回答,但您仍然可以通过使用依赖注入识别所需类型并返回适当的对象来使用工厂模式。

您可以为工厂方法的这种依赖项包含一个专用参数,或者您可以使用方法重载来注入它并只检查那些特定类型(如果有额外的 类 可能会返回在指定的两个之外)。

坚持使用工厂模式将真正帮助您在未来扩展它而无需太多额外的工作,现在偷工减料只会导致以后的痛苦。


编辑:

Injection (nb: 我已经包含了一个类型参数来通过字符串覆盖一种可能的扩展技术,但这可以很容易地成为一个注入类型对象也是,由你决定。还包括为消息构造函数同时注入消息属性的选项,因此你得到一个完全实例化的对象而不是一个空的 DTO)

class MessageFactory {
    public static function get($type,$attributes=NULL,$keyboard=NULL) {
        if ($keyboard && !($keyboard instanceof MessageKeyboardInterface)) {
            // ... trigger some exception here
        } elseif (!$keyboard) {
            $keyboard = new BasicKeyboard(); 
        }
        switch($type):
        case('standard'):
            return new StandardMessage($attributes,$keyboard);
            break;
        case('short'):
            return new ShortMessage($attributes,$keyboard);
            break;
        case('long'):
            return new LongMessage($attributes,$keyboard);
            break;
        endswitch;
    }   
}

这样,您的所有 Message 对象都可以使用相同的界面,可以使用自定义或基本键盘,并且可以轻松扩展。您当然可以扩展 $attributes 并在实例化后使用您的个人设置器,或者如果您有很多设置器,则在构造函数中循环使用它们。我个人不喜欢构造函数中的大量参数,而宁愿遍历可选参数数组。