OOP可见性,一般来说,特别是在PHP中


OOP Visibility, in general and specifically in PHP

这可能是一个常见的OOP问题,但我使用的是PHP,所以如果它普遍存在,我想知道,但如果你知道它在PHP中丢失或扭曲,请给我警告。

实例化另一个类(它没有扩展)的对象的类是否可以设置实例化对象属性的可见性?

示例:

Class Widget {
    public $property1;
    public $property2;
    public $property3;
  }
Class ClockMaker {
  public function getWidget() {
      $this -> widget = new Widget();
      $this -> widget -> property1 = "special stuff";
   }
}
$my_clock = new ClockMaker();
$my_clock -> getWidget();
$my_clock -> widget -> property2 = "my tweak";  // Totally fine and expected...
$my_clock -> widget -> property1 = "my stuff";  // Should throw an error...

据我所知,如果我将property1设置为protected,ClockMaker将无法设置该值。但是,如果现在是这样的话,$my_clock就可以使用小部件对象了。

不管怎样,一旦设置了属性,就可以防止它被设置?

您的代码不会更改任何属性,也不会调用getWidget。它将在$my_clock对象中创建widget属性,然后用空值创建另一个对象(如果启用严格错误,则会看到严格通知)。在类中声明widget

Class ClockMaker {
  private $widget;
  public function getWidget() {
      $this -> widget = new Widget();
      $this -> widget -> property1 = "special stuff";
   }
}

为什么不使用get_和set_方法?看起来它应该能解决你的问题。这些也是OOP中在对象本身之外播放对象属性的最佳实践。

class Widget {
  private $foo;
  public function setFoo($val) { $this->foo = $val; )
  public function getFoo() { return $this->foo; )
}

你还问,"不管怎样,一旦设置了属性,就可以防止它被设置?"

class Widget {
  private $foo;
  public function setFoo($val) {
    if (!isset($this->foo)) {
      $this->foo = $val;
    }
    //If you want to throw an error or something just use an else clause here.
  }
  public function getFoo() { return $this->foo; )
}
$w = new Widget();
$w->setFoo('Foooooo');
echo $w->getFoo(); //Foooooo
$w->setFoo('Magic.');
echo $w->getFoo(); //Foooooo

如果你正在寻找其他东西,你能把这个问题说得更具体一些吗?:)

如果一个类声明了一些公共的东西,那么它就是公共的。如果您不希望类成员是公共的,那么它应该被声明为受保护的或私有的。除非你求助于内省和反思(这是非常不鼓励的,因为它通常会导致意大利面条代码,并导致性能下降),否则改变成员可见性的唯一方法就是继承子类中的类,并用新的可见性重新声明成员。当你这样做的时候,你只能让一个成员比以前更显眼(私人成员可以公开,但不能反过来)。

一般来说,你通常不想公开属性(变量),因为如果你这样做了,那么外部代理可以在没有任何控制的情况下随意更改类的状态,你很快就会陷入一片混乱。在您的示例中,如果您不希望外部代理更改属性,则需要将属性2设置为受保护或私有。

但是,如果外部代理仍然需要访问它,那么您应该为此作业提供getter和setter方法。getter返回属性的值,setter允许以可控的方式对其进行更改。Getter和setter允许您执行诸如使属性只读或实现延迟初始化(这意味着如果类需要对初始化一个属性,例如DB查找,然后可以推迟到实际需要该值)

class Widget ()
{
    protected $property2;
    public function getProperty2 ()
    {
        // This is an example of lazy initialization in a getter.
        if ($this -> property2 === NULL)
        {
            $this -> property2 = 'Value has been initialized'
        }
        return ($this -> property2);
    }
    public function setProperty2 ($newProp)
    {
        // Example of input validation
        if {($newProp !== NULL) && ($newProp !== 'This is an illegal value!')}
        {
            $this -> property2 = $newProp;
        }
        else
        {
            throw new InvalidArgumentException;
        }
        return ($this);
    }
}

如果您需要property2是只读的,那么只需完全省略setter(或者如果您仍然需要类中的验证功能,则将其设为非公共的)。由于无法从外部直接访问该属性,getter和setter可以让您完全控制进入该属性的内容以及它如何再次出现。

顺便提一下,在大多数情况下,类初始化它所依赖的类被认为是一种糟糕的做法。这会使单元测试更加困难(因为你无法测试一个单独初始化自己依赖项的类),并可能使你的代码不那么清晰(因为隐藏在类中的依赖项对于只使用公共API作为指南的类的人来说并不明显)。它还剥夺了一些灵活性,因为如果一个类初始化了它自己的依赖对象,那么你就不能用它们来代替提供相同接口的其他对象。该规则存在异常(例如在发生错误时生成异常),但作为一般规则,类不应包含new语句,也不应使用Registry或Singleton类拉入对象。它们还应该避免对其他类的静态调用。

使用依赖注入被认为是一个更好的主意。

Class Clockmaker 
{
    protected $widget;
    public function __construct (Widget $newWidget)
    {
        $this -> widget = $newWidget;
    }
}

为什么这样更好?答案是,现在很明显,ClockMaker类依赖于Widget类,因此它提高了可读性。这也意味着您可以使用任何Widget子类的对象来代替Widget本身,而不会出现问题。如果您进行单元测试,您可以将Widget替换为一个子类,在该子类中,所有方法始终返回相同的值,因此您现在可以单独测试ClockMaker类(如果测试过程中发生错误,您可以确定错误在ClockMaker中。通过ClockMaker初始化Widget本身,您不知道失败的测试是由于ClockMaker中的错误还是Widget中的错误)。