查看: 1514|回复: 0

[PHP代码] 深入 PHP 面向对象、模式与实践

发表于 2017-7-17 08:00:04
clone

需要操作原对象,但又不想影响原对象.

  1. $K_back = clone $K;
复制代码

基本数据类型和数组都为真复制,即为真副本,当属性为对象时,为假复制,改变副本仍会影响原对象.解决方案:

  1. //在原对象中添加
  2. function __clone(){
  3. $this->对象 = clone $this->对象
  4. }
复制代码

__clone 在clone前自动触发,可以执行一些在备份前的属性操作.

&

传递引用

方法引用传递,改变源对象

  1. function set_K(& $K){...}
  2. function & get_K(){...}
复制代码
static

延迟静态绑定

应用场景:Dog类和Person类都需要一个返回实例化的方法,Dog类和Person类都继承于Animal抽象类.

  1. abstract class Animal{
  2. public static function create(){
  3. //实例化调用类
  4. return new static();
  5. }
  6. }
  7. class Person extends Animal{...}
  8. //返回Person实例化类
  9. Person::create();
复制代码
拦截器 __get($property) ,访问未定义的属性时调用. __set($property,$value) ,给未定义的属性赋值时被调用. __isset($property) ,对未定义属性调用isset()方法时调用. __unset($property) ,对未定义属性调用unset()方法时调用. __call($method,$arg_array)

,调用未定义方法时调用.

__call很有用,但要慎用,因为太灵活.

应用场景:有一个专门打印Person类信息的Person_Writer类,如果通过Person类调用Person_Writer类.

  1. //Person委托Person_Writer类处理打印事务.
  2. class Person {
  3. private $writer;
  4. ...
  5. function __call($method_name,$args){
  6. if(methood_exists($this->wirter,$method_name)){
  7. return $this->writer->$method_name($this);
  8. }
  9. }
  10. //高级__call写法,当委托方法参数不确定时使用.
  11. function __call($method_name,$args){
  12. //当然这里这样写法意义不大,但是call一般都是用call_user_func_array调用
  13. $args = $this ;
  14. if(methood_exists($this->wirter,$method_name)){
  15. return call_user_func_array(
  16. array($this->writer,$method_name),$args);
  17. )
  18. }
  19. }
  20. }
复制代码
回调函数

应用场景: 3个类, Product类 , Product_Sale类 , Product_Totalizer类 ,要实现:当卖出Product总共价格超过指定金额时,输出警告.

  1. //Product
  2. class Product {
  3. public $name;
  4. public $price;
  5. }
  6. //Product_Sale
  7. class Product_Sale {
  8. private $callbacks;
  9. //记录回调函数
  10. function register_callback ($callback) {
  11. if(! is_callback($callback)){
  12. thow new Exception('callback not callable');
  13. }
  14. $this->callbacks[] = $callback;
  15. }
  16. //执行回调函数
  17. function sale ($product){
  18. print "{$product->name} : 处理中 n";
  19. foreach($this->callbacks as $callback){
  20. call_user_func($callback , $product);
  21. }
  22. }
  23. }
  24. //Produce_Totalizer
  25. class Produce_Totalizer {
  26. static function warn_amount ($amt) {
  27. $count = 0;
  28. return function ($produce) use ($amt , &count) {
  29. $count += $produce->price;
  30. print " count : {count}n"
  31. if($count>$amt){
  32. print "超过指定金额{$amt}啦~";
  33. }
  34. };
  35. }
  36. }
  37. //模拟场景
  38. $product_sale = new Produce_Sale();
  39. //指定报警金额为8块
  40. $product_sale = register_callback(Produce_Totalizer::warn_amount(8));
  41. //卖商品
  42. $product_sale->sale(new Product("Durex",6));
  43. $product_sale->sale(new Produce("Jissbon",5));
  44. //输出结果
  45. Durex : 处理中
  46. count :6
  47. Jissbon : 处理中
  48. count: 11
  49. 超过指定金额8块啦~
复制代码
get_class() 和 instanceof

get_class(类) 用于判断是否精准等于类名;

instanceof 可以判断是否其本身或继承于某父类.

类中的方法和类中的属性

get_class_methods('类名') :获取类中所有方法.

get_class_vars('类名') :获取类中所有public参数;

