1.1 理解面向对象三大特点
1.1.1 封装
封装的概念
封装就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
简单理解:就好比一台笔记本电脑,它里面有cup ,网卡,显卡,硬盘等配件。这些东西并不是直接暴露给用户,而是提供一些按钮供用户使用这台电脑。其中部分配件不希望用户能够直接看到或者修改,可以 ‘’封装‘’ 起来。
封装特性
在一个对象产生之后,对象的部分成员属性和成员方法逻辑上是不允许在对象外部调用或者使用的。这就是封装特性的由来。封装关键字: private(私有的)。
比如有一个 NotebookComputer 类,其中成员属性 $cpu
和 成员方法 watchMovie()
是私有的。
<?php
class NotebookComputer
{
//成员属性
var $color = '白色';
var $brand = '外星人';
private $cpu = '英特尔第11代';
var $monitor = '8K 显示器';
function playGame()
{
echo '吃鸡! LOL !';
}
private function watchMovie()
{
echo '看各种颜色的电影 !';
}
}
$nc = new NotebookComputer();
var_dump($nc);
$nc->cpu;
//$nc->watchMovie();
在NotebookComputer 类的外部访问该类的私有成员属性和成员方法会报错。
注意:
- 被 private 修饰的成员属性不能够在类的外部进行访问,可以在类的内部访问。
- 被 private 修饰的成员方法也不能够在类的外部进行访问,可以在类的内部进行访问。
- var 和 private 修饰关键字只能选取一个(包括公共的: public 关键字和受保护的:protected 关键字)。
这里我们可以使用魔术方法__get()
来开个可以访问的后门,该方法在访问私有成员属性的时候会自动触发。
<?php
class NotebookComputer
{
//成员属性
var $color = '白色';
var $brand = '外星人';
private $cpu = '英特尔第11代';
var $monitor = '8K 显示器';
function playGame()
{
echo '吃鸡! LOL !';
}
private function watchMovie()
{
echo '看各种颜色的电影 !';
}
// 添加魔术方法 __get(),该方法有一个参数,该参数是属性名称
function __get($pro)
{
return $this->$pro;
}
}
$nc = new NotebookComputer();
var_dump($nc);
echo '<br/>';
echo $nc->cpu;
以上代码运行结果 :
可以看到没有报错并且访问到了私有属性 cpu。
注意:魔术方法
_get( )
- 触发时机: 访问私有成员属性的时候自动触发。
- 功能: 1. 为私有成员属性访问提供后门参数。 2. 屏蔽错误。
- 参数: 1个。访问的私有成员属性名称。
- 返回值: 可有可无。
一个类的私有成员属性是不能直接访问和修改的。上面我们讲到可以通过 __get()
方法实现访问私有属性,那可不可以修改私有属性呢?可以通过 __set 魔术方法来修改,该魔术方法是在设置(修改)成员属性的时候会自动触发。
<?php
class Person
{
//成员属性
var $name = '李莲英';
private $sex = '男'; //私有属性
var $age = 38;
//成员方法
function say()
{
echo '老佛爷吉祥';
}
function nn()
{
echo '哗啦啦啦啦啦~~~';
}
// 添加一个魔术方法,该方法有两个参数一个是私有属性名,一个是私有属性值。无返回值
function __set($pro_name,$new_val)
{
//通过$new_val拿到新的属性值:不男不女,给属性(sex)赋值
$this->$pro_name = $new_val;
}
}
//实例化一个对象
$lly = new Person;
var_dump($lly);
//直接访问私有成员属性会报错
//echo $lly->sex;
//设置(修改)私有成员属性
$lly->sex = '不男不女';
var_dump($lly);
以上代码运行结果 :
可以看到给私有属性 sex 重新赋值成功。
注意 :魔术方法
_set( )
触发时机: 对私有成员属性进行设置值得时候自动触发。
功能: 1. 为私有成员属性设置值。 2. 屏蔽错误。
参数: 2个。 第一个参数是私有成员属性名。第二个参数是要设置的新值。
返回值: 无。
1.1.2 继承
继承是指可以让某个类型的对象获得另一个类型的对象的属性和方法。
继承的语法格式 :
clas类名 extends 父类名 {
//书写子类独有的方法和书写
}
Father 类通过 extends 关键字继承 GrandFather 代码如下 :
<?php
//继承特性
//爷爷类
class GrandFather{
//成员属性
var $familyName = '曹';
var $lastName;
var $sex= '男';
//成员方法
function love()
{
echo '喜欢美女 !!!';
}
function drink()
{
echo '对酒当歌 !!!';
}
}
//父亲类
class Father extends GrandFather{
var $write = '7步成诗';
function run()
{
echo '我在狂奔!!!';
}
}
$fa = new Father();
var_dump($fa);
echo '<br/>';
$fa->drink();
以上代码运行结果 :
从上面结果中可以看到,Father 把 GrandFather 类中的属性和方法继承过来了,而且还可以有自己的属性 $write
和方法 run( )
。
有些概念我们需要理解清楚 :
父类: 被其它类继承的就是父类,也叫作基类。如上面案例中的:GrandFather 类。
子类: 继承其他类的类就是子类,也叫作派生类。如上面案例中的:Father 类。
注意:
子类继承父类既具备父类中所有的成员属性和方法(私有的除外)。
子类继承父类之后可以具有子类独有的属性和方法。
在继承时,子类中允许出现和父类同名的方法,这种情况不会冲突,而是子类的方法会覆盖父类的方法,这种方式叫做重载(重新加载)。
魔术方法也可以被子类继承。
如果父类具有构造方法,子类也需要构造方法进行构造成员属性,此时需要在子类中重载父类的构造方法,并且在构造方法中使用 parent : : ___construct( ) 进行调用父类的构造方法,构造继承而来的父类的成员属性。
继承代码的时候不要瞎继承,人类不要继承鸟类,不会变成鸟人。要有一定的逻辑关系。
PHP 语言是单继承,可以通过一些方式实现多继承。如:trait 。
1.1.3 多态
多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
多态是一种思想,可以简单的理解为:不同的人做相同的事得到不同的结果。
案例代码 :
<?php
//举栗子:妹子相亲,在相亲过程中可能会遇到不同的人,有屌丝,有高富帅...
//妹子
class Girl{
//约会方法
function meet(Man $obj)
{
$obj->kiss();
}
}
// 相亲对象 汉子类
abstract class Man{
// 必须实现kiss方法
abstract public function kiss();
}
//屌丝类 继承汉子类
class Loser extends Man{
//屌丝类的独有方法
public function aa()
{
echo '美女我们 AA 制吧 !';
}
//必须实现的方法
public function kiss()
{
echo '啪!!!反手一巴掌!';
}
}
//高富帅类 继承汉子类
class Gfs extends Man{
//高富帅类的独有方法
public function zj()
{
echo '献上 999 克拉钻戒 !';
}
//必须实现的方法
public function kiss()
{
echo '么么哒!!!爱你哟!';
}
}
//实例化
//妹子
$girl = new Girl();
//相亲对象
//屌丝
$loser = new Loser();
//高富帅
$gfs = new Gfs();
//开始相亲
$girl->meet($loser);
echo '<br/>';
$girl->meet($gfs);
以上代码运行结结果 :
从上面例子可以看出:不同的汉子在相亲的时候,得到的结果是不同的。这种思想就是多态,不同的人在做同一件事件得到不同的结果。
1.2 构建计算器
前台代码实现 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算器</title>
</head>
<body>
<form action="program.php" method="post">
输入数字A:<input type="text" name="numA" values=""><br>
运算符:<select name="strOperate" style="width: 160px">
<option value="+">+</option>
<option value="-">-</option>
<option value="*">x</option>
<option value="/">/</option>
</select><br>
输入数字B:<input type="text" name="numB" values=""><br>
<input type="submit" name="" value="提交">
</form>
</body>
</html>
后台计算功能代码实现 :
class Program
{
public function index()
{
$numA = $_POST["numA"];
$numB = $_POST["numB"];
$strOperate = $_POST["strOperate"];
$strResult = "";
switch ($strOperate) {
case '+':
$strResult = $numA + $numB;
break;
case '-':
$strResult = $numA - $numB;
break;
case '*':
$strResult = $numA * $numB;
break;
case '/':
$strResult = $numA / $numB;
break;
}
echo "结果是".$strResult;
}
}
在这里虽然功能实现了,但是这样的代码并不良好,如果我们在项目中另一个地方需要一个计算的功能,这个时候通常我们会选择直接复制,但这不是一个好的编码习惯,因为代码中重复的代码多到一定程度,维护时,可能就是一个大的麻烦,编程有一原则,就是用尽可能的办法去避免重复。
1.3 理解简单工厂模式
1. 什么是简单工厂模式?
简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于 23 种 GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
2. 简单工厂模式的特点
优点 :
工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化。
缺点 :
由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。
当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;这些缺点在工厂方法模式中得到了一定的克服。
3. 使用场景
工厂类负责创建的对象比较少。
客户只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心。
由于简单工厂很容易违反高内聚责任分配原则,因此一般只在很简单的情况下应用。
1.4 运用简单工厂模式重构计算器
在这里就可以创建一个 Operation 的计算类, 把计算的过程提取为计算类的方法 getResult ( ) 。
代码如下所示 :
class Operation
{
public static function getResult($numA, $numB, $strOperate)
{
$strResult = "";
switch ($strOperate) {
case '+':
$strResult = $numA + $numB;
break;
case '-':
$strResult = $numA - $numB;
break;
case '*':
$strResult = $numA * $numB;
break;
case '/':
$strResult = $numA / $numB;
break;
}
return $strResult;
}
}
客户端代码如下所示 :
class Client
{
public function index()
{
$numA = $_POST["numA"];
$numB = $_POST["numB"];
$strOperate = $_POST["strOperate"];
$operation = new Operation();
$strResult = $operation -> getResult($numA, $numB, $strOperate);
echo "结果是".$strResult;
}
}
$client = new Client();
try {
$client->index();
} catch (\Exception $e) {
echo "你输入的有误".$e;
}
上面我们虽然有面向对象的特性,但是只用到了一个特点封装,如果需求中需要增加一个开根 (sqrt) 运算,这个时候我们就需要修改 operation 类在 switch 上加一个分支。但是却需要让加减乘除的运算参与编译,如果在这个修改过程中一不小心改错了某一项的计算这就会对整个系统带来问题。在这里我们可以再把面向对象的另外两个特点(继承,多态)加进来。
工厂升级 :
在这里把所有运算方式看成为一个对象,把运算看成为所有运算方式的父类 Operation,这个类中有两个属性 $numA, $numB 代表的就是你要计算的值,同时可以提供一个 get set 的方法对计算的值进行获取和赋值,getResult()为运算方法,代码如下所示 :
// 运算类 的父类
abstract class Operation
{
## 运算类的两个属性
protected $numA = 0;
protected $numB = 0;
## 分别提供一个get,set的方法对属性的获取和设置
public function getNumA()
{
return $this->numA;
}
public function setNumA($numA = 0)
{
$this->numA = $numA;
}
public function getNumB()
{
return $this->numA;
}
public function setNumB($numB = 0)
{
$this->numB = $numB;
}
## 所有运算子类所需要实现的运算方法
public abstract function getResult();
}
子类加法类,减法类,乘法类,除法类等等,继承 Operation 运算类,实现父类中的计算方法。
代码如下所示 :
// 加法类
class OperationAdd extends Operation
{
## 实现父类的计算方法
public function GEtResult()
{
$result = $this->numA + $this->numB;
return $result;
}
}
// 减法类
class OperationSub extends Operation
{
## 实现父类的计算方法
public function GEtResult()
{
$result = $this->numA - $this->numB;
return $result;
}
}
// 乘法类
class OperationMul extends Operation
{
## 实现父类的计算方法
public function GEtResult()
{
$result = $this->numA * $this->numB;
return $result;
}
}
// 除法类
class OperationDiv extends Operation
{
## 实现父类的计算方法
public function GEtResult()
{
$result = $this->numA / $this->numB;
return $result;
}
}
简单工厂模式,也就是说到底要实例化谁,将来会不会增加实例化对象,比如增加开根运算,这是很容易变化的地方,应该考虑用一个单独的类来创造实例的过程,这就是工厂 。
创建一个简单工厂类 operationFactory,根据对应调用的方法创建对应的工厂类 。
代码如下所示 :
// 简单运算工厂类
class OperationFactory
{
## 根据需求创建对应的工厂类
public static function createOperate($operate)
{
$oper = null;
switch ($operate) {
case '+':
$oper = new OperationAdd();
break;
case '-':
$oper = new OperationSub();
break;
case '*':
$oper = new OperationMul();
break;
case '/':
$oper = new OperationDiv();
break;
}
return $oper;
}
}
客服端代码如下所示 :
class Client
{
public function index()
{
$numA = $_POST["numA"];
$numB = $_POST["numB"];
$strOperate = $_POST["strOperate"];
## 创建对应的计算类
$oper = OperationFactory::createOperate($strOperate);
$oper->setNumA($numA);
$oper->setNumB($numB);
$strResult = $oper->GEtResult();
echo "结果是".$strResult;
}
}
$client = new Client();
try {
$client->index();
} catch (\Exception $e) {
echo "你输入的有误".$e;
}
这样我们在添加新的算法的时候只需创建一个对应的运算方法的类,同时修改 switch 即可,这样就避免去操作过程中修改了其他的算法。
结果,如下图所示 :