简体   繁体   English

为其他类提供工厂方法

[英]providing factory methods to other classes

I want to create a GenericCollection class which is instantiated using another class as a template ("objects") and then performs task on it, such as loading objects from a database.我想创建一个GenericCollection类,该类使用另一个类作为模板(“对象”)进行实例化,然后对其执行任务,例如从数据库加载对象。 My Collection basically looks like this:我的收藏基本上是这样的:

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);

My issue is with actually providing the common functionality to my object classes.我的问题是实际为我的对象类提供通用功能。 What I want to achieve is我想要实现的是

  1. Minimal code duplication, so generic code like the fromArray function should be somewhere once and be reusable in all the various object classes.最少的代码重复,所以像fromArray函数这样的通用代码应该在某处一次并且可以在所有不同的对象类中重用。
  2. Proper type hinting to support my IDE and enforce compatibility at runtime.正确的类型提示以支持我的 IDE 并在运行时强制执行兼容性。
  3. Minimal misuse potential, so incomplete code pieces should not be instantiable.最小的误用可能性,因此不完整的代码片段不应该是可实例化的。

I've tried the following designs (all of which uphold goal 1):我尝试了以下设计(所有这些都支持目标 1):

1. Interface + Trait 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()
}

This doesn't work because the type hints are incompatible.这不起作用,因为类型提示不兼容。 There's actually no way to make them fit that I'm aware of.实际上没有办法让它们适合我所知道的。 Using object seems to be the only way to make the interface and the trait compatible ( self fails because the trait doesn't implement the interface, same issue if I explicitly type hint the interface), but then I can't actually type hint the interface in the Collection's constructor (same issue if I use the trait as the type hint, assuming that would even run).使用object似乎是使接口和 trait 兼容的唯一方法( self失败,因为 trait 没有实现接口,如果我显式地输入提示接口,同样的问题),但是我实际上无法输入提示集合的构造函数中的接口(如果我使用 trait 作为类型提示,同样的问题,假设它甚至会运行)。

The only way to make this approach work that I came up with is removing pretty much all the type hints, violating my second goal.我想出的使这种方法起作用的唯一方法是删除几乎所有类型提示,这违反了我的第二个目标。

2. Abstract class 2.抽象类

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()
}

and of course change the type hint in the Collection constructor to CollectableObject .当然,将 Collection 构造函数中的类型提示更改为CollectableObject

This doesn't work because I can't instantiate the abstract class in the common methods ( new self ).这不起作用,因为我无法在常用方法( new self )中实例化抽象类。

3. non-abstract Class to extend 3.非抽象类扩展

This is essentially the same as the previous one, but the class isn't made abstract so the common methods can create an instance.这与前一个基本相同,但该类没有被抽象化,因此公共方法可以创建一个实例。 This violates goal 3 as this class isn't supposed to be instantiable, but will be by calling CollectableObject::fromArray() even if I make the constructor private to stop new CollectableObject .这违反了目标 3,因为该类不应该是可实例化的,但即使我将构造函数设为私有以停止new CollectableObject ,也会通过调用CollectableObject::fromArray()来实现。


Is there any way to achieve all of my goals in this setup?在此设置中,有什么方法可以实现我的所有目标吗? Is my entire premise/overall structure flawed?我的整个前提/整体结构是否有缺陷? Is there a different approach or some way to modify one of my approached so it works?是否有不同的方法或某种方法来修改我的方法之一使其有效?

With either a trait or an abstract class, you could instantiate static instead of self :使用 trait 或抽象类,您可以实例化static而不是self

Trait特征

trait builder {
    public static function buildToo() {
        return new static();
    }
}

class Baz {
    use builder;
}

$b = Baz::buildToo();
var_dump($b);

/* output:
object(Baz)#2 (0) {}
*/

Abstract Class抽象类

abstract class Foo {
    
    public static function build() {
        return new static();
    }
}

class Bar extends Foo {}

$a = Bar::build();

/* output:
object(Bar)#1 (0) {}
*/

These are forms of late static binding .这些是后期静态绑定的形式。

Starting with PHP 8, to be released later this month (Nov 2020), you will be even be able to declare static as a return type hint :从本月晚些时候(2020 年 11 月)发布的 PHP 8 开始,您甚至可以static声明为返回类型提示

abstract class Foo {
    public static function build(): static
    {
        return new static();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM