找回密码
 加入怎通
查看: 436|回复: 2

[其他] 静态类的原罪

[复制链接]
寻路者 发表于 2013-01-06 16:05:51 | 显示全部楼层 |阅读模式
黑格尔有句名言:存在即合理。以此为论据的话,静态类的使用必然有其合理性。不过物极必反,一旦代码过于依赖静态类,其劣化的解决则不可避免。这就好比罂粟作为一种草本植物,有其在药理上的价值,但如果肆无忌惮的大量使用,它就变成了毒品。
$ k6 J- C  y4 ~' F/ g8 a
- e. m( i0 q4 T8 p  什么是静态类
+ K1 A7 a* M( g" F5 Y0 C+ R$ W* |' ]. d- t
  所谓静态类指的是无需实例化成对象,直接通过静态方式调用的类。代码如下:% G+ H- A- z/ z3 k3 N& L
/ a2 ~# ?" V, x
  class Math { public static function ceil($value) { return ceil($value); }% C5 R/ h5 ]- X
" s, E1 J5 N, z; J9 V+ e
  public static function floor($value) { return floor($value); } }
0 b! a, }- e% Z- l9 B+ _9 I/ ~" w3 _/ x- S0 G' {
  ?>8 f7 I! ?5 @. ?& C7 S1 [# F

' \1 r# ]  M+ m: O  此时类所扮演的角色更像是命名空间,这或许是很多人喜欢使用静态类最直接的原因。. k2 |2 ^. ~6 |

2 H" s) I: u' A5 S& {# F7 M5 B: Y  静态类的问题  h' K9 p# V$ Z, j8 m9 o
, M% ], s' |% W$ `. A
  本质上讲,静态类是面向过程的,因为通常它只是机械的把原本面向过程的代码集合到一起,虽然结果是以类的方式存在,但此时的类更像是一件皇帝的新衣,所以可以说静态类实际上是披着面向对象的壳儿,干着面向过程的事儿。
) ^% }; H9 ]0 O8 k# B% F9 i" X, V' C' Y' A
  面向对象的设计原则之一:针对接口编程,而不是针对实现编程。这有什么不同?打个比方来说:抛开价格因素,你喜欢独立显卡的电脑还是集成显卡的电脑?我想绝大多数人会选择独立显卡。独立显卡可以看做是针对接口编程,而集成显卡就就可以看做是针对实现编程。如此说来针对实现编程的弊端就跃然纸上了:它丧失了变化的可能性。
- s- t6 F0 F" y3 p; r: k/ o. p" Q1 s/ N
  下面杜撰一个文章管理系统的例子来具体说明一下:. W0 s' ~8 r0 T7 g8 Z3 T, |

