向其他 类 提供工厂方法
providing factory methods to other classes
我想创建一个 GenericCollection
class,它使用另一个 class 作为模板(“objects”)实例化,然后在其上执行任务,例如从数据库加载 objects。我的 Collection 基本上是这样的:
final class GenericCollection {
private $template;
private $db;
public function __construct(CollectableInterface $template, DbInterface $db) {
$this->template = $template;
$this->db = $db;
}
public function load(array $filter): iterable {
$results = $this->db->read(['t' => $this->template::tableName()], $filter, $this->template::getFields());
foreach ($results as $row) {
yield $this->template::fromArray($row);
}
}
}
$collection = new GenericCollection(MyObject::template(), $db);
我的问题是实际为我的 object classes 提供通用功能。我想要实现的是
- 代码重复最少,因此像
fromArray
函数这样的通用代码应该在某个地方出现一次,并且可以在所有各种 object class 中重复使用。
- 正确的类型提示以支持我的 IDE 并在 运行 时强制兼容性。
- 误用可能性最小,因此不完整的代码片段不应被实例化。
我尝试了以下设计(所有这些都支持目标 1):
1。接口 + 特征
interface CollectableInterface {
public static function template(): object;
public static function getFields(): array;
public static function tableName(): string;
public static function fromArray(array $data): object;
}
trait CollectableTrait {
public static function fromArray(array $data = []): object {
foreach (get_object_vars($obj = new self) as $property => $default) {
$obj->$property = $data[$property] ?? $default;
}
return $obj;
}
public static function template(): object {
return new self;
}
}
final class MyObject implements CollectableInterface {
use CollectableTrait;
// properties, implementations of tableName() and getFields()
}
这不起作用,因为类型提示不兼容。据我所知,实际上没有办法让它们合身。使用 object
似乎是使接口和特征兼容的唯一方法(self
失败,因为特征没有实现接口,如果我显式键入提示接口,同样的问题),但是我实际上无法在 Collection 的构造函数中键入提示接口(如果我使用特征作为类型提示,同样的问题,假设甚至 运行)。
使我想出的这种方法起作用的唯一方法是删除几乎所有类型提示,这违反了我的第二个目标。
2。文摘class
abstract class CollectableObject {
public static function fromArray(array $data = []): self {
foreach (get_object_vars($obj = new self) as $property => $default) {
$obj->$property = $data[$property] ?? $default;
}
return $obj;
}
public static function template(): self {
return new self;
}
abstract public static function getFields(): array;
abstract public static function tableName(): string;
}
final class MyObject extends CollectableObject {
// properties, implementations of tableName() and getFields()
}
当然还要将 Collection 构造函数中的类型提示更改为 CollectableObject
.
这不起作用,因为我无法在通用方法 (new self
) 中实例化抽象 class。
3。 non-abstract Class 扩展
这与上一个基本相同,但是 class 没有抽象化,因此常用方法可以创建一个实例。这违反了目标 3,因为这个 class 不应该是可实例化的,但即使我将构造函数设为私有以停止 new CollectableObject
.
也会通过调用 CollectableObject::fromArray()
来实例化
有什么方法可以在此设置中实现我的所有目标吗?我的整个 premise/overall 结构有缺陷吗?是否有不同的方法或某种方式来修改我的方法之一以使其有效?
使用特征或抽象 class,您可以实例化 static
而不是 self
:
特质
trait builder {
public static function buildToo() {
return new static();
}
}
class Baz {
use builder;
}
$b = Baz::buildToo();
var_dump($b);
/* output:
object(Baz)#2 (0) {}
*/
摘要Class
abstract class Foo {
public static function build() {
return new static();
}
}
class Bar extends Foo {}
$a = Bar::build();
/* output:
object(Bar)#1 (0) {}
*/
这些是 late static binding.
的形式
从将于本月晚些时候(2020 年 11 月)发布的 PHP8 开始,您甚至可以 to declare static
as a return type hint:
abstract class Foo {
public static function build(): static
{
return new static();
}
}
我想创建一个 GenericCollection
class,它使用另一个 class 作为模板(“objects”)实例化,然后在其上执行任务,例如从数据库加载 objects。我的 Collection 基本上是这样的:
final class GenericCollection {
private $template;
private $db;
public function __construct(CollectableInterface $template, DbInterface $db) {
$this->template = $template;
$this->db = $db;
}
public function load(array $filter): iterable {
$results = $this->db->read(['t' => $this->template::tableName()], $filter, $this->template::getFields());
foreach ($results as $row) {
yield $this->template::fromArray($row);
}
}
}
$collection = new GenericCollection(MyObject::template(), $db);
我的问题是实际为我的 object classes 提供通用功能。我想要实现的是
- 代码重复最少,因此像
fromArray
函数这样的通用代码应该在某个地方出现一次,并且可以在所有各种 object class 中重复使用。 - 正确的类型提示以支持我的 IDE 并在 运行 时强制兼容性。
- 误用可能性最小,因此不完整的代码片段不应被实例化。
我尝试了以下设计(所有这些都支持目标 1):
1。接口 + 特征
interface CollectableInterface {
public static function template(): object;
public static function getFields(): array;
public static function tableName(): string;
public static function fromArray(array $data): object;
}
trait CollectableTrait {
public static function fromArray(array $data = []): object {
foreach (get_object_vars($obj = new self) as $property => $default) {
$obj->$property = $data[$property] ?? $default;
}
return $obj;
}
public static function template(): object {
return new self;
}
}
final class MyObject implements CollectableInterface {
use CollectableTrait;
// properties, implementations of tableName() and getFields()
}
这不起作用,因为类型提示不兼容。据我所知,实际上没有办法让它们合身。使用 object
似乎是使接口和特征兼容的唯一方法(self
失败,因为特征没有实现接口,如果我显式键入提示接口,同样的问题),但是我实际上无法在 Collection 的构造函数中键入提示接口(如果我使用特征作为类型提示,同样的问题,假设甚至 运行)。
使我想出的这种方法起作用的唯一方法是删除几乎所有类型提示,这违反了我的第二个目标。
2。文摘class
abstract class CollectableObject {
public static function fromArray(array $data = []): self {
foreach (get_object_vars($obj = new self) as $property => $default) {
$obj->$property = $data[$property] ?? $default;
}
return $obj;
}
public static function template(): self {
return new self;
}
abstract public static function getFields(): array;
abstract public static function tableName(): string;
}
final class MyObject extends CollectableObject {
// properties, implementations of tableName() and getFields()
}
当然还要将 Collection 构造函数中的类型提示更改为 CollectableObject
.
这不起作用,因为我无法在通用方法 (new self
) 中实例化抽象 class。
3。 non-abstract Class 扩展
这与上一个基本相同,但是 class 没有抽象化,因此常用方法可以创建一个实例。这违反了目标 3,因为这个 class 不应该是可实例化的,但即使我将构造函数设为私有以停止 new CollectableObject
.
CollectableObject::fromArray()
来实例化
有什么方法可以在此设置中实现我的所有目标吗?我的整个 premise/overall 结构有缺陷吗?是否有不同的方法或某种方式来修改我的方法之一以使其有效?
使用特征或抽象 class,您可以实例化 static
而不是 self
:
特质
trait builder {
public static function buildToo() {
return new static();
}
}
class Baz {
use builder;
}
$b = Baz::buildToo();
var_dump($b);
/* output:
object(Baz)#2 (0) {}
*/
摘要Class
abstract class Foo {
public static function build() {
return new static();
}
}
class Bar extends Foo {}
$a = Bar::build();
/* output:
object(Bar)#1 (0) {}
*/
这些是 late static binding.
的形式从将于本月晚些时候(2020 年 11 月)发布的 PHP8 开始,您甚至可以 to declare static
as a return type hint:
abstract class Foo {
public static function build(): static
{
return new static();
}
}