这可能是一个常见的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中的错误)。