zoukankan      html  css  js  c++  java
  • 静态类的原罪

    黑格尔有句名言:存在即合理。以此为论据的话,静态类的存在自然有其合理性。不过物极必反,一旦代码过于依赖静态类,其劣化的结局则不可避免。这就好比罂粟作为一种草本植物,有其在药理上的价值,但如果肆无忌惮的大量使用,它就变成了毒品。

      什么是静态类

      所谓静态类指的是无需实例化成对象,直接通过静态方式调用的类。代码如下:

    1. <?php  
    2.  
    3. class Math  
    4. {  
    5.     public static function ceil($value)  
    6.     {  
    7.         return ceil($value);  
    8.     }  
    9.  
    10.     public static function floor($value)  
    11.     {  
    12.         return floor($value);  
    13.     }  
    14. }  
    15.  
    16. ?> 

      此时类所扮演的角色更像是命名空间,这或许是很多人喜欢使用静态类最直接的原因。

      静态类的问题

      本质上讲,静态类是面向过程的,因为通常它只是机械的把原本面向过程的代码集合到一起,虽然结果是以类的方式存在,但此时的类更像是一件皇帝的新衣,所以可以说静态类实际上是披着面向对象的皮儿,干着面向过程的事儿。

      面向对象的设计原则之一:针对接口编程,而不是针对实现编程。这有什么不同?打个比方来说:抛开价格因素,你喜欢独立显卡的电脑还是集成显卡的电脑?我想绝大多数人会选择独立显卡。独立显卡可以看做是针对接口编程,而集成显卡就就可以看做是针对实现编程。如此说来针对实现编程的弊端就跃然纸上了:它丧失了变化的可能性。

      下面杜撰一个文章管理系统的例子来具体说明一下:

    1. <?php  
    2.  
    3. class Article  
    4. {  
    5.     public function save()  
    6.     {  
    7.         ArticleDAO::save();  
    8.     }  
    9. }  
    10.  
    11. ?> 

      Article实现必要的领域逻辑,然后把数据持久化交给ArticleDAO去做,而ArticleDAO是一个静态类,就好像焊在主板上的集成显卡一样难以改变,假设我们为了测试代码可能需要Mock掉ArticleDAO的实现,但因为调用时使用的是静态类的名字,等同于已经绑定了具体的实现方式,Mock几乎不可能,当然,实际上有一些方法可以实现:

    1. <?php  
    2.  
    3. class Article  
    4. {  
    5.     private static $dao = 'ArticleDAO';  
    6.  
    7.     public static funciton setDao($dao)  
    8.     {  
    9.         self::$dao = $dao;  
    10.     }  
    11.  
    12.     public static function save()  
    13.     {  
    14.         $dao = self::$dao;  
    15.  
    16.         $dao::save();  
    17.     }  
    18. }  
    19.  
    20. ?> 

      有了变量的介入,可以在运行时设定具体使用哪个静态类:

    1. <?php  
    2.  
    3. Article::setDao('MockArticleDAO');  
    4.  
    5. Article::save();  
    6.  
    7. ?> 

      虽然这样的实现方式看似解决了Mock的问题,但是首先它修改的原有的代码,违反了开闭原则,其次它引入了静态变量,而静态变量是共享的状态,有可能会干扰其它代码的执行,所以并不是一个完美的解决方案。

      补充说明,利用动态语言的特性,其实可以简单的通过require一个不同的类定义文件来实现Mock,但这样做同样有弊端,设想我们在脚本里需要多次变换实现方式,但实际上我们只有一次require的机会,否则就会出现重复定义的错误。

      注:某些情况下,利用静态延迟绑定也可以提高静态类的可测试性,参考PHPUnit。

      对象的价值

      如果放弃静态类,转而使用对象,应该如何实现文章管理系统的例子?代码如下:

    1. <?php  
    2.  
    3. class Article  
    4. {  
    5.     private $dao;  
    6.  
    7.     public function __construct($dao = null)  
    8.     {  
    9.         if ($dao === null) {  
    10.             $dao = new ArticleDAO();  
    11.         }  
    12.  
    13.         $this->setDao($dao);  
    14.     }  
    15.  
    16.     public function setDao($dao)  
    17.     {  
    18.         $this->dao = $dao;  
    19.     }  
    20.  
    21.     public function save()  
    22.     {  
    23.         $this->dao->save();  
    24.     }  
    25. }  
    26.  
    27. ?> 

      实际上,这里用到了人们常说的依赖注入技术,通过构造器或者Setter注入依赖的对象:

    1. <?php  
    2.  
    3. $article = new Article(new MockArticleDAO());  
    4.  
    5. $article->save();  
    6.  
    7. ?> 

      对象有自己的状态,不会发生共享状态干扰其它代码的执行的情况。

      …

      当然,静态类有好的一面,比如说很适合实现一些无状态的工具类,但多数时候,我的主观倾向很明确,多用对象,少用静态类,避免系统过早的固化。

  • 相关阅读:
    java Future模式的使用
    Objects源码解析
    VUE优秀的组件库总结
    数据库的一致性读,赃读,多线程与赃读,ACID,UNDO
    线程基础,多线程架构,高并发,线程安全基础知识
    程序员必备的开发利器
    spring security 实现登录验证码及记住我
    springboot 集成 spring security 自定义登录
    ELK整合SpringBoot日志收集
    ElasticSearch整合SpringBoot的API操作
  • 原文地址:https://www.cnblogs.com/cnsanshao/p/2152659.html
Copyright © 2011-2022 走看看