主要内容:
- trait的功能
- 代码复用
- 在继承上下文中的应用
- 实现功能扩展
- 在trait组合中命名冲突的解决方案
- trait 和 interface的组合
- 抽象类中的static方法中的self实现(这部分内容是在第二天0724中的,但适合放到这块)
- 实战-抽奖及双色球(双色球这个案例挺无聊的)
1. trait的功能
1-1. 代码复用
<?php
// 使用trait将公共的show()提取出来
trait tDemo
{
public function show()
{
printf('<pre>%s</pre>', print_r(get_class_vars(__CLASS__), true));
//这个地方的class就是指这个class。get_class_vars就是获得这个class中所有的vars。
// __CLASS__: 返回当前类的名称字符串
// self: 返回的当前类的引用
}
}
class User1
{
use tDemo;
protected $name = '张三';
protected $gender = '男';
}
(new User1)->show();
echo '<hr>';
class User2
{
use tDemo;
protected $name = '小王';
protected $gender = '女';
}
(new User2)->show();
1-2. 在继承上下文中的应用
- 优先次序:子类 > trait > 父类
<?php
trait tDemo
{
// public static $name = 'trait中的属性';
public static function hello()
{
return 'trait中的方法' . __METHOD__;
}
}
// 基类/父类/超类
abstract class Dad
{
public static function hello()
{
return '基类中的方法' . __METHOD__;
}
}
// 子类/扩展类
class Son extends Dad
{
// 引入trait
// use tDemo;
// public static $name = '扩展类中的属性';
// public static function hello()
// {
// return '扩展类中的方法' . __METHOD__;
// }
}
echo Son::hello();
1-3. 实现功能扩展
<?php
trait tDemo1
{
// 打印所有属性
public function getProps()
{
printf('<pre>%s</pre>', print_r(get_class_vars(__CLASS__), true));
}
}
trait tDemo2
{
// 打印所有方法
public function getMethods()
{
printf('<pre>%s</pre>', print_r(get_class_methods(__CLASS__), true));
}
}
// 使用tDemo3来组合tDemo1和tDemo2这二个trait
trait tDemo3
{
use tDemo1, tDemo2;
}
class Work1
{
// 扩展这个类的功能
// 添加二个公共方法
// 1. 打印所有属性
// 2. 打印所有方法
use tDemo1, tDemo2;
public $name = '西瓜';
public $price = 2;
public function getInfo()
{
return $this->name . ': ' . $this->price;
}
}
echo (new Work1)->getInfo(), '<hr>';
echo (new Work1)->getProps();
echo (new Work1)->getMethods();
class Work2
{
// 此时我只需要引入一个trait就可以
use tDemo3;
public $name = '西瓜';
public $price = 2;
public function getInfo()
{
return $this->name . ': ' . $this->price;
}
}
echo (new Work2)->getInfo(), '<hr>';
echo (new Work2)->getProps();
echo (new Work2)->getMethods();
2. 在trait组合中命名冲突的解决方案
<?php
trait tDemo1
{
public function display()
{
return __METHOD__;
}
}
trait tDemo2
{
public function display()
{
return __METHOD__;
}
}
trait tDemo3
{
use tDemo1, tDemo2 {
// 给tDemo2::display()起个别名: td2
tDemo2::display as td2;
// 调用tDemo1::display()替换掉tDemo2::display()
tDemo1::display insteadOf tDemo2;
}
}
// 工作类尽可能写得代码清晰,简洁
class Work
{
use tDemo3;
}
echo (new Work)->display(), '<hr>';
// 别名访问tDemo2::display
echo (new Work)->td2();
3. trait 和 interface的组合
<?php
// 接口
interface iDemo
{
public static function index();
}
// trait
trait tDemo
{
// 将接口中的抽象方法的实现过程放在trait中实现,并在工作类中调用
public static function index()
{
return __METHOD__;
}
}
// 实现类
class Hello implements iDemo
{
use tDemo;
}
// 客户端
echo Hello::index();
4. 抽象类中的static方法中的self实现
- 有些绕口啊,其实就是part1中普通class中的方法上移到抽象类中,其中用self会报错。
这个时候需要将self换成static(前者的意思应该是绝对绑定的self,后者static则有相对绑定的意思—— 后期静态绑定)
self::总是与当前声明该方法(create)的类绑定,并不能与调用类(User/Product)绑定
- 声明类: CreateInstance(抽象类)
- 调用类: User, Product
- 解决: 将类的定义与类的调用完全分离
- 使用static: 后期静态绑定
- 前期: 声明
- 后期: 调用
static::关键字,可以自动与当前方法的调用类进行绑定
part1
<?php
abstract class CreateInstance
{
}
class User extends CreateInstance
{
// 创建当前类的实例
public static function create() : self
{
return new self();
}
}
class Product extends CreateInstance
{
// 创建当前类的实例
public static function create() : self
{
return new self();
}
}
// 生成User的实例
$user = User::create();
var_dump($user);
// 生成Product的实例
$product = Product::create();
var_dump($product);
- part2
<?php
abstract class CreateInstance
{
// 创建当前类的实例
public static function create() : self
{
// self::总是与当前声明该方法(create)的类绑定,并不能与调用类(User/Product)绑定
// 声明类: CreateInstance(抽象类)
// 调用类: User, Product
// 因此用下面这个就不行,需要用后面的static部分。
// return new self();
// 解决: 将类的定义与类的调用完全分离
// 使用static: 后期静态绑定
// 前期: 声明
// 后期: 调用
// static::关键字,可以自动与当前方法的调用类进行绑定
return new static();
}
}
class User extends CreateInstance
{
}
class Product extends CreateInstance
{
}
// 生成User的实例
// 当前调用create()方法的类是User,它不是抽象类
$user = User::create();
var_dump($user);
// 生成Product的实例
$product = Product::create();
var_dump($product);
// static: 之前用来声明静态成员, 现在还有可以与静态成员 的调用类进行绑定
5. 实战1: 抽奖程序
- 教程
<?php
// 实战: 双色球开奖的背景知识
// 抽象类 + 接口 + trait
// 奖品
$prizes = ['电脑', '手机', '平板', '耳机', '拖鞋', '口罩'];
interface iCreateId
{
public static function generateId($min, $max);
}
trait createId
{
// 生成一个唯一ID: 抽象方法的实现
public static function generateId($min, $max)
{
return mt_rand($min, $max);
}
}
// 开奖类
class DrawPrize implements iCreateId
{
// 当前接口中的抽象方法,放在了trait中实现,并引入到当前的工作类中
use createId;
// 发奖品
public static function award($prizes, $id)
{
return $prizes[$id];
}
}
// $id = DrawPrize::generateId(0, 5);
// $prize = DrawPrize::award($prizes, $id);
// printf('奖品是: <span style="color:red">%s</span>', $prize);
- 自己的部分
<?php
$prizes = ['钢笔','圆珠笔','铅笔','橡皮','手表','手机','宇宙飞船'];
interface iCreateID
{
public static function generateId($min,$max);
//这里是一个抽象方法,然后后面在trait中来实现。
//没有大括号实现的就是抽象方法。
}
trait createID
{
public static function generateID($min, $max)
{
return mt_rand($min, $max);
}
}
class DrawPrize implements iCreateID
{
use createID;
public static function award($prizes, $id)
{
return $prizes[$id];
}
}
$id = DrawPrize::generateId(0, 6);
$prize = DrawPrize::award($prizes, $id);
printf('一共有七个个奖品:<br>钢笔-圆珠笔-铅笔-橡皮-手表-手机-宇宙飞船<br><br>您的运气还不错,
本次中奖的奖品是:<br ><span style = "color:red">%s<span>', $prize);
6. 实战2:双色球
- 教程的部分
<?php
// 引入接口iCreateId, trait: createId
require 'demo6.php';//上一个抽奖就是demo6.php
// 抽象类: 彩票
abstract class Lottery implements iCreateId
{
// 在trait中实现接口中的抽象方法
use createId;
// 1. 生成中奖所需要的球的编号。红球、蓝球都用一个function。红球从33个中最终随机产生6个,篮球从16个中随机产生1个。
protected static function createBalls($min, $max, $num)
{
// ①. 按开奖规则生成指定步长与数量的球编号
$allBalls = range($min, $max, 1);
// ②. 根据取出的球的数量来区别当前是红球还是蓝球。
// 数量为1时取篮球。因此就是随机取1个即可。
if ($num === 1) return $allBalls[array_rand($allBalls)];
// ③. 取红球
return array_filter(array_rand($allBalls, $num), function ($key) use ($allBalls) {
// array_rand()返回的索引键名中有可能包括0,以及已经取出的key,需要过滤掉。
// $num为打算取出的数量。
//return array_key_exists($key, $allBalls);
// 添加 ($key > 0) 进行过滤
return ($key > 0) && array_key_exists($key, $allBalls);
}) ;
}
// 2. 生成一个双色球的中奖号
// red: [1,33, 6], blue: [1,16, 1]
abstract protected static function doubleColorBall(array $redRule, array $blueRule);
// 3. 随机生成一组试机号
abstract protected static function createRandBalls(array $redRule, array $blueRule, array $range);
}
// 实现类: 抽奖
class DrawLottery extends Lottery
{
// 生成一个双色球的中奖号
// red: [1,33, 6], blue: [1,16, 1]
public static function doubleColorBall(array $redRule, array $blueRule)
{
// 1. 生成红球的red: [1,33, 6]
$redBalls = self::createBalls(...$redRule);
sort($redBalls);
// 2.生成蓝球 blue: [1, 16, 1]
$blueBalls = self::createBalls(...$blueRule);
// 3. 将红蓝球进行组合生成一组中奖号码
array_push($redBalls, $blueBalls);
return $redBalls;
}
// 随机生成一组试机号
public static function createRandBalls(array $redRule, array $blueRule, array $range=[1, 10])
{
$count = self::generateId(...$range);
//...是将$range中的1-10进行了拓展。因为generateId(demo6中有)后面应该是($min,$max)。...发挥的作用其实就是这样的。
$randBalls = []; //用来接收收取出来的随机数字。
for ($i = 0; $i < $count; $i++) {
$randBalls[]=self::doubleColorBall($redRule, $blueRule);
}
//产生出随机数组。
return $randBalls;
}
}
$draw = DrawLottery::doubleColorBall([1,33,6], [1, 16, 1]);
$randBalls = DrawLottery::createRandBalls([1,33,6], [1, 16, 1], [1,6]);
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>模拟双色球</title>
<style>
.container1 {
display: grid;
grid-template-columns: repeat(7, 40px);
gap: 10px;
margin-top: 10px;
}
.container1 > .ball {
width: 40px;
height: 40px;
color: white;
font-weight: bold;
text-shadow: 1px 1px 1px #555;
border-radius: 50%;
box-shadow: 3px 3px 3px #888;
text-align: center;
line-height: 40px;
}
.container1 > .ball:nth-of-type(-n+6) {
background-color: red;
}
.container1 > .ball:last-of-type {
background-color: deepskyblue;
}
</style>
</head>
<body>
<h2>模拟双色球开奖</h2>
<h3>今日开奖号码: <small style="color: green"><?=date('Y年m月d日', time())?></small></h3>
<div class="container1">
<?php foreach ($draw as $item) : ?>
<span class="ball"><?=$item?></span>
<?php endforeach;?>
</div>
<hr>
<h3>今日试机号:</h3>
<!--将上面显示一组号码的代码再套一个外层循环就可以-->
<?php foreach ($randBalls as $draw): ?>
<div class="container1">
<?php foreach ($draw as $item) : ?>
<span class="ball"><?=$item?></span>
<?php endforeach;?>
</div>
<?php endforeach;?>
</body>
</html>