反射API 2 模式 2.1 组合

问题:课堂类被演讲类和研讨会类继承着.但是演讲类和研讨类都要实现一次性计费和上N次课计费的方法.和输出计算的方式.

解决方案1: 在课堂类中添加计算一次性付费的方法,上N次课的计费方法和输出计算方式的方法.

解决方案2: 运用组合,将处理计费和输出计算方式单独封装为一个计费策略类.

  1. abstract class Cost_Strategy {
  2. protected $duration;
  3. abstract function cost ();
  4. abstract function charge_type();
  5. public __construct($duration){
  6. $this->duration = $duration;
  7. }
  8. }
  9. class Timed_Const_Strategy extends Cost_Stratedy {
  10. function cost () {
  11. //上一次课给5块钱- -.
  12. return $this->duration * 5;
  13. }
  14. function charge_type(){
  15. return "多次课结算";
  16. }
  17. }
  18. class Fixed_Const_Strategy extends Cost_Stratedy {
  19. function cost (){
  20. return 30 ;
  21. }
  22. function charge_type(){
  23. return "一次性课结算";
  24. }
  25. }
  26. abstract class Leason {
  27. private $cost_strategy;
  28. public __construct(Const_Strategy $cost_strategy){
  29. $this->cost_strategy = $cost_strategy;
  30. }
  31. function __call($method_name,$args){
  32. $args = $cost_strategy ;
  33. if(methood_exists($this->cost_strategy,$method_name)){
  34. return call_user_func_array(
  35. array($this->writer,$method_name),$args);
  36. )
  37. }
  38. }
  39. }
  40. //运用
  41. $leasons[] = new Seminar(new Timed_Const_Strategy(4));
  42. $leasons[] = new Lecture(new Fixed_Const_Strategy(null));
  43. foreach ($leasons as $leason){
  44. print "leason charge : {$leason->const()}";
  45. print "charge_type : {$leason->charge_type()}"
  46. }
  47. leason charge 20. charge_type : 多次课结算;
  48. leason charge 30. charge_type : 一次课结算;
复制代码

组合既委托.同级委托.

继承既父子关系.

3 生成对象 3.1 单例模式

确保系统中只有唯一一个用例.例如系统配置文件.

重点

1: 构造方法私有.

2: 类本身包含自己的实例化属性.

  1. class Preferences {
  2. private static $instance;
  3. private function __construct(){ ... }
  4. public static function get_instance(){
  5. if(empty(self::$instance)){
  6. self::$instance = new Preferences();
  7. }
  8. return self::$instance;
  9. }
  10. ...
  11. }
  12. //使用
  13. $preferences = Preferences::get_instance();
复制代码
3.2 工厂模式

通过一个父类,生产处多个不同功能的子类.

特点:产品方(新浪微博)和需求方(显示新浪微博)一一对应.

问题:印象笔记中,来源可能为新浪微博,或者开发者头条,在印象笔记显示的时候,两者的页眉和页尾是不一样的.

3.3 抽象模式

RLGL!!!.印象笔记不只要显示新浪微博内容!!!还要显示我的新浪账号,还要该微博啊

!!卧槽

~憋着急,吻我.

工厂模式主要用于生产一一对应的产品方和需求方,而抽象模式要做的是一个需求方(印象笔记_显示新浪微博),要多个工厂(把需求方抽象为多个需求方),例如提供新浪内容的工厂,提供新浪账号的工厂.提供微博内容的评论的工厂等.

代码:

  1. abstract class Show_Evernote {
  2. abstract function get_header_text();
  3. abstract function get_context();
  4. abstract function get_footer_text();
  5. abstract function get_user();
  6. abstract function get_comment();
  7. }
  8. class 显示新浪微博 extends Show_Evernote{
  9. function get_header_text(){...};
  10. function get_context(){new 新浪微博_内容;}
  11. function get_footer_text(){...};
  12. function get_user(){new 新浪微博_账号 ;}
  13. function get_comment(){new 新浪微博_评论;}
  14. }
  15. //使用
  16. 印象笔记控件类->内容 = 显示新浪微博->get_context;
  17. 印象笔记控件类->账号 = 显示新浪微博->get_context;
  18. ...
复制代码
3.4 平行模式

当使用工厂/抽象模式必须要制定具体的创建者(需求方).

平行模式和抽象模式的模型图一致,但代码实现不一样.

抽象模式中父类均为抽象类,而平行模式中,所以类都为普通类,方便父类的实例化.

在这里列出显示印象笔记类的实现代码

  1. class Show_Evernote{
  2. private $内容;
  3. private $账号;
  4. private $评论;
  5. function __construct(内容,账号,评论){
  6. $this->内容 = 内容;
  7. $this->账号 = 账号;
  8. $this->评论 = 评论;
  9. }
  10. function get_内容(){
  11. return clone $this->内容);
  12. }
  13. function get_账号(){
  14. return clone $this->账号);
  15. }
  16. function get_评论(){
  17. return clone $this->评论;
  18. }
  19. }
  20. //使用
  21. $factory = new Show_Evernote(
  22. new 新浪微博内容(),
  23. new 新浪微博账号(),
  24. new 新浪微博评论()
  25. );
  26. 印象笔记控件类->显示印象笔记 = $factory;
复制代码

其实大家可以发现,原型模式只不过只在最顶层类中包装了一下各组件子类而已,然而这样可以轻松的组合他们,例如实现一个显示新浪微博内容,但要显示开发者头条账号的需求?

4 使用对象 4.1 组合模式

组合模式,可以理解为单一对象管理组合对象(聚合组件),最终组合体下的各个组合部件最好类型一致.不然特殊性越多,需要判断就越多.

假设捶背男,洗脚男,洗发男,用来服务一个人(妹子).

假设妹子的几个部位可用的服务男均为无限个.

  1. //创建一个妹子
  2. $妹子 = new 人();
  3. //添加洗脚男、捶背男
  4. $妹子->add_man(new 洗脚男);
  5. $妹子->add_man(new 捶背男);
  6. //循环所有男的给予舒服的方法.
  7. $妹子->计算舒服程度();
复制代码

这是一个很理想的组合模式,在现实情况,我们使用组合模式,可能不得不创建多种类型的洗脚男,需要添加许多判断条件.

4.2 装饰模式

装饰模式,首先洗脚男,洗发男,捶背男都是人,但是如果,一个男的又捶背,又洗发,这怎么玩?. add_man 两次?这不科学吧,来给这些男的装饰一下吧~

  1. abstract class 人{
  2. ...
  3. abstract function get_well();
  4. }
  5. class 男 extends 人 {
  6. //无论你是神马男,服务你,你就能获得10点舒服度.
  7. private $well = 10;
  8. function get_well(){
  9. return $this->well();
  10. }
  11. }
  12. abstract class 装饰男类型 extends 人 {
  13. protected $人;
  14. function __construct(人 $人){
  15. $this->人 = $人;
  16. }
  17. }
  18. class 捶背装饰 extends 类型男装饰{
  19. function get_well(){
  20. return $this->人->get_well()+30;
  21. }
  22. }
  23. class 洗发装饰 extends 类型男装饰{
  24. function get_well(){
  25. return $this->人->get_well()+20;
  26. }
  27. }
  28. class 洗褪装饰 extends 类型男装饰{
  29. //老子不喜欢别人碰我的毛裤.
  30. function get_well(){
  31. return $this->人->get_well()-20;
  32. }
  33. }
  34. //创建捶背,能给予的舒服指数 - -嘻嘻.
  35. $人 = new 捶背装饰(new 男);
  36. $人->get_well(); // 10+30 = 40
  37. //来来来,全能选手,捶背、洗发、洗腿一起来
  38. $人 = new 洗脚装饰(new 洗发装饰(new 捶背装饰(new 男()))); //10+30+20-20 = 40,注意顺序,由里到外执行.
复制代码

装饰模式,既(组合+继承),基类方法一定要尽量少,不然子类可能有它不该有的方法.直接类继承,她只可能是一种形态,而她的多种形态可能一并拥有的时候,应该运用组合.

继承即单一多态,组合既多种多态.

这个例子中,你可以添加女,然后把装饰男类型改为装饰通用类型,但每个get_well()都要多一个判断是男还是女(如果给予的舒服程度不一样).

这只是确保不可能出现在 男 , 女 之外的第三种人,如果基类为动物,给予服务的可能是鸡,鹅,鸭,那么装饰类型应该运用工厂模式,动物形态和装饰形态一一对应.方便拓展.

除了服务类型,服务男的样子也很重要,这就多了一种装饰,现在有 装饰男类型 和 相貌男类型 ,这种情况怎么破,其实类似.

  1. //如何获取捶背的帅哥麦?,
  2. $人 =new 男类型(new 捶背(new 帅哥麦(new 男())));
复制代码
4.3 外观模式

即给外部系统提供清晰接口

例如当Model层写得很混乱,但是里面的方法还能用,那我们的Controller层应该列举一些清晰的访问方法来供View层访问.外观模式,强调的是清晰的访问接口.

5 执行任务 5.1 策略模式

给类添加功能.对象要显式的调用它.

继续刚才的洗脚男和人的故事吧…你丫的爽完了要给钱吧?支付宝?微信?现金?

这个付款方式有多种,实现方法不应该放在 人 类中,而是应该委托给别的类

  1. abstract class 人 {
  2. protectd $支付方式;
  3. function set_支付方式(){...}
  4. function 付款(金额){
  5. return $支付方式->付款($金额);
  6. }
  7. }
  8. abstract class 付款{
  9. abstract function 付款($金额);
  10. }
  11. class 支付宝付款 extends 付款{
  12. function 付款($金额){
  13. return 外接支付宝付款流程($金额);
  14. }
  15. }
  16. ...
  17. //使用
  18. $男 =new 男();
  19. ///爽爽爽
  20. ...
  21. //结账
  22. $支付宝支付账单 = new 支付宝付款($金额);
  23. $人 = new 男();
  24. $人->set_支付方式(new 支付宝付款());
  25. $人->付款();
复制代码
5.2 观察者模式

当被观察者发生变化,观察者需要被通知.

当数据发生变化,页面需要被通知.

使用步骤:

观察者加载到被观察者中. 被观察者通知观察者.

例如登陆类(被观察)状态改变,要出发邮件系统和日志系统(观察者)

  1. interface 被观察者{
  2. function attach(观察者);
  3. function detatch(观察者);
  4. function notify();
  5. }
  6. class Login implements 被观察者{
  7. private $观察者;
  8. function __construct(){
  9. $this->观察者 = array();
  10. }
  11. function attach($观察者){
  12. $this->观察者 = $观察者;
  13. }
  14. function detach($观察者){
  15. //删除某个观察者的操作;
  16. }
  17. function notify(){
  18. foreach ($this->观察者 as $单个观察者){
  19. $单个观察者->update($this);
  20. }
  21. }
  22. }
  23. interface 观察者{
  24. function update(被观察者);
  25. }
  26. abstract class Login_观察者 implements 观察者{
  27. private $login;
  28. function __construct (Login $login){
  29. $this->login = $login;
  30. $login->attach($this);
  31. }
  32. function update(观察者 $观察者){
  33. if ($观察者 ===$this->login){
  34. $this->do_update($观察者);
  35. }
  36. }
  37. abstract function do_update(Login $login);
  38. }
  39. class 邮件观察者 extends 登陆观察者 {
  40. function do_update(Login $login){
  41. //判断条件 发送邮件
  42. }
  43. }
  44. class 日志观察者 extends 登陆观察者 {
  45. function do_update(Login $login){
  46. //判断条件 记录到日志;
  47. }
  48. }
  49. //使用
  50. $login = new Login();
  51. new 邮件观察者 ($login);
  52. new 日志观察者 ($login);
复制代码

PHP有内置的SPL实现上述的观察者模式.

5.3 访问者模式

问题: 在一个军队中,有很多军队,军队下面可能包含军队/步兵/弓箭手,这时我们要显示一个军队的战斗力/需要粮食的各级分配?(遍历对象并设置显示方法).怎么办?.解决办法是军队还是保存自己的基本信息,设置一个访问者,访问者包含总战斗力方法和总粮食的方法.

