@RMate

Как правильно реализовать создание дочерних экземпляров класса?

Всем привет!
Имеется что-то вроде автоответчика, в скрипт могут поступать вопросы, допустим, с почты, по смс, из соцсети.
Задача алгоритма подобрать ответ, и ответить туда, откуда пришёл запрос.
Для чего был сделан интерфейс
IAnswerProvider, который требует всех реализующих его потомков реализовать метод sendAnswer()
Имеются наследующие его классы
SmsAnswerProvider
EmailAnswerProvider
PhoneAnswerProvider

И всё работает хорошо, пока не возникает самый интересный вопрос.
Я хочу отправить уведомление всем, кто когда-либо взаимодействовал со мной... для чего я выбираю из базы всех пользователей (в базе содержится информация о источнике) и начинаю рассылку.
в реализации я вынужден делать что-то вроде
switch(источник)
    case "sms": 
        answerProvider = new SmsAnswerProvider();
        answerProvider.sendAnswer("Hello world")!
        break;
    case "email": 
        answerProvider = new EmailAnswerProvider();
        answerProvider.sendAnswer("Hello world")!
        break;


А если источников будет 30? Я делаю что-то не так. Подскажите, пожалуйста, как правильно реализовать в данном случае мою идею. Спасибо.

PS:
Думал, что мне идеально подойдет паттерн "фабрика", но чем дальше я про него читаю, тем меньше понимаю, нужен ли он мне.
  • Вопрос задан
  • 131 просмотр
Решения вопроса 2
mad_maximus
@mad_maximus
interface SenderInterface
{
     public function sendAnswer(string $message): void;

     public function supports(string $type): bool;
}

class EmailSender implements SenderInterface
{
    public function sendAnswer(string $message): void
    {
           // тут отправляете сообщение
    }
   
     public function supports(string $type): bool
     {
           return 'email' === $type;
     }
}

class SmsSender implements SenderInterface
{
     public function sendAnswer(string $message): void
    {
           // тут отправляете сообщение
    }

     public function supports(string $type): bool
     {
           return 'sms' === $type;
     }
}

class SenderAggregate
{
     private $providers;
     
     public function __construct(SenderInterface ...$providers)
     {
           $this->providers = $providers
     }

    public function send(string $type, string $message)
    {
           foreach ($this->providers as $provider) {
              if ($provider->supports($type) {
                  return $provider->sendAnswer($message);
              }
           }

            throw Exception;
    }
}
Ответ написан
@EvgeniiR
https://github.com/EvgeniiR
Как то так
class UserData {
    /**
     * @readonly
     * @var string
     */
    public $source;

    public function __construct(string $source)
    {
        $this->source = $source;
    }
}

class NoSupportingSenderFound extends \Exception {}
class AnswerTransportError extends \Exception {}

interface AnswerSender {
    public function supportSource(string $source): bool;

    /**
     * @throws AnswerTransportError
     */
    public function sendAnswer(string $answer): void;
}

class AnswerSenderFacade
{
    /**
     * @var AnswerSender[]
     */
    private $senders = [];

    public function __construct(AnswerSender ... $answerSenders)
    {
        $this->senders = $answerSenders;
    }

    /**
     * @throws AnswerTransportError
     * @throws NoSupportingSenderFound
     */
    public function sendAnswer(UserData $userData, string $answer)
    {
        foreach ($this->senders as $sender) {
            if($sender->supportSource($userData->source)) {
                $sender->sendAnswer($answer);
                return;
            }
        }
        throw new NoSupportingSenderFound("...");
    }
}


В конструктор класса AnswerSenderFacade нужно передать инстансы всех реализаций интерфейса AnswerSender. В Symfony это просто делается через tagged services (Навесить тег на _instanceof AnswerSender через конфиг и в конфиге же указать что все помеченные тегом классы нужно заинжектить)

Над названием конечно можно ещё подумать.

p.s. в случае Симфони, правда, в конструкторе вместо ...$providers придётся использовать iterable и дополнительно проверять что пришли инстансы SenderInterface через instanceof
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
Alex_Wells
@Alex_Wells
PHP/TS/Kotlin developer
Создаете Registry, где храните RegistryEntry. Для каждого типа провайдера по соответствующей реализации RegistryEntry.

pseudo:
class Registry<T extends RegistryEntry> {
    private Map<string, T> registries = new HashMap();

    public void register(T entry) {}
    public T find(string registryName) {}
}

class RegistryEntry {
    public string registryName;
}

class AnswerProviderRegistry<AnswerProviderRegistryEntry> extends Registry {}
class AnswerProviderRegistryEntry<T extends IAnswerProvider> extends RegistryEntry {
    abstract public T create();
}

class SmsAnswerProviderRegistryEntry<SmsAnswerProvider> extends AnswerProviderRegistryEntry {
    constructor() {
        this.registryName = "sms";
    }

    public SmsAnswerProvider create() {
        return new SmsAnswerProvider();
    }
}
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы