文章目录
面向对象特性
理解面向对象概念
面向对象(OO)开发方法会在系统中引入对象的分类、关系和属性,从而帮助程序开发和代码重用。
类和对象
-
面向对象软件由一系列具有属性和操作的自包含对象组成,这些对象之间能够交互,从而达到我们的要求。对象属性是与对象相关的特性或变量。操作则是对象可以执行的用来改变其自身或对外部产生影响的方法、行为或函数(属性可以与成员变量和特性这些词交替时通,而操作也可以与方法交替使用)。
-
面向对象软件的一个重要优点是支持和鼓励封装的能力,封装也叫数据隐藏。从本质上讲,访问一个对象中的数据只能通过对象的操作来实现,对象的操作也就是对象的接口。封装允许在不影响项目其他部分的情况下进行修改或修复bug。
-
在面向对象的软件中,对象是一个能够存储数据并且提供操作这些数据的方法集合。这个集合是唯一的,可标识的。对象可以按类进行分类。类是表示彼此之间可能互不相同,但是必须具有一些共同点的对象集合。虽然,类所包含的对象可能具有不同属性值,但是,这些对象都具有以相同方式实现的相同操作以及表示相同事务的相同属性。
多态性
面向对象的编程语言必须支持多态性,多态性是指不同类对同一操作可以有不同的行为和实现。在PHP中,只有类的成员函数可以是多态的。
继承
继承允许我们使用子类在类之间创建层次关系。子类将从它的超类(也叫父类)继承属性和操作。通过继承,我们可以在已有类的基础上创建新类。=>使代码有更好的可重用性。
创建类、属性和操作
类结构
- 使用关键字class创建类
- 通过在类定义中使用某些关键字来声明变量,可以创建属性。这些关键字与变量作用域相关:public、private、protected
- 通过在类定义中声明函数,可以创建类的操作。
class classname{
public $attribute1;
public $attribute2;
function operation1(){
//操作
}
function operation2($param1, $param2){
//操作
}
}
构造函数
当创建一个对象时,对象的构造函数将被调用。通常,这将执行一些有效的初始化任务。
构造函数的声明与其他操作的声明一样,只是其名称必须是__construct()。
function __construct($param){
echo "Construct called with parameter".$param."<br />";
}
PHP允许函数重载,意味着可以提供多个具有相同名称的函数,但这些函数必须具备不同数量或类型的参数。
析构函数
析构函数允许在销毁一个类之前被调用执行,它将完成一些操作或实现一些功能,这些操作或功能通常在所有对该类的引用都被重置或超出作用域时自动发生。
析构函数的名称必须是__destruct()。析构函数不能带有任何参数。
类的实例化
声明一个类后,创建一个可供使用的对象,它是一个特定的个体,即类的一个成员。使用关键字new创建一个对象。需要制定创建的对象时哪一个类的实例,并且为构造函数提供任何所需的参数。
class classname{
function __construct($param){
echo "Construct called with parameter".$param."<br />";
}
}
$a = new classname("First");
$b = new classname("Second");
使用类属性
在一个类中,可以访问一个特殊的指针: t h i s 。 可 以 通 过 this。可以通过 this。可以通过this->attribute引用变量。
class classname
{
public $attribute;
function operation($param)
{
$this->attribute = $param;
echo $this->attribute;
}
}
是否可以在类外访问一个属性是由访问修饰符确定的。
调用类操作
可以以调用类属性相同的方法调用类操作,可以按照迪奥用其他函数的方法调用类操作:操作名称以及必要参数。
class classname
{
function operation1()
{
//操作
}
function operation2($param1, $param2)
{
//操作
}
}
$a = new classname();
$a -> operation1();
$a -> operation2(12,'test');
$x = $a -> operation1(); #有返回值的情况下,这种方式获得返回数据
使用private和public关键字控制访问
访问修饰符:控制属性和方法的可见性。通常,访问修饰符放置在属性和方法声明之前。
- public:默认选项。公有属性或方法可以在类的内部和外部进行访问。
- private:被标记的属性或方法只能在类内部直接进行访问。且私有的属性和方法将不会被继承。
- protected:被标记的属性或方法只能在类内部进行访问。它也存在于任何子类。
编写访问器函数
通常,在类外部直接访问类属性不是一个好做法,可以通过 __ get() 和 __ set() 函数来实践封装特性。除了直接访问类属性,如果将属性设置为私有或受保护并且编写了访问器函数,可以通过一段代码来完成所有属性的访问。
class classname
{
private $attribute;
function __get($name) #返回$attribute属性值
{
return $this->$name;
}
function __set($name, $value) #给属性$attribute赋值
{
$this->$name = $value;
}
}
- __get()函数:有一个参数(属性名称),并且返回属性值
- __set()函数:有两个参数:属性名称和要赋予的值
- 不能直接访问这两个函数,双下划线表明这些函数在PHP中有特殊含义。使用这两个函数检查并设置任何不可直接访问的属性的值。即使声明了这两个函数,当访问声明为共有的属性时,他们也不会被使用。
$a -> attribute = 5;
#该语句隐式调用__set()函数,其中$name参数值被设置为"attribute",$value参数值被设置为5.__set()函数需要添加必要的错误检查代码。
$a -> attribute
#该代码将隐式调用__get()函数,其中$name参数值被设置为”attribuute“,__get()函数返回参数值的代码实现有程序员提供
访问器函数优点:只需要在一个地方修改代码就可以实现所有要修改的功能。
在PHP中实现继承
要指定一个类成为另一个类的子类,可以使用关键字extends。
class A
{
public $attribute1;
function operation1()
{}
}
class B extends A
{
public $attribute2;
function operation2()
{}
}
#如下所有对类B对象的操作和属性的访问都是有效的,作为A的子类,B类具有与A一样的功能和数据。
$b = new B();
$b -> operation1();
$b -> attribute1 = 10;
$b -> operation2();
$b -> attribute2 = 5;
继承是单方向的。子类可以从父类或超累继承特性,但是父类却不能从子类继承特性。
通过继承使用private和protected控制可见性
<?php
class A
{
private function operation1()
{
echo "operation1 called,"."<br />";
}
protected function operation2()
{
echo "operation2 called."."<br />";
}
public function operation3()
{
echo "operation3 called."."<br />";
}
}
class B extends A
{
function __construct()
{
# $this -> operation1();
# Error: Call to private method A::operation1() from context 'B'
$this -> operation2();
$this -> operation3();
}
}
$b = new B;
?>
说明:私有操作不能在子类中调用
# $b -> operation2();
# Error: Call to protected method A::operation2() from context ''
$b -> operation3();
说明:protected函数可以被继承但是只能在子类内部使用。
覆盖
在子类中,再次声明相同的属性和操作也是有效的,而且某些情况下这会非常有用。
覆盖:在子类中给某个属性赋予一个与其超类属性值不同的默认值,或者给某个操作赋予一个与其超类操作不同的功能。
<?php
class A
{
public $attributre = 'default value';
function operation()
{
echo "Something<br />";
echo 'The value of $attributre is '.$this -> attributre.'<br />';
}
}
class B extends A
{
public $attributre = 'different value';
function operation()
{
echo "Something else<br />";
echo 'The value of $attributre is '.$this -> attributre.'<br />';
}
}
$a = new A();
$a -> operation();
$b = new B();
$b -> operation();
?>
与在子类中定义新属性和操作并不影响超类一样,在子类中覆盖属性或操作也不会影响超类。如果子类提供了替代定义,则替代定义将拥有优先级并且覆盖初始定义。
parent关键字允许调用父类操作的最初版本。虽然调用了父类的操作,但是PHP将使用当前类的属性值。
<?php
class A
{
public $attributre = 'default value';
function operation()
{
echo "Something<br />";
echo 'The value of $attributre is '.$this -> attributre.'<br />';
}
}
class B extends A
{
public $attributre = 'different value';
function operation()
{
parent::operation(); #parent关键字的使用!!!
}
}
$a = new A();
$a -> operation();
$b = new B();
$b -> operation();
?>
使用final关键字进制继承和覆盖
当在一个函数声明前面使用这个关键字时,这个函数将不能再任何子类中覆盖;也可以使用final关键字禁止一个类被继承。
class A
{
public $attributre = 'default value';
final function operation()
{
echo "Something<br />";
echo 'The value of $attributre is '.$this -> attributre.'<br />';
}
}
class B extends A
{
public $attributre = 'different value';
function operation()
{
echo "Something else<br />";
echo 'The value of $attributre is '.$this -> attributre.'<br />';
}
}
# Fatal error: Cannot override final method A::operation()
final class A
{
public $attributre = 'default value';
final function operation()
{
echo "Something<br />";
echo 'The value of $attributre is '.$this -> attributre.'<br />';
}
}
class B extends A
{
public $attributre = 'different value';
function operation()
{
echo "Something else<br />";
echo 'The value of $attributre is '.$this -> attributre.'<br />';
}
}
#Fatal error: Class B may not inherit from final class (A)
多重继承
PHP不支持多重继承。即每个类都只能继承一个父类,一个父类可以有多个子类。
多重继承:极易产生混淆。
PHP不支持多重继承,但提供了两种机制来支持多重继承功能:接口和Trait。
接口
接口的思想:指定一个实现该接口的类必须实现的一系列函数。
interface Displayable
{
function display(); #接口不需要包含实现
}
class webPage implements Displayable #webPage类可以继承一个类,同时又可以实现一个或多个接口
{
function display()
{
//...
}
}
Trait
在Trait中,可以对将在多个类中复用的功能进行分组。一个类可以组合多个Trait,而Trait可以继承其他Trait。
Trait包含了实现;Trait是代码重用的最佳构建模块。
<?php
trait logger
{
public function logmessage($message, $level = 'DEBUG')
{
//wwrite $message to a log
}
}
class fileStorage
{
use logger;
function store($data)
{
//...
$this -> logmessage($msg);
}
}
?>
默认情况下,Trait方法覆盖继承的方法。
Trait的一个优点是可以组合多个Trait,当有多个方法具有相同的名字时,可以显示地指定需要使用特定Trait的功能。
<?php
trait fileLogger
{
public function logmessage($message, $level = 'DEBUG')
{
//wwrite $message to a log
}
}
trait sysLogger
{
public function logmessage($message, $level = 'ERROR')
{
//write $message to the syslog
}
}
class fileStorage
{
use fileLogger, sysLogger
{
fileLogger::logmessage insteadOf sysLogger; #insteadof关键字:指定要用的Trait
sysLogger::logmessage as private logsysmessage; #as关键字:重命名Trait
}
function store($data)
{
//...
$this -> logmessage($message);
$this -> logsysmessage($message);
}
}
?>
水平组合:完全由其他Trait组成或包含的Trait,在Trait内使用use语句,就像在类中使用一样。
类设计
创建类:减少创建一个新页面所需的HTML代码。这样在修改页面的时候只需要修改页面不同的部分,而相同的部分会自动生成。该类应该为建立新页面提供灵活的框架,而不应限制创作的自由。
自定义类代码
类需要一个逻辑名称,有意义!!!
<?php
class Page
{
//class Page's attributes
public $content;
public $title = "TLA Consulting Pty Ltd";
public $keywords = "TLA Consulting, Three Letter Abbreviation,
some of my best friends are search engines";
public $buttons = array("Home" => "home.php",
"Contact" => "contact.php",
"Services" => "services.php",
"Site Map" => "map.php");
// class Page's operations
public function __set($name, $value)
{
$this -> $name = $value;
}
public function Display()
{
echo "<html>\n<head>\n";
$this -> DisplayTitle();
$this -> DisplayKeywords();
$this -> DisplayStyles();
echo "</head>\n<body>\n";
$this -> DisplayHeader();
$this -> DisplayMenu($this -> buttons);
echo $this -> content;
$this -> DisplayFooter();
echo "</body>\n</html>\n";
}
public function DisplayTitle()
{
echo "<title>".$this -> title."</title>";
}
public function DisplayKeywords()
{
echo "<meta name = 'keywords' content='".$this -> keywords."'/>";
}
public function DisplayStyles()
{
?>
<link href="styles.css" type="text/css" rel="stylesheet">
<?php
}
public function DisplayHeader()
{
?>
<!-- page header -->
<header>
<img src="logo.gif" alt="TLA logo" height="70" width="70" />
<h1>TLA Consulting</h1>
</header>
<?php
}
public function DisplayMenu($buttons)
{
echo "<!-- menu -->
<nav>";
foreach($buttons as $name => $url){
#while (list($name, $url) = each($buttons)) {
$this->DisplayButton($name, $url,
!$this->IsURLCurrentPage($url));
}
echo "</nav>\n";
}
public function IsURLCurrentPage($url)
{
if (strpos($_SERVER['PHP_SELF'],$url) === false)
{
return false;
}else{
return true;
}
}
public function DisplayButton($name,$url,$active=true)
{
if ($active) { ?>
<div class="menuitem">
<a href="<?=$url?>">
<img src="s-logo.gif" alt="" height="20" width="20" />
<span class="menutext"><?=$name?></span>
</a>
</div>
<?php
} else { ?>
<div class="menuitem">
<img src="side-logo.gif">
<span class="menutext"><?=$name?></span>
</div>
<?php
}
}
public function DisplayFooter()
{
?>
<!-- page footer -->
<footer>
<p>© TLA Consulting Pty Ltd.<br />
Please see our
<a href="legal.php">legal information page</a>.</p>
</footer>
<?php
}
}
?>
首页使用Page类完成生成页面内容的大部分工作
<?php
require("page.php");
$homepage = new Page();
$homepage->content ="<!-- page content -->
<section>
<h2>Welcome to the home of TLA Consulting.</h2>
<p>Please take some time to get to know us.</p>
<p>We specialize in serving your business needs
and hope to hear from you soon.</p>
</section>";
$homepage -> Display();
?>
一个更好的方法时用继承来创建新类,新类从Page类里继承大多数功能,但是覆盖需要修改的部分。
<?php
require ("page.php");
class ServicesPage extends Page
{
private $row2buttons = array(
"Re-engineering" => "reengineering.php",
"Standards Compliance" => "standards.php",
"Buzzword Compliance" => "buzzword.php",
"Mission Statements" => "mission.php"
);
public function Display()
{
echo "<html>\n<head>\n";
$this->DisplayTitle();
$this->DisplayKeywords();
$this->DisplayStyles();
echo "</head>\n<body>\n";
$this->DisplayHeader();
$this->DisplayMenu($this->buttons);
$this->DisplayMenu($this->row2buttons);
echo $this->content;
$this->DisplayFooter();
echo "</body>\n</html>\n";
}
}
$services = new ServicesPage();
$services -> content ="<p>At TLA Consulting, we offer a number
of services. Perhaps the productivity of your employees would
improve if we re-engineered your business. Maybe all your business
needs is a fresh mission statement, or a new batch of
buzzwords.</p>";
$services->Display();
?>
面向对象高级功能
使用类级别常量
类级别常量:不需要初始化该类的情况下使用,通过::操作符并指定常量所属的类来访问类级别常量。
<?php
class Math{
const pi = 3.14159;
}
echo "Math::pi = ".Math::pi;
?>
实现静态方法
static关键字:适用于允许在未初始化类的情况下调用方法。在一个静态方法中,不能使用this关键字。
<?php
class Math{
static function squared($input)
{
return $input*$input;
}
}
echo Math::squared(8);
?>
检查类类型和类型提示
instanceof关键字允许检查一个对象的类型。它可以检查一个对象是否是特定类的实例,是否是从某个类继承过来或者是是否实现了某个接口。iinstanceof关键字是一个高效率的条件操作符。
前面的示例中,类B作为类A的子类
{$b instanceof B} 返回true
{$b instanceof A} 返回true
{$b instanceof Displayable} 返回false
PHP还提供了类型提示的功能。使用类型提示,可以指定必须传入的参数类类型,同时,如果传入的参数类类型不是指定的类型,将产生一个错误。类型提示适用于类和接口,不适用于Trait。
function check_hint(B $someclass){
//...
}
以上示例要求$someclass必须是类B的实例,如果传入其他类的实例,则产生错误。
延迟静态绑定
在对相同函数由多个实现的继承层级中,可以使用延迟静态绑定来确定调用具体类的方法。
<?php
class A
{
public static function whichclass()
{
echo __CLASS__;
echo "<br />";
}
public static function test()
{
self::whichclass();
}
}
class B extends A
{
public static function whichclass()
{
echo __CLASS__;
}
}
A::test();
B::test();
?>
上述代码中虽然类B覆盖了whichclass()方法,但是当B调用test()方法时,test()方法将在父类A上下文中执行。使用延迟静态绑定解决该问题。
static::whichclass(); #static修饰符确保PHP使用了运行时调用的类
对象克隆
clone关键字:允许复制一个已有对象。
$c = clone $b;
#将创建$b的副本,具有与$b相同的类型与属性值。
如果通过克隆获得非默认行为,必须在基类中创建一个 __clone()方法。该方法类似于构造函数或析构函数,不会直接调用它.该方法的一个优点是使用默认行为创建一个默认行为创建一个副本后能够被调用。在这个阶段,只改变需要改变的内容。
使用抽象类
抽象类:不能被实例化,同样类方法也没有实现,只是提供类方法的声明,没有具体实现。
任何包含抽象方法的类自身必须是抽象的。也可以声明没有任何抽象方法的抽象类。
抽象方法和抽象类主要用于复杂的类层次关系中,该层次关系需要确保每一个子类都包含并覆盖了某些特定方法,这也可以通过接口来实现。
__call()重载方法
__call()函数:实现方法重载,该方法必须带有两个参数,第一个包含了被调用的方法名称,而第二个包含了传递给该方法的参数数组。
public function __call($method, $p)
{
if($method == 'display'){
if(is_object($p[0])){
$this -> displayObject($p[0]);
}else if(is_array($p[0])){
$this -> displayArray($p[0]);
}else{
$this -> displayScalar($p[0]);
}
}
}
__autoload()方法
该函数是一个单独的函数,也就是可以在任何声明之外声明这个函数。如果实现了这个函数,它将在实例化一个还没有被声明的类时自动调用。该方法的主要用途是尝试引入特定文件,而又需要该文件来初始化所需类。
function __autoload($name){
include_once $name.".php"; #引入一个具有与该类相同名称的文件
}
迭代器和迭代
要实现一个迭代器,被迭代的类必须实现IteratorAggregate接口,并且定义一个能够返回迭代类实例的getIterator方法。这个类必须实现Iterator接口,该接口提供了一系列必须实现的方法。
<?php
class ObjectIterator implements Iterator {
private $obj;
private $count;
private $currentIndex;
function __construct($obj) #构造函数不是必须的,但很明显,在构造函数中可以设置将要迭代的项数和当前数据项的链接
{
$this->obj = $obj;
$this->count = count($this->obj->data);
}
function rewind() #将内部数据指针设置会数据开始处
{
$this->currentIndex = 0;
}
function valid() #将判断数据指针的当前位置是否还存在更多数据
{
return $this->currentIndex < $this->count;
}
function key() #返回数据指针值
{
return $this->currentIndex;
}
function current() #保存在当前数据指针的值
{
return $this->obj->data[$this->currentIndex];
}
function next() #在数据中移动数据指针的位置
{
$this->currentIndex++;
}
}
class Object1 implements IteratorAggregate
{
public $data = array();
function __construct($in)
{
$this->data = $in;
}
function getIterator()
{
return new ObjectIterator($this);
}
}
$myObject = new Object1(array(2, 4, 6, 8, 10));
$myIterator = $myObject->getIterator();
for($myIterator->rewind(); $myIterator->valid(); $myIterator->next())
{
$key = $myIterator->key();
$value = $myIterator->current();
echo $key." => ".$value."<br />";
}
?>
生成器
将生成器理解为:定义时像函数,运行时向迭代器。
生成器需要使用yield关键字返回执行结果。
必须在foreach循环中调用生成器函数,这将创建一个能够保存生成器函数内部状态的Generator对象。在外部foreach循环的每次迭代中,生成器将执行下一个内部迭代。
在外部foreach循环的下一次迭代中,生成器将从上次执行结束的位置继续执行,知道遇到下一个yield语句。
<?php
function fizzbuzz($start, $end)
{
$current = $start;
while ($current <= $end) {
if ($current%3 == 0 && $current%5 == 0) {
yield "fizzbuzz";
} else if ($current%3 == 0) {
yield "fizz";
} else if ($current%5 == 0) {
yield "buzz";
} else {
yield $current;
}
$current++;
}
}
foreach(fizzbuzz(1,20) as $number) {
echo $number.'<br />';
}
?>
迭代器使用了懒执行,一次只有一个值会被创建并保存在内存中!!!
将类转换成字符串
__toString()函数:该函数的所有返回内容都会被echo语句打印。
<?php
class Printable
{
public $testone;
public $testtwo;
public function __toString()
{
return (var_export($this, TRUE));
}
}
?>
使用反射API
反射:通过访问已有类和对象来找到类和对象结构及内容的能力。
#reflection.php
<?php
require_once("page.php");
$class = new ReflectionClass("Page");
echo "<pre>".$class."</pre>"; #<pre>标记位于不同行上
?>
名称空间
名称空间:将一组类和/或函数进行分类的方法。它可以将相关内容聚合在一个函数库。
优点:避免名称冲突;有助于缩短类名称。
创建:通过关键字namespace,后面时具体的名称空间值。在文件中,名称空间声明之后的代码将自动进行该名称空间。请注意,如果需要在一个文件中声明名称空间,文件第一行必须是名称空间声明。
#orders.php
<?php
namespace orders;
class order
{
//...
}
class orderItem
{
//...
}
?>
#访问这些类--加前缀
include 'orders.php';
$myOrder = new orders\order();
## 不加前缀
namespace orders; #名称空间声明位于文件头部,所以不需要添加前缀
include 'orders.php';
$myOrder = new order();
请注意:使用orders名称空间的函数时,需要使用名称空间具体值作为前缀,加上反斜线\,然后才是要使用的类名称。反斜线\是名称空间间隔符。
对于在名称空间内部引用的任何类,如果没有指定完整命名名称空间,都会被认为是在当前名称空间。但是,PHP将在当前名称空间搜索不带有完整名称的函数和常量,如果没有找到,PHP将在全局名称空间搜索。此行为i并不适用于类。
子名称空间
名称空间也有层级结构
<?php
namespace bob\html\page;
class Page{
//...
}
?>
# 在名称空间外使用Page类
$services = new bob\html\page\Page();
# 在bob名称空间是使用Page类
$services1 = new html\page\Page();
全局名称空间
任何没有在已声明名称空间下编写的代码都被认为属于全局名称空间。
有一个全局名称空间类Page。如果要访问,
$services2 = \Page();
名称空间的导入和别名
use语句可以用作名称空间的导入和别名
#用bob\html\page名称空间的代码
use bob\html\page;
$services3 = new page\Page();
#使用as定义别名
use bob\html\page as www;
$services4 = new www\Page();