Недавно я разработал новый метод (для меня) создания экземпляров объектов, который, как я думаю, я буду часто использовать в своих будущих разработках. Я сильно подозреваю, что это устоявшийся шаблон дизайна, у которого, вероятно, есть название, но я не получил квалификацию CS и придумал его сам.
Я говорю, что придумал это, но математики любят заявлять, как они открывают новую математику, что она уже существует в природе и структуре Вселенной, и аналогичным образом я подозреваю, что эта закономерность является естественной эволюцией, которая происходила раньше, несколько раз.
Частный конструктор с необязательным возвращаемым объектом
Большинство базовых классов объектов, которые я создаю, имеют частные конструкторы и предлагают один или несколько общедоступных методов создания экземпляров:
class token {
public static function create(string $prop) : ?token {
$token = null;
if(is_valid($prop)){
/* I use static routinely in case I want to extend
the class later */
$token = new static();
$token->prop = $prop;
}
return $token;
}
protected function __construct(){
}
}
Круто, теперь, когда мы вызываем token::create('Foo') , мы можем получить объект токена или получить значение null. Это только начало.
Но что, если что-то пойдет не так? И мы хотим знать, что? НУЛЬ мало о чем нам говорит.
Кое-что, что я делал в прошлом, это передавал общий обработчик ошибок конструктору.
class token {
public static function create(string $prop, errorHandler $errorHandler) : ?token {
$token = null;
if(is_valid($prop)){
/* I use static routinely in case I want to extend the
class later */
$token = new static();
$token->prop = $prop;
} else {
$errorHandler->addError(errorHandler::INVALID_PROPERTY, $prop.' is not a valid string for some reason.');
}
return $token;
}
...
}
Это немного лучше, мы можем предоставить нашему классу ErrorHandler любое количество констант ошибок и скомпилировать массив сообщений об ошибках для отправки пользователю.
Проблема в этом двоякая. Если метод ::create имеет более нескольких параметров, разработчики должны передать значения по умолчанию для всех промежуточных свойств, прежде чем они смогут добавить объект обработчика ошибок, если только мы не разместим его заранее, я полагаю; и, во-вторых, мы должны создать объект обработчика ошибок до создания объекта, который мы действительно хотим, каждый раз. Это добавляет шаблонные накладные расходы к каждому созданию экземпляра объекта, которых я бы предпочел избежать.
Представление объекта результата запроса
Метод ::create подразумевает, что он вроде как будет работать…что он вернет объект правильного типа. Добавление ? на подсказке типа, потому что все может пойти наперекосяк, вот где возникают проблемы.
Что, если бы мы могли быть уверены, что вернемся к объекту определенного типа? Мы могли бы “запросить” объект и обработать возвращаемый результат.
class token {
public static function request(string $prop) : tokenRequestResult {
$result = new tokenRequestResult();
if(!is_valid($prop)){
$result->addError(tokenRequestResult::INVALID_PROPERTY_VALUE);
}
if(!$result->getErrorMask()){
$token = new static();
$token->prop = $prop;
$result->setObject($token);
}
return $result;
}
....
}
Я определил интерфейс и абстрактный класс для обработки большей части функциональности объекта Результат запроса .
interface requestResult {
public function getErrorMask() : int;
public function getErrorMessages() : array;
// TODO: When PHP >= 7.4 add typehints as per commented lines below
public function getObject();
public function setObject($object);
// public function getObject() : Object;
// public function setObject(Object $object);
public function addError(int $maskBit, ?string $message);
}
abstract class baseRequestResult implements requestResult {
protected $errorMask = 0;
protected $errorMessages = [];
protected $object;
public function getErrorMask(): int {
return $this->errorMask;
}
public function getErrorMessages(): array {
return $this->errorMessages;
}
public function buildErrorMessageList(dataWriter $writer) : string {
$response = '';
if(count($this->errorMessages)){
$response .= $writer->beforeResults();
foreach($this->errorMessages as $errorMessage){
$response .= $writer->item((object)['message'=>$errorMessage]);
}
$response .= $writer->afterResults();
} else {
$response .= $writer->noResults();
}
return $response;
}
// TODO: When PHP >= 7.4 add typehints as per interface
abstract public function getObject();
abstract public function setObject($object);
public function addError(int $maskBit, ?string $message) {
$this->errorMask |= $maskBit;
if($message && !in_array($message, $this->errorMessages)){
$this->errorMessages[] = $message;
}
}
}
И класс запроса токена действительно очень мал и легко воспроизводим для разных типов объектов:
class tokenRequestResult extends baseRequestResult {
const INVALID_TYPE = 1<<0;
const MATCHING_TOKEN_IN_USE = 1<<1;
// TODO: When PHP >= 7.4 use typehint for \token here instead of instanceof condition
public function setObject($token) {
if($token instanceof \token){
$this->object = $token;
}
}
public function getObject() : \token {
return $this->object;
}
}
Внешне API затем используется следующим образом:
$tokenResult = token::request('Foo');
if($tokenResult->getErrorMask()){
/*
There are errors that need handling. We can flesh out the
tokenRequestResult object to handle the various error mask values
and/or error messages, pass in a writer class to output the
errors to screen, whatever.
*/
} else {
/*
But if reported errors are 0 we can be confident that `->getObject()`
will get us our native `token` object and act accordingly
*/
$token = $tokenResult->getObject();
$token->callPublicMethodOfTokenObject();
}
Я думаю, что эта техника окажется довольно ценной в моей работе. Как я уже сказал, это, вероятно, задокументированный шаблон, но, учитывая, что я не знаю, как он может называться, я не уверен, как начать его искать.
Если вы случайно знаете его название, я был бы благодарен, но если оно для вас новое Я надеюсь, что вы найдете это полезным.
Оригинал: “https://dev.to/thisleenoble/evolution-of-php-object-instantiation-with-detailed-error-diagnostics-1ddo”