访问者

  1. abstract class 军队访问者{
  2. abstract function 访问(单元);
  3. function 访问军队($军队){
  4. $this->访问($军队);
  5. }
  6. function 访问弓箭手($弓箭手){
  7. $this->访问($弓箭手);
  8. }
  9. //这里重复定义了大量代码,其实可以用call来替代
  10. function __call($method_name,$args){
  11. if(strrpos($method_name, "访问")){
  12. return call_user_func_array(
  13. array($this,"访问"),$args
  14. );
  15. }
  16. }
  17. }
  18. class 军队战斗力访问者 extends 军队访问者{
  19. private $text="";
  20. function 访问($单元){
  21. $ret = "";
  22. $pad = 4*$单元->getDpth(); //设置显示深一级前面多4个空格.
  23. $ret .= sprintf( "%{$pad}s","");
  24. $ret .= get_class($单元). ": ";
  25. $ret .= "战斗力: " .$单元->bombardStrenth()."n";
  26. $this->text .=$ret;
  27. }
  28. function get_text(){
  29. return $this->text;
  30. }
  31. }
复制代码

被访问者

  1. abstract class 单元{
  2. function 接受($军队访问者){
  3. $method = "访问_".get_class($this);
  4. $军队访问者->$method($this);
  5. }
  6. private $depth;
  7. protected function set_depath($depth){
  8. $this->depth=$depth;
  9. }
  10. function get_depth(){
  11. return $this->depth;
  12. }
  13. ...
  14. }
  15. abstract class 综合单元 extends 单元{
  16. function 接受($军队访问者){
  17. parent::接受($军队访问者)
  18. foreach($this->单元集合 as $this_unit){
  19. $this->unit->接受($军队访问者);
  20. }
  21. }
  22. }
  23. class 军队 extends 综合单元{
  24. function bombardStrenth(){
  25. $ret =0;
  26. foreach($this-units() as $unit){
  27. $ret += $unit->bombardStrenth();
  28. }
  29. return $ret
  30. }
  31. }
  32. class 弓箭手 extends 单元{
  33. function bombardStrenth(){
  34. return 4;
  35. }
  36. }
复制代码

调用

  1. $main_army = new Army();
  2. $main_army->add_unit(new 步兵());
  3. $main_army->add_unit(new 弓箭手());
  4. $军队战斗力访问者_实例 =new 军队战斗力访问者();
  5. $main_army->接受(均分战斗力访问者);
  6. print $军队战斗力访问者->get_text();
复制代码

输出

  1. 军队: 战斗力: 50
  2. 步兵: 攻击力 :48
  3. 弓箭手: 攻击力: 4
复制代码
5.4 命令模式

例子为Web页面的login和feed_back,假如都需要使用ajax提交,那么问题来了,将表单封装好提交上去,得到了返回结果.如何根据返回结果跳转不同的页面?.

有些同学就说了,login和feed_back各自写一个方法憋,提交的时候调用各自的方法.

然后再来个logout命令..增加..删除..命令怎么办..

命令模式比较适合 命令执行 例如登陆,反馈等简单只需要判断是否成功的任务

命令:

  1. abstract class Command{
  2. abstract function execute(Conmmand_Context $context);
  3. }
  4. class Login_Command extends Command{
  5. function execute(CommandContext $context){
  6. $managr =Register::getAccessManager();
  7. $user = $context->get("username");
  8. $pass = $context->get('pass');
  9. $user_obj = $manager->login($user,$pass);
  10. if(is_null($user_obj)){
  11. $context->setError($manager->getError());
  12. return false;
  13. }
  14. $context->addParam("user",$user_obj);
  15. return true;
  16. }
  17. }
复制代码

部署命令的调用者

  1. class Command_Facotry{
  2. public function get_command($action){
  3. $class = UCFirst(strtolower($action))."_Command";
  4. $cmd = new $class();
  5. return $cmd;
  6. }
  7. }
复制代码

客户端

  1. class Controller{
  2. private $context;
  3. function __construct(){
  4. //Command_Context主要用来存储request和params
  5. $this->context =new Command_Context();
  6. }
  7. function process(){
  8. $cmd Command_Factory::get_commad($this->context->get('action'));
  9. if(!$cmd-execute($this->context)){
  10. //错误处理
  11. }else{
  12. //成功 分发视图
  13. }
  14. }
  15. }
复制代码

使用

  1. $controller =new Controller();
  2. $context = $controller->get_context();
  3. $context->add_param('action','login');
  4. $context->add_param('username','404_k');
  5. $context->add_param('pass','123456');
  6. $controller->process();
复制代码

来自: http://blog.jobbole.com/97315/



回复

使用道具 举报