У меня есть набор предметов (MainObject
) которые однозначно определяются двумя объектами (SubObject1
, SubObject2
) и строка (theString
). Я с того чтобы достать MainObject
из набора, возвращая существующий объект на основе двух подобъектов и строки, если он существует, в противном случае создайте новый, добавьте его в набор и верните этот объект.
Следующий псевдокод демонстрирует это в вымышленном мире, где стандартный массив может использовать объекты в качестве ключей.
class SubObject1{}
class SubObject2{}
class MainObject{
private $subObject1, $subObject2, $theString;
public function __construct(SubObject1 $subObject1, SubObject2 $subObject2, string $theString):MainObject {
$this->subObject1=$subObject1;
$this->subObject2=$subObject2;
$this->theString=$theString;
}
}
class ObjectCollection
{
private $map=[];
public function getObject(SubObject1 $subObject1, SubObject2 $subObject2, string $theString):MainObject {
if(isset($this->map[$subObject1][$subObject2][$theString])) {
$mainObject=$this->map[$subObject1][$subObject2][$theString];
}
else {
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$this->map[$subObject1][$subObject2][$theString]=$mainObject;
}
return $mainObject;
}
}
$objectCollection=new ObjectCollection();
$subObject1_1=new SubObject1();
$subObject1_2=new SubObject1();
$subObject2_1=new SubObject2();
$subObject2_1=new SubObject2();
$o=$objectCollection->getObject($subObject1_1, $subObject2_1, 'hello'); //returns a new object
$o=$objectCollection->getObject($subObject1_2, $subObject2_1, 'hello'); //returns a new object
$o=$objectCollection->getObject($subObject1_1, $subObject2_1, 'goodby'); //returns a new object
$o=$objectCollection->getObject($subObject1_1, $subObject2_1, 'hello'); //returns existing object
Как это лучше всего реализовать?
Одной из возможностей является что-то вроде следующего непроверенного кода, однако он немного многословен, и мне интересно, есть ли более чистое решение.
public function getObject(SubObject1 $subObject1, SubObject2 $subObject2, string $theString):MainObject {
if(isset($this->map[$theString])) {
if($this->map[$theString]->contains($subObject1)) {
$subObject1Storage=$this->map[$theString][$subObject1];
if($subObject1Storage->contains($subObject2)) {
$mainObject=$subObject1Storage[$subObject2];
}
else {
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$subObject1Storage[$subObject2]=$mainObject;
}
}
else {
$subObject1Storage = new \SplObjectStorage();
$this->map[$theString][$subObject1]=$subObject1Storage;
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$subObject1Storage[$subObject2]=$mainObject;
}
}
else {
$this->map[$theString] = new \SplObjectStorage();
$subObject1Storage = new \SplObjectStorage();
$this->map[$theString][$subObject1]=$subObject1Storage;
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$subObject1Storage[$subObject2]=$mainObject;
}
return $mainObject;
}
Логика, которую я имел в виду, была следующей:
Фабрика (или абстрактная фабрика в случае слишком большого количества объектов) позаботится о создании самого объекта.
Контейнер сопоставит уникальные идентификаторы с объектами, созданными фабрикой.
И может извлекать объекты на основе этих идентификаторов.
Это простая часть, пользовательская часть должна быть еще проще, вы можете добавить свои собственные методы, чтобы делать любую магию с псевдонимами и так далее.
namespace Example;
/**
* Class ObjectFactory
*
* @package Example
*/
class ObjectFactory {
/**
* This is obviosuly not ideal but it can work
* with a limited amount of objects. Otherwise use an
* abstract factory and let each instance take care of a few
* related objects
*
* @param string $objectAlias
*
* @throws \Exception
*/
public function make(string $objectAlias) {
switch($objectAlias) {
case 'object_unique_id_1':
try{
$instance = new $objectAlias;
}catch (\Exception $exception) {
// log or whatever and rethrow
throw new \Exception("Invalid class? maybe, I dunno");
}
// return $instance
// etc
}
}
}
Вы также можете использовать отражение здесь, чтобы рекурсивно получить аргументы для объекта и вывести новые экземпляры объекта в текущем объекте на основе аргументов в конструкции, по сути, создать свой собственный маленький контейнер DI.
Но если вы хотите сохранить здравомыслие, используйте что-то вроде Прыщ.
Контейнер:
<?php
namespace Example;
/**
* Class Container
*
* @package Example
*/
class Container {
/**
* @var array
*/
private $map = [];
/**
* @param $objectAlias
* @param $objectInstance
*
* @throws \Exception
*/
public function set($objectAlias, $objectInstance) {
// You can use a try catch here, I chose not to
if(isset($this->map[$objectAlias])) {
throw new \Exception("Already exists");
}
$this->map[$objectAlias] = $objectInstance;
}
/**
* @param $objectAlias
*
* @return bool|mixed
*/
public function get($objectAlias) {
if(isset($this->map[$objectAlias])) {
return $this->map[$objectAlias];
}
return false;
}
}
Специальный контейнер, который будет содержать ваши собственные методы
<?php
namespace Example;
/**
* Class ContainerHashMapThingy
*
* @package Example
*/
class ContainerHashMapThingy extends Container {
// Your methods go here
}
И пример объекта:
<?php
namespace Example;
/**
* Class ExampleObject1
*
* @package Example
*/
class ExampleObject1 {
/**
* @return string
*/
public function alias() {
// This is just for example sake
// You can just as well have a config, another class to map them or not map them at all
return 'example_object_1';
}
}
И реальный пример
<?php
$factory = new \Example\ObjectFactory();
$container = new \Example\Container();
$objectOne = $factory->make('example_object_1');
$container->set('first_object', $objectOne);
Идея в том, чтобы дать вам чистый лист для контейнера + фабрики.
Если вы расширили контейнер, вы можете реализовать свои собственные методы, удалить материал из map
массив, даже переписать set
метод для удовлетворения ваших собственных потребностей.
Хотя это не полный ответ, его очень сложно дать, поскольку, как я уже сказал, ваши потребности могут отличаться.
Я надеюсь, что это поможет вам выбрать правильный путь.
Других решений пока нет …