使用设计模式是促进最佳实践和良好设计的好办法。设计模式可以提供针对常见的编程问题的灵活的解决方案。
工厂模式(Factory)允许你在代码执行时实例化对象。它之所以被称为工厂模式是因为它负责“生产”对象。工厂方法的参数是 你要生成的对象对应的类名称。
调用工厂方法
<?php
class Example
{
// The parameterized factory method
public static function factory($type)
{
if (include_once 'Drivers/' . $type . '.php') {
$classname = 'Driver_' . $type;
return new $classname;
} else {
throw new Exception ('Driver not found');
}
}
}
?>
按上面的方式可以动态加载drivers。如果Example类是一个数据库抽象类,那么可以这样来生成MySQL和 SQLite驱动对象:
<?php
// Load a MySQL Driver
$mysql = Example::factory('MySQL');
// Load a pgSQL Driver
$sqlite = Example::factory('pgSQL');
?>
单例模式(Singleton)用于为一个类生成一个唯一的对象。最常用的地方是数据库连接。 使用单例模式生成一个对象后,该对象可以被其它众多对象所使用。
单例模式
<?php
class Example
{
// 保存类实例在此属性中
private static $instance;
// 构造方法声明为private,防止直接创建对象
private function __construct()
{
echo 'I am constructed';
}
// singleton 方法
public static function singleton()
{
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}
return self::$instance;
}
// Example类中的普通方法
public function bark()
{
echo 'Woof!';
}
// 阻止用户复制对象实例
public function __clone()
{
trigger_error('Clone is not allowed.', E_USER_ERROR);
}
}
?>
<?php
// 这个写法会出错,因为构造方法被声明为private
$test = new Example;
// 下面将得到Example类的单例对象
$test = Example::singleton();
$test->bark();
// 复制对象将导致一个E_USER_ERROR.
$test_clone = clone $test;
?>
__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state() 和 __clone()等方法在PHP中被称为"魔术方法"(Magic methods)。 你在命名自己的类方法时不能使用这些方法名, 除非你希望使用"魔术"功能。
PHP把所有以__(两个下划线)开头的类方法当成魔术方法。所以当你定义类方法时,除了上述魔术方法,建议不要以__为前缀。
serialize() 函数会检查是否存在一个魔术方法 __sleep().如果存在,__sleep()方法会先被调用,然后才执行序列化操作。这个功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法不返回任何内容,则NULL
被序列化,并产生一个E_NOTICE
错误。
__sleep()方法常用于提交未提交的数据,或类似的清理操作。同时,如果你有一些很大的对象, 不需要全部保存,这个功能就很好用。
与之相反,unserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用__wakeup方法,预先准备对象需要的资源。
__wakeup()经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
Sleep 和 wakeup
<?php
class Connection
{
protected $link;
private $server, $username, $password, $db;
public function __construct($server, $username, $password, $db)
{
$this->server = $server;
$this->username = $username;
$this->password = $password;
$this->db = $db;
$this->connect();
}
private function connect()
{
$this->link = mysql_connect($this->server, $this->username, $this->password);
mysql_select_db($this->db, $this->link);
}
public function __sleep()
{
return array('server', 'username', 'password', 'db');
}
public function __wakeup()
{
$this->connect();
}
}
?>
<?php
// Declare a simple class
class TestClass
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function __toString() {
return $this->foo;
}
}
$class = new TestClass('Hello');
echo $class;
/*输出:
Hello
*/
?>
在PHP 5.2.0之前,__toString()方法只有结合使用echo() 或 print()时 才能生效。PHP 5.2.0之后,则可以在任何字符串环境生效(例如通过printf(),使用%s修饰符),但 不能用于非字符串环境(如使用%d修饰符)。从PHP 5.2.0,如果将一个未定义__toString()方法的对象 转换为字符串,会报出一个E_RECOVERABLE_ERROR
错误。
$...
] )当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
Note:
本特性只在PHP 5.3.0 及以上版本有效。
<?php
class CallableClass
{
function __invoke($x) {
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
/*
输出:
int(5)
bool(true)
*/
?>
$properties
)当调用var_export()时,这个静态 方法会被调用(自PHP 5.1.0起有效)。
本方法的唯一参数是一个数组,其中包含按array('property' => value, ...)格式排列的类属性。
<?php
class A
{
public $var1;
public $var2;
public static function __set_state($an_array) // As of PHP 5.1.0
{
$obj = new A;
$obj->var1 = $an_array['var1'];
$obj->var2 = $an_array['var2'];
return $obj;
}
}
$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';
eval('$b = ' . var_export($a, true) . ';'); // $b = A::__set_state(array(
// 'var1' => 5,
// 'var2' => 'foo',
// ));
var_dump($b);
/*
输出:
object(A)#2 (2) {
["var1"]=>
int(5)
["var2"]=>
string(3) "foo"
}
*/
?>
PHP 5 新增了一个 final 关键字。如果父类中的方法被声明为final,则子类无法覆盖该方法; 如果一个类被声明为final,则不能被继承。
Final 方法示例
<?php
class BaseClass {
public function test() {
echo "BaseClass::test() called\n";
}
final public function moreTesting() {
echo "BaseClass::moreTesting() called\n";
}
}
class ChildClass extends BaseClass {
public function moreTesting() {
echo "ChildClass::moreTesting() called\n";
}
}
// 产生 Fatal error: Cannot override final method BaseClass::moreTesting()
?>
Final 类示例
<?php
final class BaseClass {
public function test() {
echo "BaseClass::test() called\n";
}
// 这里无论你是否将方法声明为final,都没有关系
final public function moreTesting() {
echo "BaseClass::moreTesting() called\n";
}
}
class ChildClass extends BaseClass {
}
// 产生 Fatal error: Class ChildClass may not inherit from final class (BaseClass)
?>
对象复制可以通过clone关键字来完成(如果可能,这将调用对象的__clone()方法)。对象中的 __clone()方法不能被直接调用。
当对象被复制后,PHP5会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。
当复制完成时, 如果定义了__clone()方法, 则新创建的对象(复制生成的对象)中的__clone()方法会被调用, 可用于修改属性的值(如果有必要的话)。
复制一个对象
<?php
class SubObject
{
static $instances = 0;
public $instance;
public function __construct() {
$this->instance = ++self::$instances;
}
public function __clone() {
$this->instance = ++self::$instances;
}
}
class MyCloneable
{
public $object1;
public $object2;
function __clone()
{
// 强制复制一份this->object, 否则仍然指向同一个对象
$this->object1 = clone $this->object1;
}
}
$obj = new MyCloneable();
$obj->object1 = new SubObject();
$obj->object2 = new SubObject();
$obj2 = clone $obj;
print("Original Object:\n");
print_r($obj);
print("Cloned Object:\n");
print_r($obj2);
/*
输出:
Original Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 1
)
[object2] => SubObject Object
(
[instance] => 2
)
)
Cloned Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 3
)
[object2] => SubObject Object
(
[instance] => 2
)
)
*/
?>
PHP5中的对象比较(object comparison)要比PHP4中复杂,也比其它一般的面向对象语言复杂。
当使用对比操作符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。
而如果使用全等操作符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
通过下面的示例你可以理解以上原则。
PHP 5的对象比较
<?php
function bool2str($bool)
{
if ($bool === false) {
return 'FALSE';
} else {
return 'TRUE';
}
}
function compareObjects(&$o1, &$o2)
{
echo 'o1 == o2 : ' . bool2str($o1 == $o2) . "\n";
echo 'o1 != o2 : ' . bool2str($o1 != $o2) . "\n";
echo 'o1 === o2 : ' . bool2str($o1 === $o2) . "\n";
echo 'o1 !== o2 : ' . bool2str($o1 !== $o2) . "\n";
}
class Flag
{
public $flag;
function Flag($flag = true) {
$this->flag = $flag;
}
}
class OtherFlag
{
public $flag;
function OtherFlag($flag = true) {
$this->flag = $flag;
}
}
$o = new Flag();
$p = new Flag();
$q = $o;
$r = new OtherFlag();
echo "Two instances of the same class\n";
compareObjects($o, $p);
echo "\nTwo references to the same instance\n";
compareObjects($o, $q);
echo "\nInstances of two different classes\n";
compareObjects($o, $r);
/*
输出:
Two instances of the same class
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : FALSE
o1 !== o2 : TRUE
Two references to the same instance
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : TRUE
o1 !== o2 : FALSE
Instances of two different classes
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE
*/
?>
PHP 5 可以使用类型约束。函数的参数可以指定只能为对象(在函数原型里面指定类的名字),php 5.1 之后也可以指定只能为数组。
注意,即使使用了类型约束,如果使用NULL作为参数的默认值,那么在调用函数的时候依然可以使用NULL作为实参。
类型约束示例
<?php
//如下面的类
class MyClass
{
/**
* 测试函数
* 第一个参数必须为类OtherClass的一个对象
*/
public function test(OtherClass $otherclass) {
echo $otherclass->var;
}
/**
* 另一个测试函数
* 第一个参数必须为数组
*/
public function test_array(array $input_array) {
print_r($input_array);
}
}
//另外一个类
class OtherClass {
public $var = 'Hello World';
}
?>
函数调用的参数与定义的参数类型不一致时,会抛出一个致命错误。
<?php
// 两个类的对象
$myclass = new MyClass;
$otherclass = new OtherClass;
// 致命错误:参数1必须是类OtherClass的一个对象
$myclass->test('hello');
// 致命错误:参数1必须为类OtherClass的一个实例
$foo = new stdClass;
$myclass->test($foo);
// 致命错误:参数1不能为null
$myclass->test(null);
// 正确:输出 Hello World
$myclass->test($otherclass);
// 致命错误:参数1必须为数组
$myclass->test_array('a string');
// 正确:输出 数组
$myclass->test_array(array('a', 'b', 'c'));
?>
类型约束不只是用在类的成员函数里,也能使用在函数里。
<?php
// 如下面的类
class MyClass {
public $var = 'Hello World';
}
/**
* 测试函数
* 第一个参数必须是类MyClass的一个对象
*/
function MyFunction (MyClass $foo) {
echo $foo->var;
}
// 正确
$myclass = new MyClass;
MyFunction($myclass);
?>
类型约束允许NULL值:
<?php
/* 接受NULL值 */
function test(stdClass $obj = NULL) {
}
test(NULL);
test(new stdClass);
?>
类型约束只支持对象 和 数组(php 5.1之后)两种类型。而不支持整型 和 字符串类型。
从PHP 5.3.0开始,PHP增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类。
该功能从语言内部角度考虑被命名为”后期静态绑定“。”后期绑定“的意思是说,static::不再被解析为定义当前方法所在的类,而是在实际运行时计算的。也可以称之为”静态绑定“,因为它可以用于(但不限于)静态方法的调用。
使用self:: 或者 __CLASS__对当前类的静态引用,取决于定义当前方法所在的类:
self:: 用法
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
/*
输出:
A
*/
?>
后期静态绑定试图通过引入一个关键字表示运行时最初调用的类来绕过限制。简单地说,这个关键字能够让你在上述例子中调用test()时引用的类是B而不是A。最终决定不引入新的关键字,而是使用已经预留的static关键字。
static:: 简单用法
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // 后期静态绑定从这里开始
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
/*
输出:
B
*/
?>
Note:
static:: 在处理静态方法时与 $this 是不同的。 $this-> 会遵循继承规则,但是 static:: 不会。该差异将稍后在本手册中详细说明。
static:: 用于非静态引用
<?php
class TestChild extends TestParent {
public function __construct() {
static::who();
}
public function test() {
$o = new TestParent();
}
public static function who() {
echo __CLASS__."\n";
}
}
class TestParent {
public function __construct() {
static::who();
}
public static function who() {
echo __CLASS__."\n";
}
}
$o = new TestChild;
$o->test();
/*
输出:
TestChild
TestParent
*/
?>
Note:
后期静态绑定的处理方式解决了以往完全没有办法解决的静态调用。另外一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。
<?php
class A {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__."\n";
}
}
class B extends A {
public static function test() {
A::foo();
parent::foo();
self::foo();
}
public static function who() {
echo __CLASS__."\n";
}
}
class C extends B {
public static function who() {
echo __CLASS__."\n";
}
}
C::test();
/*
输出:
A
C
C
*/
?>
在PHP中有很多方式来触发一个方法的调用,例如回调函数或者魔术方法。因为后期静态绑定取决于运行时的信息,因此在特殊情况下可能会得到意想不到的结果。
在魔术方法中使用后期静态绑定
<?php
class A {
protected static function who() {
echo __CLASS__."\n";
}
public function __get($var) {
return static::who();
}
}
class B extends A {
protected static function who() {
echo __CLASS__."\n";
}
}
$b = new B;
$b->foo;
/*
输出:
B
*/
?>
在php5 的对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。下面通过一些例子来说明。
php的引用是别名,就是两个不同的变量名字指向相同的内容。在php5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。
引用和对象
<?php
class A {
public $foo = 1;
}
$a = new A;
$b = $a; // $a ,$b都是同一个标识符的拷贝
// ($a) = ($b) = <id>
$b->foo = 2;
echo $a->foo."\n";
$c = new A;
$d = &$c; // $c ,$d是引用
// ($c,$d) = <id>
$d->foo = 2;
echo $c->foo."\n";
$e = new A;
function foo($obj) {
// ($obj) = ($e) = <id>
$obj->foo = 2;
}
foo($e);
echo $e->foo."\n";
/*
输出:
2
2
2
*/
?>
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。 如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()来实现。
<?php
// classa.inc:
class A {
public $one = 1;
public function show_one() {
echo $this->one;
}
}
// page1.php:
include("classa.inc");
$a = new A;
$s = serialize($a);
// 把变量$s保存起来以便文件page2.php能够读到
file_put_contents('store', $s);
// page2.php:
// 要正确了解序列化,必须包含下面一个文件
include("classa.inc");
$s = file_get_contents('store');
$a = unserialize($s);
// 现在可以使用对象$a里面的函数 show_one()
$a->show_one();
?>
当一个应用程序使用函数session_register()来保存对象到会话中时,在每个页面结束的时候这些对象都会自动序列化,而在每个页面开始的时候又自动解序列化。 所以一旦对象被保存在会话中,整个应用程序的页面都能使用这些对象。但是,session_register()这个函数在php5.3.0已经废弃,而且在php6.0.0就不再支持,所以不要依赖这个函数。
在应用程序中序列化对象以便在之后使用,强烈推荐在整个应用程序都包含对象的类的定义。 不然有可能出现在解序列化对象的时候,没有找到该对象的类的定义,从而把没有方法的类__PHP_Incomplete_Class_Name作为该对象的类,导致返回一个没有用的对象。
所以在上面的例子中,当运行session_register("a"),把变量$a放在会话里之后,需要在每个页面都包含文件classa.inc,而不是只有文件page1.php和page2.php。
克隆
$pig1=$pig 这是一个典型的引用关系,而非克隆
怎么能证明:改了$pig1的名字,$pig的名字也发生变化,因此证明它们的关系是引用关系。
魔术方法__clone 不需要传入任何参数,在外面执行clone关键字的时候,自动调用这个魔术方法。在这个魔术方法,可以来实现我们需要的克隆逻辑。
__call 查找不存在的成员方法的时候,自动执行__call方法。必须要传进两个参数,第一个参数为方法名,第二个参数为向方法里面传入的实例,为一个索引数组。
__autoload【重点,对很多同学来说也是难点】 自动加载
1,因为它是根据new的类名去找文件,所以类名要和文件名一样
2,在加载的时候,用谁的时候就会去加载谁,所以速度很快
3,即使是继承的类,它也会加载进来,避免逻辑太过于复杂
4,在类的方法当中去new了其他的类,它会也帮你去找这个类。
避免,一次性要加载几百个类文件过来,效率高,并且很方便,不用我们去管怎么去加载的。
注意:
由于未来你就会用到linux操作系统,因为linux操作系统文件名严格区分大小写,因此请注意,类名和文件名大小写一致。
基本格式:
function __autoload($className){
}
串行化的时候serialize的时候,自动执行__sleep方法,需要打包哪个成员属性你就返回的时候,在数组里面,放入哪个成员属性变量名的字符串。
反串行化的时候unserialize执行__wakeup方法,可以直接在里面来设置对应的成员属性。
抽象类和抽象方法:
1, 抽象类是一个特殊类
2, 有抽象方法的类就是抽象类
3, 抽象类也很有用,但是你可能会一段时间用不到。
能够很好的解决团队配合,能够让各个部门在功能没有完成之前就可以知道对方的功能,甚至类结构。也可以拿到这个抽象类后,在这个类的基础上面扩展更多的功能。
抽象方法:没有方法体的方法就是抽象方法。
注意:
1, 抽象当中用抽象方法的话,抽象方法前面必须要加abstract这个关键字
2, 如果有抽象方法,抽象类前面必须要加abstract这个关键字
3, 抽象类当中可以有普通类当正常的属性啊,方法啊
4, 抽象方法使用的时候,必须要继承过去,实现全部的抽象方法,只要有一个方法未实现,很抱歉,报错!!!
5, 抽象类当中的真实存在的方法和属性继承后,在类当中是可以直接使用的。
6, 也可以对抽象类加上修饰等级的关键词。抽象类与抽象类之间可以继承
7, 抽象类当中的方法,可以加上形参,可以加上默认值,加了默认值和形参后,在真实的方法当中也必须要加形参和默认值。
接口:
1, 接口是一个特殊的抽象类
2, 接口当中的所有方法都为抽象方法,接口当中只能有常量的成员属性。
3, 接口定义以interface关键词开始
4, 接口在实现的过程当中,必须要加implements关键词去实现,在具体实现的子类当中必须要实现接口当中的全部方法
5, 接口当中的方法必须要全部都是public 等级
6, 可以在子类当中一次性实多个接口,也就是说可以用多个接口来规范一个类
7, 接口与接口之间能继承吗?接口与接口之间可以继承。但是方法必须要为public等级
8, Php只支持单继承,但是可以用接口模拟多继承关系
多态:
就是在一个类里面,调用其他不同的类。让当前类,能够处理更加健壮的业务。