Когда вы станете более серьезно относиться к программированию, вы неизбежно столкнетесь с фразой «Код для интерфейса» в видеороликах, книгах или статьях. И это никогда не имело для меня смысла. Я усомнился в необходимости создания интерфейса и последующей его реализации. Как определить, когда и где использовать эти интерфейсы?
Всякий раз, когда я смотрел учебник или читал статью, они объясняли, что такое интерфейс: «Это класс без реализации», а я такой: «Эмм, спасибо 😏». Я имею в виду, я уже знал это; что мне действительно хотелось знать, так это почему и когда его использовать.
Я помню, как однажды спросил в сообществе Discord, и один из старших просто сказал: «Не волнуйтесь, в конечном итоге он вам подойдет», и так и произошло, это заняло некоторое время, но это произошло. Если вы испытываете это, знайте, что мы все через это прошли; давайте поможем вам понять, почему вам нужно кодировать интерфейс .
Поскольку ИИ берет верх, и все теряют из-за этого дерьмо, мы не хотим опаздывать на вечеринку. Мы хотим добавить его на наш сайт, это сделает небольшой чат-бот, который отвечает на вопросы о нашем продукте.
В своем примере я буду использовать PHP; не стесняйтесь использовать любой язык, который вам удобен. Главное — это концепция.
Наш чат-бот может быть таким простым:
<?php class ChatBot { public function ask(string $question): string { $client = new OpenAi(); $response = $client->ask($question); return $response; } }
Существует единственный метод ask()
, который использует OpenAI
SDK для подключения к их API, постановки вопроса и простого возврата ответа.
Теперь мы можем начать использовать нашего чат-бота.
$bot = new ChatBot(); $response = $bot->ask('How much is product X'); // The product costs $200.
На данный момент реализация выглядит хорошо, работает как положено, проект развернут и используется. Но мы не можем отрицать, что наш чат-бот сильно зависит от Open AI API; Я уверен, что вы согласны.
Теперь давайте рассмотрим сценарий, в котором цены на Open AI удвоятся и продолжат расти. Каковы наши варианты? Мы либо просто смиряемся со своей судьбой, либо ищем другой API. Первый вариант прост, мы просто продолжаем им платить, а второй не так прост, как кажется. У нового провайдера, скорее всего, будет собственный API и SDK; нам придется обновить все классы, тесты и сопутствующие компоненты, изначально разработанные для Open AI, это большая работа.
Это также вызывает обеспокоенность; что, если новый API не оправдает наших ожиданий с точки зрения точности или увеличит время простоя? Что, если мы хотим просто поэкспериментировать с разными провайдерами одновременно? Например, предоставить нашим подписанным клиентам клиент OpenAI, используя при этом более простой API для гостей? Вы можете видеть, насколько это может быть сложно, и знаете почему? Потому что наш код был плохо спроектирован.
У нас не было видения; мы просто выбрали API и полностью зависели от него и его реализации. Вот от всего этого нас бы спас принцип «Код к интерфейсу». Как? Давайте посмотрим.
Начнем с создания интерфейса:
<?php interface AIProvider { public function ask(string $question): string; }
У нас есть свой интерфейс или, как я его называю, контракт. Давайте реализуем это или напишем для него код.
<?php class OpenAi implements AIProvider { public function ask(string $question): string { $openAiSdk = new OpenAiSDK(); $response = $openAiSdk->ask($question); return "Open AI says: " . $response; } } class RandomAi implements AIProvider { public function ask(string $question): string { $randomAiSdk = new RandomAiSDK(); $response = $randomAiSdk->send($question); return "Random AI replies: " . $response->getResponse(); } }
На самом деле и
OpenAiSDK
, иRandomAiSDK
будут внедрены через конструктор. Таким образом, мы делегируем сложную логику создания экземпляров DI-контейнеру . Эта концепция известна как инверсия управления . Это связано с тем, что каждому поставщику обычно требуются определенные конфигурации.
Теперь у нас есть два провайдера, которых мы можем использовать для ответа на вопросы. Независимо от их реализации, мы уверены, что при задании вопроса они подключатся к своему API и ответят на него. Они должны соблюдать договор AIProvider
.
Теперь в нашем ChatBot
мы можем сделать следующее
class ChatBot { private AIProvider $client; // A dependency can be injected via the constructor public function __construct(AIProvider $client) { $this->client = $client; } // It can also be set via a setter method public function setClient(AIProvider $client): void { $this->client = $client; } public function ask(string $question): string { return $this->client->ask($question); } }
Обратите внимание, что пример призван продемонстрировать несколько способов внедрения зависимости, в данном случае
AIProvider
. Вам не обязательно использовать как конструкторы, так и сеттеры.
Как видите, мы внесли некоторые изменения; мы больше не зависим от OpenAI, и вы не найдете никаких упоминаний о нем. Вместо этого мы зависим от контракта/интерфейса. И каким-то образом мы можем применить этот пример в реальной жизни; мы все хотя бы раз были ChatBot
.
Представьте себе покупку системы солнечных батарей. Компания обещает прислать технических специалистов для ее установки, уверяя вас, что независимо от того, какого сотрудника они пришлют, работа будет выполнена, и в конце концов вам установят панели. Так что вам все равно, отправят ли они Джоша или Джорджа. Они могут быть разными, один лучше другого, но с обоими заключен контракт на установку панелей.
Они не будут типа: «Вы знаете, я вместо этого ремонтирую ваш телевизор, компания их обязывает выполнить указанную работу». И RandomAi
, и OpenAi
выступают сотрудниками AIProvider
; вы задаете вопрос, и вам дадут ответ. Точно так же, как вас не заботило, кто устанавливает панели, ChatBot
вообще не должен заботиться о том, кто выполняет эту работу. Ему просто нужно знать, что любая предоставленная реализация сделает это.
Теперь вы можете свободно использовать то или иное.
$bot = new ChatBot(); // For subscribed users $bot = new ChatBot(new OpenAi()); $response = $bot->ask('How much is Product X'); // Open AI says: 200$ // For guests $bot->setClient(new RandomAi()); $response = $bot->ask('How much is Product X'); // Random AI replies: 200$
Теперь у вас есть возможность изменить всего поставщика API, и ваш код всегда будет вести себя одинаково. Вам не нужно ничего в нем менять, поскольку вы написали код для интерфейса , поэтому ни одна из проблем, о которых мы говорили ранее, не будет проблемой.
В нашем примере, кодируя интерфейс, мы также учли три принципа SOLID , даже не подозревая об этом, позвольте мне уточнить.
Я не буду вдаваться в подробности; о каждом из принципов можно написать длинную статью. Это всего лишь краткое объяснение, показывающее, чего мы достигли, написав код для интерфейса.
Первый принцип, который мы соблюдали, — это принцип открытости-закрытости, который гласит, что код должен быть открыт для расширения и закрыт для модификации. Как бы сложно это ни звучало, вы этого достигли. Подумайте об этом, ChatBot
сейчас закрыт на модификацию; мы больше не будем трогать код. Это была наша цель с самого начала.
Но он открыт для расширения; если бы мы добавили третьего, четвертого или даже пятого провайдера, нас ничто не остановило бы. Мы можем реализовать интерфейс, и наш класс может использовать его «из коробки», никаких изменений не требуется.
Не буду утомлять вас его определением, но, по сути, оно гласит, что вы можете заменять классы ВСЕМИ их подклассами и наоборот. Технически все наши провайдеры AI are-a
AIProvider
, и их реализации можно менять местами друг на друга, не влияя на корректность ChatBot
, последний даже не знает, какого провайдера он использует 😂, так что да, мы уважаем г-жу Лискову .
Должен признать, об этом могла бы быть отдельная статья. Но проще говоря, принцип гласит, что вам следует полагаться на абстракции, а не на конкретику, и именно это мы и делаем. Мы зависим от провайдера, а не от конкретного поставщика, такого как Open AI.
Помните: все это потому, что мы написали код для интерфейса.
Всякий раз, когда вы обновляете класс, который, как вы знаете, не следует обновлять, и ваш код становится хакерским из-за операторов if, вам нужен интерфейс. Всегда спрашивайте себя, действительно ли этому классу нужно знать, как? Буду ли я всегда пользоваться услугами этого поставщика услуг? Или драйвер базы данных? Если нет, вы знаете, что делать.
С учетом вышесказанного, просто дайте ему немного времени, и в конечном итоге он вам подойдет .