一、属性的引入
举个例子,我们需要对$user对象的public类变量name做trim操作,那么我们首先想到这么做:
$user = new User();
$user->name = trim($name);
然而,这样做有个问题,就是如果我们需要对所有地方的User实例的name属性都要进行trim操作,我们就需要改动多处;另外,假如哪一天我不仅要trim操作,还要首字母大写,那我还得改动很多地方。万一我遗漏了怎么办?
如果我们像这样去定义“属性”,而不是简单粗暴的定义一个名为publice $name的成员变量,那么就可以实现在任何地方,对User的实例的name属性进行trim操作,不用担心有漏网之鱼。同样,有了setter函数,我们如果还可以方便的再加上“首字母大写”的操作:
private $name;
public function setName($value)
{
$this->name = ucfirst(trim($value));
}
同样,在属性读之后的自定义处理:
public function getName()
{
return ucfirst($this->name);
}
或者结合更多成员变量做更为复杂的处理:
public function getName()
{
return ucfirst($this->firstname).' '.ucfirst($this->lastname);
}
二、属性与成员变量区别联系
===3个区别
内-外
成员变量是就类的结构而言的概念,属于内的概念。而属性是就类的功能逻辑而言的概念,属于外的概念。
读写权限控制
成员变量没有读写权限控制,而属性可以指定为只读或只写,或可读可写,这就有了权限这么一说
预处理-后处理
成员变量不对读出作任何后处理,不对写入作任何预处理,而属性则可以
===3个联系
二者的访问形式完全一样。只不过属性访问实际上是getter和setter方法创建了一个名为name的属性。在这个例子里,它指向了一个私有成员变量$name。getter和setter定义的属性和类的成员变量一样。二者的的主要区别在于:当这种属性被读取时,对应的 getter 方法将被调用; 而当属性被赋值时,对应的 setter 方法就调用。如:
pivate $name;//尽管$name是私有,但是调用$name或给$name赋值实际上是分别调用getName()、setName()
$name = $user->name;//等效于$name = $user->getName();
$user->name = 'Jason';//等效于$user = $user->setName('Jason');
public成员变量可以视为一个可读可写、没有任何预处理或后处理的属性。而private成员变量由于外部不可见,与属性“外”的特性不相符,所以不能视为属性。同样的,protect属性也不能视为属性
大多数情况下,属性要由成员变量来实现,如:User类有一个成员变量int $age表示年龄。年龄是属性,$age是成员变量
但是二者并没有必然的关系。与非门里就是有2个成员变量($_key1与$_key2),但是有3个属性(key1,key2,output)
class NotAndGate extends Object{
private $_key1;
private $_key2;
public function setKey1($value){
$this->_key1 = $value;
}
public function setKey2($value){
$this->_key2 = $value;
}
public function getOutput(){
if (!$this->_key1 || !$this->_key2)
return true;
else if ($this->_key1 && $this->_key2)
return false;
}
}
三、实现属性3步走
class User extends yii\base\Object // 第一步:继承自 yii\base\Object
{
private $_title; // 第二步:声明一个私有成员变量
public function getTitle() // 第三步:提供getter和setter
{
return $this->_title;
}
public function setTitle($value)
{
$this->_title = trim($value);
}
}
//在读取和写入对象的一个不存在的成员变量时, __get() __set() 会被自动调用。
public function __get($name) // 这里$name是属性名
{
$getter = 'get' . $name; // getter函数的函数名
if (method_exists($this, $getter)) {
return $this->$getter(); // 调用了getter函数
} elseif (method_exists($this, 'set' . $name)) {
throw new InvalidCallException('Getting write-only property: '
. get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Getting unknown property: '
. get_class($this) . '::' . $name);
}
}
// $name是属性名,$value是拟写入的属性值
public function __set($name, $value)
{
$setter = 'set' . $name; // setter函数的函数名
if (method_exists($this, $setter)) {
$this->$setter($value); // 调用setter函数
} elseif (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Setting read-only property: ' .
get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Setting unknown property: '
. get_class($this) . '::' . $name);
}
}
这里的访问不存在的成员变量我一直困惑,倒饬了好久。明明是存在啊,存在一个私有的成员变量啊。这个不存在不是真的不存在,而是外部没有权限访问到的意思。
关于public(都可访问)、protected(本类和子类)、private(本类)举例说明如下:
//父类
class father{
public function a(){
echo "function a<hr>";
}
protected function b(){
echo "function b<hr>";
}
private function c(){
echo "function c<hr>";
}
}
//子类
class child extends father{
function d(){
parent::a();//调用父类的a方法
}
function e(){
parent::b(); //调用父类的c方法
}
function f(){
parent::c(); //调用父类的b方法
}
}
$father=new father();
$father->a();
$father->b(); //显示错误 外部无法调用私有的方法 Call to protected method father::b()
$father->c(); //显示错误 外部无法调用受保护的方法Call to private method father::c()
$chlid=new child();
$chlid->d();
$chlid->e();
$chlid->f();//显示错误 无法调用父类private的方法 Call to private method father::c()
$user = new User();
$user->_title <==> $user->getTitle()$user->_title = 'haha' <==> $user->setTitle('haha')
$_title是私有的,只有类内部能使用(User::_title)。这里的类实例$user是类的实例,$user->_title这是从外部访问的,所以肯定访问不到。所以启动__get()和)__set()继而调用getTitle()和setTitle()来访问。
问:将 private $_title 写成 public $title ,也是可以实现对 $post->title 的读写的。但这不是好的习惯。原因如下:
1、失去了类的封装性。 一般而言,成员变量对外不可见是比较好的编程习惯。 从这里你也许没看出来,但是假如有一天,你不想让用户修改标题了,只要把setter删掉。 怎么确保代码中没有直接修改标题?使用 public $title 的方法的话,那么一旦有没清理干净的对标题的写入,就会抛出异常。而private $title是禁止直接读和取的,可以排查写入的异常。
2、牵一发动全身。对于标题的写入,你想去掉空格。 使用setter的方法,只需要像上面的代码段一样在这个地方调用 trim() 就可以了。 但如果使用 public $title 的方法,那么每个写入语句都要调用 trim() 。你能保证没有一处遗漏?private $title只要改一次就能作用所有地方吗????我理解的是public $tittle所有地方都可以调用修改,因而需要所有调用修改的地方都发生变化。但是private $title只有类内部($this->_title)调用修改,只要改这一处就可以了。
四、提高效率的小技巧
使用 $pro = $object->getPro() 来代替 $pro = $object->pro , 用 $objcect->setPro($value) 来代替 $object->pro = $value 。 这在功能上是完全一样的效果,但是避免了使用 __get() 和 __set() ,相当于绕过了遍历的过程。