这可能是一个普遍的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可以使用小部件对象。
无论如何要防止属性设置后设置?
答案 0 :(得分:1)
您的代码不会更改任何属性,并且永远不会调用getWidget。它将在widget
对象中创建$my_clock
属性,然后从空值创建另一个对象(如果启用严格错误,则会看到严格的通知)。在您的班级中声明widget
:
Class ClockMaker {
private $widget;
public function getWidget() {
$this -> widget = new Widget();
$this -> widget -> property1 = "special stuff";
}
}
答案 1 :(得分:0)
为什么不使用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 :(得分:0)
如果一个类声明某些公共内容,那么它就是公开的。如果您不打算将该类成员公开,则应将其声明为protected或private。除非你采用内省和反思(强烈劝阻,因为它通常会导致意大利面条代码并导致性能下降),否则改变成员可见性的唯一方法是继承子类中的类并重新声明成员新的知名度。当你这样做时,你只能使一个成员比以前更加明显(私人成员可以成为公共成员,但不是相反)。
一般来说,你通常不希望公开属性(变量),因为如果你这样做,那么外部代理可以随意改变类的状态而不需要任何控制,你可以快速进入一个大的一塌糊涂。在您的示例中,如果您不希望外部代理更改它,则需要使property2成为受保护的或私有的。
但是,如果外部代理仍需要访问它,那么您应该为此作业提供getter和setter方法。 getter返回属性的值,setter允许它以受控方式进行更改。 getters和setter允许你执行诸如使属性只读,或实现延迟初始化(意味着如果类需要执行一些昂贵的操作) 初始化一个属性,如数据库查找,然后它可以推迟到实际需要的值)
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(如果您的类中仍需要其验证功能,则将其设置为非公共)。由于酒店无法直接从外部访问,因此您可以完全控制进入物业的物品以及物品的再次出现。
顺便提一下,在大多数情况下,一个类初始化它依赖的类被认为是不好的做法。它使得单元测试更加困难(因为你不能测试一个单独初始化它自己的依赖项的类)并且可以使你的代码不那么清晰(因为隐藏在类中的依赖对于使用你的类的人来说并不是很明显只有公共API作为指南)。它还会让你失去一些灵活性,因为如果一个类初始化它自己的依赖对象,那么你就不能将它们替换为提供相同接口的其他对象。规则有例外(例如在发生错误时生成异常),但作为一般规则,类不应在其中包含new
语句,也不应使用Registry或Singleton类提取对象。他们还应该避免对其他类的静态调用。
使用Dependency Injection代替了更好的主意。
Class Clockmaker
{
protected $widget;
public function __construct (Widget $newWidget)
{
$this -> widget = $newWidget;
}
}
为什么这样更好?现在的答案很明显,你的ClockMaker类依赖于Widget类,因此它提高了可读性。它还意味着您可以使用任何Widget的子类代替Widget本身而没有问题。如果您进行单元测试,您可以将Widget替换为所有方法始终返回相同值的子类,因此您现在可以单独测试ClockMaker类(如果在测试期间发生错误,您可以确定错误是在ClockMaker中。使用ClockMaker初始化Widget本身,您不知道测试失败是由于Clockmaker中的错误还是Widget中的错误。