6 X, Z" D7 G( f2 [- k. m6 A" [; y  class Article { public function save() { ArticleDAO::save(); } }
+ x: V* c+ G8 X- d# Z/ m0 g: N; i0 C2 \8 Z: `% n! v
  ?># W, {, m% C' c9 G4 e

2 c# K: q& A5 C7 z+ A+ Y2 s  Article实现必要的领域逻辑,然后把数据持久化交给ArticleDAO去做,而ArticleDAO是一个静态类,就好像焊在主板上的集成显卡一样难以改变,假设我们为了测试代码可能需要Mock掉ArticleDAO的实现,但因为调用时使用的是静态类的名字,等同于已经绑定了具体的实现方式,Mock几乎不可能,当然,实际上有一些方法可以实现:
0 h' u4 u1 ?; J; q/ I2 W5 \5 M; P4 I" b8 c; l1 I3 ]7 x
  class Article { private static $dao = 'ArticleDAO';
3 q0 i( l9 f% C- t& C( ^
, P4 A9 h; g# Z/ z. d  public static funciton setDao($dao) { self:dao = $dao; }6 a5 }& N* Q: i& H

1 e' V1 m7 Y# T( w  public static function save() { $dao = self:dao;
, ~" g4 w0 R* K$ I" h5 V. V# c, K1 D; b% M$ e
  $dao::save(); } }: E3 T9 Z+ V5 O4 Q8 w& i
. r8 Z- \* C7 n5 C
  ?>
7 M/ r' O2 T: b! h) V; D5 v! N1 l# `4 Y3 v- v; K# O
  有了变量的介入,可以在运行时设定具体使用哪个静态类:- |$ ]' a0 E, L4 T9 E3 y, t2 ~

* P$ o) v4 s, @, M  Article::setDao('MockArticleDAO');
& ^) v! {3 }& F+ T0 w; l; j$ g2 b
4 \/ i8 I4 q1 S/ J% @# T( P) S  Article::save();
9 s6 g( @) f1 r: D+ b
4 m6 i. U  s1 t! T: b  ?>
2 G7 ~5 b: M& D' o
: I6 X" `6 U  l7 J  虽然这样的实现方式看似解决了Mock的问题,但是首先它修改的原有的代码,违反了开闭原则,其次它引入了静态变量,而静态变量是共享的状态,有可能会干扰其它代码的执行,所以并不是一个完美的解决方案。
3 G( ~# X  G4 B( m  X0 o4 g# b
% `0 H' e; A* h, P0 S  补充说明,利用动态语言的特性,其实可以简单的通过require一个不同的类定义文件来实现Mock,但这样做同样有弊端,设想我们在脚本里需要多次变换实现方式,但实际上我们只有一次require的机会,否则就会出现重复定义的错误。* ]$ S7 P  v  T5 ?# w% o# V% s

# \# |# M# ?4 d, M( @; w  注:某些情况下,利用静态延迟绑定也可以提高静态类的可测试性,参考PHPUnit。
8 G  Z  n, y) P1 B/ E
( x  ^* e4 ~. r3 V; w, g( V+ S! U  对象的价值
& l3 f9 ]+ P! g. o8 d! T: c; n8 k; E; X. L0 ?
  如果放弃静态类,转而使用对象,应该如何实现文章管理系统的例子?代码如下:# {' Q2 l' l$ Q3 Z" D

' s. m; Z3 d0 b  class Article { private $dao;$ V5 a2 A0 |: A% H$ j+ I

. d1 g% P7 w- E  public function __construct($dao = null) { if ($dao === null) { $dao = new ArticleDAO(); }' ~" G0 e( E- O( o5 H

! S  r/ e8 p  O( i. P" A  $this->setDao($dao); }2 ?6 v5 u. S: ^/ k& K7 V
' X4 ^# \# _+ ]% D: t9 r3 Q% e9 _
  public function setDao($dao) { $this->dao = $dao; }
' ~7 |5 x5 I  h. j" \9 i1 \8 I6 e8 h% W6 Z2 _; W1 z% n/ E7 Y  A
  public function save() { $this->dao->save(); } }
2 W+ g1 z- F; O. I' K& K! C
3 S, r, v* v* _: p  ?>/ Y5 M+ @  t# G' u& y  a+ f

2 E2 m3 }3 P# Z  实际上,这里用到了人们常说的依赖注入技术,通过构造器或者Setter注入依赖的对象:/ f/ C: c0 W6 q3 H% A5 H& S

8 S' B! C  S" U+ A, ^9 Q) R& R  $article = new Article(new MockArticleDAO());4 ^- v* E' S7 o+ z+ x6 u& E4 W

; v' x/ a4 g' q1 L0 K  $article->save();
$ ]2 q; W- S6 O5 h' E0 F0 [) t5 }9 J  u( ]
  ?>7 J" s; W, a( n; r- ]3 Q5 `

- z( D5 D2 k) r/ l4 S( W& e& ]  对象有自己的状态,不会发生共享状态干扰其它代码的执行的情况。; d# l- I: ~7 A* A
6 x6 W& j- |  w4 @& C9 V7 }
  …. G- P3 i" C, b8 S3 W

$ O) S' b# S' S9 `3 G' e1 l  当然,静态类有好的一面,比如说很适合实现一些无状态的工具类,但多数时候,我的主观倾向很明确,多用对象,少用静态类,避免系统过早的固化。顺便说一句,希望别有人告诉我静态类比对象快之类的说教,谢谢。

暂时无法加载帖子列表

回复

使用道具 举报

清心凉神 发表于 2026-09-18 22:32:32 | 显示全部楼层
刚好遇到类似问题,看完这个帖子心里有底了
回复

使用道具 举报

skdfjs2 发表于 2026-09-18 23:44:47 | 显示全部楼层
分析得很透彻,很多细节都说到点子上了~
回复

使用道具 举报

    您需要登录后才可以回帖 登录 | 加入怎通

    本版积分规则

    QQ|手机版|小黑屋|网站地图|真牛社区 ( 苏ICP备2023040716号-2 )

    GMT+8, 2026-9-18 23:49 , Processed in 0.045741 second(s), 26 queries , Gzip On.

    免责声明:本站信息来自互联网,本站不对其内容真实性负责,如有侵权等情况请联系420897364#qq.com(把#换成@)删除。

    Powered by Discuz! X3.5

    快速回复 返回顶部 返回列表