面向对象编程
类,对象,方法和属性
面向对象编程是一种编程风格,习惯上把一个特定主题的所有变量和功能归为一个类。面向对象程序设计被认为比过程式的程序风格更加先进和高效。这个效率源于它支持更好的代码组织,提供模块化,并减少了重复自己的代码。据说,我们可能还是喜欢在小而简单的项目中使用过程式的风格。然而,随着我们的项目越来越复杂,我们最好使用面向对象的风格。
用一个比喻来说明类和对象的关系: 人类这个群体就是一个类,一个人就可以理解为是一个对象,而这个人的身高、体重就可以理解成是这个对象的属性;这个人会唱歌,会吃饭等行为就是方法。
我们将讨论这几个问题
如何创建类?
如何添加属性到一个类?
如何从一个类创建对象?
如何获取和设置对象的属性?
如何添加方法到一个类?
如何创建类?
对于下面给出的例子,我们将创建一个汽车类。
class Car {
// The code
}
如何添加属性到一个类?
让我们添加一些属性到汽车类。属性可以像任何其他变量一样接受字符串,整数和布尔值(真/假值)等值。
class Car {
public $comp;
public $color = 'beige';
public $hasSunRoof = true;
}
如何从一个类创建对象?
我们可以从同一个类创建多个对象,每个对象都有自己的一组属性。我们使用new关键字创建对象。创建对象的过程也称为实例化。
$bmw = new Car();
对象,它们有什么好处?
在过程式的风格中,所有的函数和变量在全局范围内,通过调用它们的名字来访问它们。
而类的存在,使类内部的任何东西都隐藏起来。这是因为类中的代码被封装在类范围内,而不在全局范围内。通过从类中创建对象来实现这一点。
我们可以从同一个类创建尽可能多的对象,他们都将共享类的方法和属性。看到下面的图片:
从相同的汽车类,我们创造了三个单独的对象变量:奔驰,宝马,和奥迪。
尽管所有的对象都是由同一个类创建的,因此具有类的方法和属性,但它们仍然不同。这不仅仅是因为它们具有不同的变量名,而且因为它们可能具有分配给它们属性的不同值。
例如,在上面的图像中,它们的颜色属性不同 - 奔驰是绿色,宝马蓝色,奥迪是橙色。
一个类包含所有由它创建的对象共享的方法和属性。尽管对象共享相同的代码,但它们可以有不同的行为,因为它们的属性可以被赋予不同的值。
如何获取/设置对象的属性?
一旦我们创建了一个对象,我们就可以得到它的属性。
class Car {
public $comp;
public $color = 'beige';
public $hasSunRoof = true;
}
$bmw = new Car();
echo $bmw->color; //获取属性 beige
$bmw->color = 'blue'; //设置属性
echo $bmw->color; //获取属性 blue
如何添加方法到一个类?
类通常包含函数。一个类中的函数被称为方法。这里我们将方法hello() 添加到类中。
class Car {
public $comp;
public $color = 'beige';
public $hasSunRoof = true;
public function hello()
{
return "beep";
}
}
$bmw = new Car ();
$mercedes = new Car ();
echo $bmw -> hello(); // beep
echo $mercedes -> hello(); // beep
$this关键字
$this表示该对象自己,并允许我们在类的范围内访问它。
class Car {
// The properties
public $comp;
public $color = 'beige';
public $hasSunRoof = true;
// 这个方法现在可以获取到类内的属性了。
public function hello(){
return "Beep I am a " . $this->comp . ", and I am " . $this->color;
}
}
$bmw = new Car();
$mercedes = new Car ();
$bmw->comp = "BMW";
$bmw->color = "blue";
$mercedes->comp = "Mercedes Benz";
$mercedes->color = "green";
//我们调用hello方法。
echo $bmw->hello(); // Beep I am a BMW, and I am blue.
echo $mercedes->hello(); // Beep I am a Mercedes Benz, and I am green.
链式方法和属性
class Car {
public $tank; //剩余的油量
//加油
public function fill($float) {
$this-> tank += $float;
return $this;
}
// 汽车行驶并且消耗油
public function ride($float) {
$miles = $float;
$gallons = $miles/50;
$this->tank -= ($gallons);
return $this;
}
}
$bmw = new Car();
//加10份油,并且行驶40miles,得到最后的剩余油量$tank.
$tank = $bmw->fill(10)->ride(40)->tank;
echo $tank;
//result 9.2
可见性是面向对象中非常重要的概念,一个优秀的设计,往往是对可见性的控制非常到位的。
只开放给外部需要调用的方法,或者属性。就像一个黑箱子,把不需要暴露出来的统统隐藏起来。
public修饰符允许来自外部或内部的代码访问类的方法和属性。
private修饰符则阻止从类外的任何代码访问类的方法或属性,当我们尝试从类的外部设置它的值时,我们遇到了一个致命的错误。
class Car {
private $model;
public function getModel()
{
return "The car model is " . $this -> model;
}
}
$mercedes = new Car();
//无法从类的外部访问或者设置model私有属性的值。故会报错
$mercedes->model="Mercedes benz";
echo $mercedes->getModel();
Fatal error: Cannot access private property Car::$model
如何设置和读取 私有属性呢?
为了与私有属性进行交互,我们使用public方法,因为它们可以与类外和类内的代码交互。
如下:
class Car {
private $model;
//public的方法允许被类外部代码访问
public function setModel($model)
{
$this->model = $model;
}
public function getModel()
{
return "The car model is " . $this->model;
}
}
$mercedes = new Car();
$mercedes -> setModel("Mercedes benz");
echo $mercedes -> getModel();
?>
__construct()构造函数
魔术方法的名字总是以两个下划线开头,而__construct()方法也不例外。当我们实例化(new)一个对象的时候,就会自动调用__construct构造函数。这种方法称为构造函数。
通常我们使用构造函数初始化一个对象,比如为一个属性设置一个值。
class Car{
private $model;
// A constructor method.
public function __construct($model)
{
$this -> model = $model;
}
}
$car1 = new Car ('mercedes');
__CLASS__
魔术常量,用于获取类名。
class Car {
private $model = '';
//__construct
public function __construct($model = null)
{
if($model)
{
$this -> model = $model;
}
}
public function getCarModel()
{
return " The " . __CLASS__ . " model is: " . $this -> model;
}
}
$car1 = new Car('Mercedes');
echo $car1 -> getCarModel();
// The
// Car
// model is: Mercedes
其他魔术常量:
__LINE__ | 得到使用该常数的行号 |
__FILE__ | 获取使用该常量的完整路径或文件名。 |
__METHOD__ | 获取使用该常量的方法的名称 |
面向对象编程的主要优点之一是能够通过继承减少代码重复。
当程序员不止一次地写同一个代码时,就会出现代码重复。在继承中,我们有一个拥有自己的方法和属性的父类,以及可以使用父代码的子类(或多个类)。通过使用继承,我们可以创建一个可重用的代码片段,只在父类中写入一次,并在子类中再次使用。
为了声明一个类继承另一个类的代码,我们使用extends关键字:
class Parent {
// The parent’s class code
}
class Child extends Parent {
// The child can use the parent's class code
}
在下面的例子中,跑车类继承了汽车类,所以它可以访问汽车的所有方法和非私有的属性。
//父类
class Car {
private $model;
//Public setter method
public function setModel($model)
{
$this -> model = $model;
}
public function hello()
{
return "beep! I am a " . $this -> model . "
";
";
}
}
//子类
class SportsCar extends Car {
}
//实例化一个子类
$sportsCar1 = new SportsCar();
//使用父类的方法
$sportsCar1 -> setModel('Mercedes Benz');
echo $sportsCar1 -> hello();
子类重写父类的方法
// The parent class has hello method that returns "beep".
class Car {
public function hello()
{
return "beep";
}
}
//The child class has hello method that returns "Halllo"
class SportsCar extends Car {
public function hello()
{
return "Hallo";
}
}
$sportsCar1 = new SportsCar();
echo $sportsCar1 -> hello(); //Hallo
使用final关键字禁止子类重写父类的方法
// The parent class has hello method that returns "beep".
class Car {
final public function hello()
{
return "beep";
}
}
class SportsCar extends Car {
public function hello()
{
return "Hallo";
}
}
$sportsCar1 = new SportsCar();
echo $sportsCar1 -> hello();
Fatal error: Cannot override final method Car::hello()
抽象类和抽象方法
抽象类是至少有一个抽象方法的类。抽象方法只能有名字和参数,没有其他的代码。因此,我们不能从抽象类中创建对象。
我们使用抽象类,但是我们只能确定方法的名字,而不是写这个方法的细节。
我们需要创建子类,将代码添加到子类方法体内的,最后使用这些子类创建对象。
抽象类用abstract关键字声明,并且包含抽象方法。
abstract class Car {
abstract public function calcNumMilesOnFullTank();
}
一旦在一个类中有一个抽象方法,这个类也必须是抽象的。
抽象类可以有非抽象方法。其实它甚至可以有属性。
abstract class Car {
protected $tankVolume;
public function setTankVolume($volume)
{
$this -> tankVolume = $volume;
}
// Abstract method
abstract public function calcNumMilesOnFullTank();
}
class Honda extends Car {
//因为我们继承了抽象类,所以我们需要在子类中定义它的抽象方法,
//并且向方法的主体添加代码
public function calcNumMilesOnFullTank()
{
$miles = $this -> tankVolume*30;
return $miles;
}
}
interface接口 - 类似于抽象类。
接口就像是在定义类的规范一样,实现接口的类,一定要包含接口中的方法。
定义好一个接口,那么和团队中其他工程师合作的时候,它们如果实现了你的接口,就必须在类里实现你在接口里定义好的方法。
interface Car {
public function setModel($name);
public function getModel();
}
class miniCar implements Car {
private $model;
public function setModel($name)
{
$this -> model = $name;
}
public function getModel()
{
return $this -> model;
}
}
一个类可以同时实现多个接口
interface Vehicle {
public function setHasWheels($bool);
public function getHasWheels();
}
class miniCar implements Car, Vehicle {
private $model;
private $hasWheels;
public function setModel($name)
{
$this -> model = $name;
}
public function getModel()
{
return $this -> model;
}
public function setHasWheels($bool)
{
$this -> hasWheels = $bool;
}
public function getHasWheels()
{
return ($this -> hasWheels)? "has wheels" : "no wheels";
}
}
抽象类和接口有什么区别?
我们看到抽象类和接口是相似的,因为它们提供了必须在子类中实现的抽象方法。但是,他们仍然有以下不同:
interface | abstract class | |
代码 |
|
|
可见性 |
|
|
同一个类可以同时实现多个接口 | 同一个类只能继承一个抽象类 |
php5不允许对基本数据类型(整数,浮点数,字符串和布尔值)进行参数预检
下面的代码仅仅在php7才是有效的
class car {
protected $model;
protected $hasSunRoof;
protected $numberOfDoors;
protected $price;
// string type hinting
public function setModel(string $model)
{
$this->model = $model;
}
// boolean type hinting
public function setHasSunRoof(bool $value)
{
$this->hasSunRoof = $value;
}
// integer type hinting
public function setNumberOfDoors(int $value)
{
$this->numberOfDoors = $value;
}
// float type hinting
public function setPrice(float $value)
{
$this->price = $value;
}
}
而php5和7都支持,对类类型的参数进行预检。
如下:
class Car {
protected $driver;
// The constructor can only get Driver objects as arguments.
public function __construct(Driver $driver)
{
$this -> driver = $driver;
}
}
class Driver {}
$driver1 = new Driver();
$car1 = new Car ($driver1);
类定义静态方法
有些时候,我们需要使用属性或者方法,但是没有必要创建一个对象。这个时候,就可以为类定义静态方法。
常见的使用场景: 可以使用静态变量和静态方法来计数。
class Utilis {
// 通过static关键字定义静态属性
static public $numCars = 0;
// 定义静态方法
static public function addToNumCars($int)
{
$int = (int)$int;
self::$numCars += $int;
}
}
// 设置静态属性
Utilis::$numCars = 3;
// 获取静态属性
echo Utilis::$numCars; // 3
// 调用静态方法
Utilis::addToNumCars(3);
echo Utilis::$numCars; // 6
使用场景2:可以使用静态方法来构建一个工具类
class Utilis {
// 使用php的header方法来让用户跳转到相应的url
static public function redirect($url)
{
header("Location: $url");
exit;
}
}
Utilis::redirect("http://www.notedeep.com");
trait
http://php.net/manual/zh/language.oop5.traits.php
可以让两个无关的类具有类似的行为。
自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。
Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。