有没有一种方法可以防止一个类被实例化,除非是在另一个特定类的实例中进行实例化?

3
我有两个类A和B,它们都继承自同一个父类。在PHP中,有没有一种方法可以确保只能在类A中创建类B的实例,而在其他地方创建类B的实例将导致错误?(注意:类B不是类A的子类。)

你想让B只在类A内部实例化吗?即只能通过A的方法来实例化B,还是可以由A的实例来实例化B? - moonwave99
@moonwave99 - 仅限于 A 中。我正在编辑标题,因为它不够明确。 - JDelage
请查看我的编辑,将buildB()方法设为私有,然后你就完成了^^ - moonwave99
是的,我认为这可能是正确的方法。 - JDelage
3个回答

4
使用 debug_backtrace 函数:
class Ancestor{}

class A extends Ancestor{

    public function buildB()
    {

        return new B;

    }

}

class B extends Ancestor{

    public function __construct(){

        $backtrace = debug_backtrace();
        if( $backtrace[1]['class'] !== 'A' )
            throw new Exception("Don't you dare!");

        echo "Built successful!\n";

    }

}

试一下:

//Everything ok this way:
$a = new A;
$a -> buildB();

// You will have an exception in any other case:
try{

    $b = new B; 

}catch(Exception $e){

    echo $e -> getMessage();

}

编辑:如果您想确保在A的代码内部创建B,也可以这样做 - 只需将buildB设为私有即可。


1
哦,不错。我想不到那个。 - JDelage
我会进行一些测试,以查看debug_backtrace()是否足够好,以使其成为可行的解决方案。否则,您最好不要有这个要求。从一般的角度来看,解耦的代码比紧密耦合的代码更好,并且强制执行此要求违反了这个基本原则。话虽如此,您的特定情况可能需要此要求,但如果我设计这个,我会考虑不同的类结构。 - Tim G
@moonwave99,这是一个聪明的方法,不过我给你加一分。 - Tim G
@TimG 我同意你的观点 - 就我个人而言,我永远不会在代码中提供这样的强制措施:如果在某些对象之外创建对象会导致程序崩溃,那就不要这样做 :D 无论如何,还是谢谢你! - moonwave99

1

我并不是从 PHP 的角度考虑这个问题,而是更多地从面向对象编程的角度思考... 我认为你能够实现它的唯一方法就是将 B 的构造函数设为私有,然后暴露一个静态方法,该方法接受 A 的参数和 B 的输出参数,然后该方法可以私下实例化 B 并通过输出参数将其返回给 A。

//Pseudocode, language-indifferent

    class A{
       var _B;

       public B GetMeAnInstanceOfB(){
          _B=B.CreateInstanceOfB(this);
       }

       //alternate
       public B GetMeAnotherInstanceOfB(){
          _B=new B(this);
       }
    }

    class B{
       private B();
       //alternate
       private B(A);
       static B CreateInstanceOfB(A){
          return new(b);
       }
    }

这可能有点粗糙,而且可能存在漏洞,但这是一种尝试。从技术上讲,A的子类仍然可以获得B,因此如果您封闭该类(防止子类),那将关闭该门。

这是一个有趣的问题...

编辑:这个模块确实没有解决问题,但也许(?)它更好了 - 我为B创建了一个以A作为参数的公共构造函数,并且现在只在A中进行B的实例化。唯一的问题是它仍然存在JDelage指出的同样的问题 - 如果我实例化A,我可以构建一个B...


是的,我正在使用PHP工作,但问题确实与语言无关。你这里提出的想法很棒,我喜欢它。 - JDelage
如果它是语言无关的,则在A中定义嵌套私有类B。不幸的是,这在PHP中无法实现。但是,Stack Overflow上发布了一些解决方法。 - Josh
@DavidW 对不起,我并不是试图否定你的回答。只是考虑到他的评论提供了另一个回答/建议。 - Josh
@JDelage - 谢谢 - 让我在发帖之前强迫自己思考 :) - David W
@JDelage - 嗯...好吧,我想没有什么。我猜我之前认为如果只有A的实例可以创建B,则暗示着任何A的实例都可以创建B。这是我的推断有误吗?你是在考虑控制(缺乏更好的术语)A的实例吗? - David W
显示剩余6条评论

1

是的。

您可以通过使类B的构造函数接受一个参数来实现它。并为类A定义一个方法,该方法创建B的对象。

如果$argument == null || !($argument instanceof A),则抛出异常或中止程序。

示例代码:

class X {
    public $i = 0;
    public function getI() {
        return $i;
    }
    public function setI($x) {
        $i = $x;
    }
}

class A extends X {
    public function setI($x) {
        $i = $x * 2;
    }
    public function makeB($var){
        $b = new B($var);
    }
}

class B extends X {
    public function __construct($a) {
        if (null == $a) {
            echo "no arguments given!\r\n";
            //exit;
        }else if (!($a instanceof A)) {
            echo "disallowed\r\n";
            //exit;
        }else{
            echo "initialized b\r\n";
        }
    }
    public function setI($x) {
        $i = $x * 3;
    }
}

$a = new A();

$a->makeB();
$a->makeB(new X());
$a->makeB(&$a);

输出:

Warning: Missing argument 1 for A::makeB(), called in file.php
no arguments given!
disallowed
initialized b

您可以在这里看到演示。

这与@David W的答案类似,除了$i。我不明白$i是做什么的。 - JDelage
啊,那只是我添加的一个虚拟属性。你可以忽略它。 - Prasanth
好的,我想那可能是情况,只是让我有些困惑。 - JDelage

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接