向其他 类 提供工厂方法

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 提供通用功能。我想要实现的是

  1. 代码重复最少,因此像 fromArray 函数这样的通用代码应该在某个地方出现一次,并且可以在所有各种 object class 中重复使用。
  2. 正确的类型提示以支持我的 IDE 并在 运行 时强制兼容性。
  3. 误用可能性最小,因此不完整的代码片段不应被实例化。

我尝试了以下设计(所有这些都支持目标 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();
    }
}