B 树、B- 树、B+ 树和B* 树

B

即二叉搜索树:
1. 所有非叶子结点至多拥有两个儿子(LeftRight);
2. 所有结点存储一个关键字;
3. 非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树;
如:


B树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中;否则,如果查询关键字比结点关键字小,就进入左儿子;如果比结点关键字大,就进入右儿子;如果左儿子或右儿子的指针为空,则报告找不到相应的关键字;
如果B树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么B树的搜索性能逼近二分查找;但它比连续内存空间的二分查找的优点是,改变B树结构(插入与删除结点)不需要移动大段的内存数据,甚至通常是常数开销;
如:


B树在经过多次插入与删除后,有可能导致不同的结构:


右边也是一个B树,但它的搜索性能已经是线性的了;同样的关键字集合有可能导致不同的树结构索引;所以,使用B树还要考虑尽可能让B树保持左图的结构,和避免右图的结构,也就是所谓的平衡问题;
实际使用的B树都是在原B树的基础上加上平衡算法,即平衡二叉树;如何保持B树结点分布均匀的平衡算法是平衡二叉树的关键;平衡算法是一种在B树中插入和删除结点的策略;

B-

是一种多路搜索树(并不是二叉的):
1. 定义任意非叶子结点最多只有M个儿子;且M>2
2. 根结点的儿子数为[2, M]
3. 除根结点以外的非叶子结点的儿子数为[M/2, M]
4. 每个结点存放至少M/2-1(取上整)和至多M-1个关键字(至少2个关键字);
5. 非叶子结点的关键字个数=指向儿子的指针个数-1
6. 非叶子结点的关键字:K[1], K[2], , K[M-1];且K[i] < K[i+1]
7. 非叶子结点的指针:P[1], P[2], , P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
8. 所有叶子结点位于同一层;
如(M=3):


B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;
B-树的特性:
1. 关键字集合分布在整颗树中;
2. 任何一个关键字出现且只出现在一个结点中;
3. 搜索有可能在非叶子结点结束;
4. 其搜索性能等价于在关键字全集内做一次二分查找;
5. 自动层次控制;
由于限制了除根结点以外的非叶子结点,至少含有M/2个儿子,确保了结点的至少利用率,其最底搜索性能为:


其中,M为设定的非叶子结点最多子树个数,N为关键字总数;
所以B-树的性能总是等价于二分查找(与M值无关),也就没有B树平衡的问题;
由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占M/2的结点;删除结点时,需将两个不足M/2的兄弟结点合并;

B+

B+树是B-树的变体,也是一种多路搜索树:
1. 其定义基本与B-树同,除了:
2. 非叶子结点的子树指针与关键字个数相同;
3. 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
5. 为所有叶子结点增加一个链指针;
6. 所有关键字都在叶子结点出现;
如(M=3):


B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;
B+的特性:
1. 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
2. 不可能在非叶子结点命中;
3. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
4. 更适合文件索引系统;

B*

B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针:

B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);
B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;
B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;
所以,B*树分配新结点的概率比B+树要低,空间使用率更高;

小结

B树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于走右结点;
B-树:多路搜索树,每个结点存储M/2M个关键字,非叶子结点存储指向关键字范围的子结点;
所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;
B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;
B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3

详解 B-Tree 与B+Tree

 1、什么是B-TreeB-tree又叫平衡多路查找树。

一棵m阶的B-tree (m叉树)的特性如下:

    1) 树中每个结点至多有m个孩子;

    2) 除根结点和叶子结点外,其它每个结点至少有[m / 2]向上取整个孩子;

    3) 若根结点不是叶子结点,则至少有2个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点);

    4) 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部结点或查询失败的结点,实际上这些结点不存在,指向这些结点的指针都为null);

    5) 每个非终端结点中包含有n个关键字信息: (n,P0,K1,P1,K2,P2,……,Kn,Pn)。其中:a)   Ki (i=1…n)为关键字,且关键字按顺序排序K(i-1)< Ki。         b)   Pi为指向子树根的接点,且指针P(i-1)指向子树中所有结点的关键字均小于Ki,但都大于K(i-1)。         c)   关键字的个数n必须满足: [m / 2]-1 <= n <= m-1。    

备注:在此处[ ]中的运算表示向上取整下面是B-Tree的结构示例图:  

2.jpg

一个为m阶的B-Tree,设其索引N个key,则其树高h的上限为logm((N+1)/2),检索一个key,其查找节点个数的渐进复杂度为O(logmN)。从这点可以看出,B-Tree是一个非常有效率的索引数据结构。 
 

2、什么是B+Tree  
B+tree:是B-tree的变形树。

一棵m阶的B+tree和m阶的B-tree的差异在于:

    1.有n棵子树的结点中含有n个关键字; (B-tree是n棵子树有n-1个关键字)

    2.所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (B-tree的叶子节点并没有包括全部需要查找的信息)

    3.所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (B-tree的非终节点也包含需要查找的有效信息) 
B+树的结构示例图如下:

3.jpg

3、B+Tree与B-Tree 的区别
 1)B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。  

    2)在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。

 

思考:为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引? 

1) B+树的磁盘读写代价更低

B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。 

2) B+树的查询效率更加稳定 
  由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。 
  

MySQL ACID及四种隔离级别的解释

以下内容出自《高性能MySQL》第三版,了解事务的ACID及四种隔离级有助于我们更好的理解事务运作。

下面举一个银行应用是解释事务必要性的一个经典例子。假如一个银行的数据库有两张表:支票表(checking)和储蓄表(savings)。现在要从用户Jane的支票账户转移200美元到她的储蓄账户,那么至少需要三个步骤:

1、检查支票账户的余额高于或者等于200美元。

2、从支票账户余额中减去200美元。

3、在储蓄帐户余额中增加200美元。

上述三个步骤的操作必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。

 

可以用START TRANSACTION语句开始一个事务,然后要么使用COMMIT提交将修改的数据持久保存,要么使用ROLLBACK撤销所有的修改。事务SQL的样本如下:

1. start transaction;

2. select balance from checking where customer_id = 10233276;

3. update checking set balance = balance – 200.00 where customer_id = 10233276;

4. update savings set balance = balance + 200.00 where customer_id = 10233276;

5. commit;

 

ACID表示原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。一个很好的事务处理系统,必须具备这些标准特性:

 

原子性(atomicity)

  一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性

一致性(consistency)

     数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即使在执行第三、四条语句之间时系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。)

隔离性(isolation)

     通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。(在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则其看到支票帐户的余额并没有被减去200美元。)

持久性(durability)

  一旦事务提交,则其所做的修改不会永久保存到数据库。(此时即使系统崩溃,修改的数据也不会丢失。持久性是个有占模糊的概念,因为实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必,而且不可能有能做到100%的持久性保证的策略。)

 

隔离级别:

READ UNCOMMITTED(未提交读)

  在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。这个级别会导致很多问题,从性能上来说,READ UNCOMMITTED不会比其他的级别好太多,但却缺乏其他级别的很多好处,除非真的有非常必要的理由,在实际应用中一般很少使用。

READ COMMITTED(提交读)

  大多数数据库系统的默认隔离级别都是READ COMMTTED(但MySQL不是)。READ COMMITTED满足前面提到的隔离性的简单定义:一个事务开始时,只能"看见"已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候叫做不可重复读(nonrepeatble read),因为两次执行同样的查询,可能会得到不一样的结果

REPEATABLE READ(可重复读)

  REPEATABLE READ解决了脏读的问题。该隔离级别保证了在同一个事务中多次读取同样记录结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

SERIALIZABLE(可串行化)

  SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE会在读取每一行数据都加锁,所以可能导致大量的超时和锁争用问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。

打钩说明该隔离级别还存在这种情况,打X代表该隔离级别已经解决了这种情况:022015137336388.jpg

MyISAM引擎和InnoDB引擎的特点

随着MySQL的不断更新,由于各存储引擎功能特性差异较大,这篇文章主要是介绍如何来选择合适的存储引擎来应对不同的业务场景,朋友们可以根据业务需求,选择合适的存储引擎。^.^

MyISAM

特性

不支持事务:MyISAM存储引擎不支持事务,所以对事务有要求的业务场景不能使用

表级锁定:其锁定机制是表级索引,这虽然可以让锁定的实现成本很小但是也同时大大降低了其并发性能

读写互相阻塞:不仅会在写入的时候阻塞读取,MyISAM还会在读取的时候阻塞写入,但读本身并不会阻塞另外的读

只会缓存索引:MyISAM可以通过key_buffer缓存以大大提高访问性能减少磁盘IO,但是这个缓存区只会缓存索引,而不会缓存数据

适用场景

不需要事务支持(不支持)

并发相对较低(锁定机制问题)

数据修改相对较少(阻塞问题)

以读为主

数据一致性要求不是非常高

最佳实践

尽量索引(缓存机制)

调整读写优先级,根据实际需求确保重要操作更优先

启用延迟插入改善大批量写入性能

尽量顺序操作让insert数据都写入到尾部,减少阻塞

分解大的操作,降低单个操作的阻塞时间

降低并发数,某些高并发场景通过应用来进行排队机制

对于相对静态的数据,充分利用Query Cache可以极大的提高访问效率

MyISAM的Count只有在全表扫描的时候特别高效,带有其他条件的count都需要进行实际的数据访问

InnoDB

特性

具有较好的事务支持:支持4个事务隔离级别,支持多版本读

行级锁定:通过索引实现,全表扫描仍然会是表锁,注意间隙锁的影响

读写阻塞与事务隔离级别相关

具有非常高效的缓存特性:能缓存索引,也能缓存数据

整个表和主键以Cluster方式存储,组成一颗平衡树

所有Secondary Index都会保存主键信息

适用场景

需要事务支持(具有较好的事务特性)

行级锁定对高并发有很好的适应能力,但需要确保查询是通过索引完成

数据更新较为频繁的场景

数据一致性要求较高

硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,尽可能减少磁盘 IO

最佳实践

主键尽可能小,避免给Secondary index带来过大的空间负担

避免全表扫描,因为会使用表锁

尽可能缓存所有的索引和数据,提高响应速度

在大批量小插入的时候,尽量自己控制事务而不要使用autocommit自动提交

合理设置innodb_flush_log_at_trx_commit参数值,不要过度追求安全性

避免主键更新,因为这会带来大量的数据移动

NDBCluster

特性

分布式:分布式存储引擎,可以由多个NDBCluster存储引擎组成集群分别存放整体数据的一部分

支持事务:和Innodb一样,支持事务

可与mysqld不在一台主机:可以和mysqld分开存在于独立的主机上,然后通过网络和mysqld通信交互

内存需求量巨大:新版本索引以及被索引的数据必须存放在内存中,老版本所有数据和索引必须存在与内存中

适用场景

具有非常高的并发需求

对单个请求的响应并不是非常的critical

查询简单,过滤条件较为固定,每次请求数据量较少,又不希望自己进行水平Sharding

最佳实践

尽可能让查询简单,避免数据的跨节点传输

尽可能满足SQL节点的计算性能,大一点的集群SQL节点会明显多余Data节点

在各节点之间尽可能使用万兆网络环境互联,以减少数据在网络层传输过程中的延时

想了解更多关于MyISAM和InnoDB的性能对比,可以参考Percona的这个

http://www.percona.com/blog/2007/01/08/innodb-vs-myisam-vs-falcon-benchmarks-part-1/

http://imysql.com/2014/11/01/mysql-faq-convert-myisam-to-innodb-tips.shtml

设计模式 – 观察者模式

 

观察者模式 Observer

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。

  这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己。

 

观察者模式的组成

  抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。

  抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。

  具体主题角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。

  具体观察者角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。

 

程序实例

  通过程序实例来说明观察者模式:

  首先定义抽象的观察者:

//抽象观察者角色public interface Watcher
{    public void update(String str);

}

        然后定义抽象的主题角色,即抽象的被观察者,在其中声明方法(添加、移除观察者,通知观察者):

//抽象主题角色,watched:被观察
public interface Watched
{
    public void addWatcher(Watcher watcher);

    public void removeWatcher(Watcher watcher);

    public void notifyWatchers(String str);

}

        然后定义具体的观察者:

public class ConcreteWatcher implements Watcher
{

    @Override
    public void update(String str)
    {
        System.out.println(str);
    }

}

  之后是具体的主题角色: 

import java.util.ArrayList;
import java.util.List;

public class ConcreteWatched implements Watched
{
    // 存放观察者
    private List<Watcher> list = new ArrayList<Watcher>();

    @Override
    public void addWatcher(Watcher watcher)
    {
        list.add(watcher);
    }

    @Override
    public void removeWatcher(Watcher watcher)
    {
        list.remove(watcher);
    }

    @Override
    public void notifyWatchers(String str)
    {
        // 自动调用实际上是主题进行调用的
        for (Watcher watcher : list)
        {
            watcher.update(str);
        }
    }

}

  编写测试类:

public class Test
{
    public static void main(String[] args)
    {
        Watched girl = new ConcreteWatched();
        
        Watcher watcher1 = new ConcreteWatcher();
        Watcher watcher2 = new ConcreteWatcher();
        Watcher watcher3 = new ConcreteWatcher();
        
        girl.addWatcher(watcher1);
        girl.addWatcher(watcher2);
        girl.addWatcher(watcher3);
        
        girl.notifyWatchers("开心");
    }

}

设计模式 – 策略者模式

<?php

abstract class Expression{
    private static $keycount=0;
    private $key;
    abstract function interpret( InterpreterContext $context);

    public function getKey(){

        if(@!assert($this->key)){
            self::$keycount++;
            $this->key=self::$keycount;
        }

        return $this->key;
    }
}

class LiteralExpression extends Expression{
    private $value;
    function __construct($value){
        $this->value = $value;
    }

    function interpret(InterpreterContext $context){
        $context->replace($this, $this->value);

        echo $this->getKey().'<br>';
    }
}

class InterpreterContext{
    private $expressionstore = array();

    function replace(Expression $exp, $value){
        $this->expressionstore[$exp->getKey()] = $value;
    }

    function lookup(Expression $exp){
        return $this->expressionstore[$exp->getKey()];
    }
}

$context = new InterpreterContext();
$literal = new LiteralExpression('four');
$literal->interpret($context);


$literal2 = new LiteralExpression('xxxxx');
$literal2->interpret($context);

echo $context->lookup($literal).'<br />';
echo $context->lookup($literal2).'<br />';
echo $context->lookup($literal2).'<br />';
?>

1.概述

        在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。

例子1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单那的显示样式。

例子2:出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。

1336731431_2462.png

2.问题

如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?

3.解决方案

策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。(Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it. )

策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。

4.适用性

当存在以下情况时使用Strategy模式

1)• 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。

2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。

3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。

4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

5.结构

1336732187_4598.jpg

6.模式的组成

环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。

抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。

具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。

7.效果

Strategy模式有下面的一些优点:

1) 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。

2) 提供了可以替换继承关系的办法: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。

3) 消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。

4) 实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。

Strategy模式缺点:

1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类:  本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。

2 ) Strategy和Context之间的通信开销 :无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。

3 )策略模式将造成产生很多策略类:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。

8.实现

<?php  
/** 
* 策略模式 
* 定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化 
* 
*/   
  
  
/** 
* 出行旅游 
* 
*  
*/  
interface TravelStrategy{  
    public function travelAlgorithm();  
}   
  
  
/** 
 * 具体策略类(ConcreteStrategy)1:乘坐飞机 
 */  
class AirPlanelStrategy implements TravelStrategy {  
    public function travelAlgorithm(){  
        echo "travel by AirPlain", "<BR>\r\n";   
    }  
}   
  
  
/** 
 * 具体策略类(ConcreteStrategy)2:乘坐火车 
 */  
class TrainStrategy implements TravelStrategy {  
    public function travelAlgorithm(){  
        echo "travel by Train", "<BR>\r\n";   
    }  
}   
  
/** 
 * 具体策略类(ConcreteStrategy)3:骑自行车 
 */  
class BicycleStrategy implements TravelStrategy {  
    public function travelAlgorithm(){  
        echo "travel by Bicycle", "<BR>\r\n";   
    }  
}   
  
  
  
/** 
 *  
 * 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。 
 * 算法解决类,以提供客户选择使用何种解决方案: 
 */  
class PersonContext{  
    private $_strategy = null;  
  
    public function __construct(TravelStrategy $travel){  
        $this->_strategy = $travel;  
    }  
    /** 
    * 旅行 
    */  
    public function setTravelStrategy(TravelStrategy $travel){  
        $this->_strategy = $travel;  
    }  
    /** 
    * 旅行 
    */  
    public function travel(){  
        return $this->_strategy ->travelAlgorithm();  
    }  
}   
  
// 乘坐火车旅行  
$person = new PersonContext(new TrainStrategy());  
$person->travel();  
  
// 改骑自行车  
$person->setTravelStrategy(new BicycleStrategy());  
$person->travel();  
  
?>

2)排序策略:某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,

如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,

使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,

也能够灵活地增加新的排序算法。

设计模式 – 装饰者模式

示例1:

abstract class Tile{
    abstract function getWealthFactor();
}

class plains extends Tile{
    private $wealthfactor = 2;
    function getWealthFactor(){
        return $this->wealthfactor;
    }
}

abstract class TileDecorator extends Tile{
    protected $tile;
    function __construct(Tile $tile)
    {
        $this->tile = $tile;
    }
}

class DiamondDecorator extends TileDecorator{
    function getWealthFactor(){
        return $this->tile->getWealthFactor()+2;
    }
}

class PollutionDecorator extends TileDecorator{
    function getWealthFactor(){
        return $this->tile->getWealthFactor()-4;
    }
}

//$tile = new Plains();
//echo $tile->getWealthFactor();

//$tile = new DiamondDecorator(new Plains());
//echo $tile->getWealthFactor();

$tile = new PollutionDecorator(new DiamondDecorator( new Plains() ));
echo $tile->getWealthFactor();

示例2:

class RequestHelper{}

//抽象基类
abstract class ProcessRequest{
    abstract function process( RequestHelper $req );
}

//具体组件
class MainProcess extends ProcessRequest{
    function process( RequestHelper $req ){
        echo __CLASS__."doing somthing useful with request<br />";
    }
}

//抽象装饰类
//为子类保存了一个ProcessRequest对象
abstract class DecorateProcess extends ProcessRequest{
    protected $processrequest;
    function __construct(ProcessRequest $pr){
        $this->processrequest = $pr;
    }
}

class LogRequest extends DecorateProcess{
    function process( RequestHelper $req){
        echo __CLASS__.":logging request <br />";
        $this->processrequest->process($req);
    }
}

class AuthenticateRequest extends DecorateProcess{
    function process( RequestHelper $req){
        echo __CLASS__.":authenticate request <br />";
        $this->processrequest->process($req);
    }
}

class StrucureRequest extends DecorateProcess{
    function process( RequestHelper $req){
        echo __CLASS__.":strucure request <br />";
        $this->processrequest->process($req);
    }
}

$process = new AuthenticateRequest(new StrucureRequest( new LogRequest ( new MainProcess())));
$process->process( new RequestHelper());


//AuthenticateRequest:authenticate request
//StrucureRequest:strucure request
//LogRequest:logging request
//MainProcessdoing somthing useful with request

装饰者模式 和 组合模式的区别

装饰者模式可以用来透明地把对象包装在具有同样接口的另一个对象中。这样一来,你可以给一个方法加一些行为,然后将方法调用传递给原始对象。

相对于创建子类来说,使用装饰者对象是一种更灵活的选择。

装饰者模式和组合模式两者很像,那么这二者之间又有什么区别呢?

1、组合模式是一种结构型模式,用于把众多子对象组织为一个整体,及此程序员与大批对象打交道时可以将他们当作一个对象来对待,并将它们组织为层次性的树。通常它并不修改方法调用,

而只是将其沿组合对象与子对象的链向下传递,直到到达并落实在叶对象上。

2、装饰者模式也是一种结构型模式,但它并非用于组织对象,而是用于在不修改现有对象或从其派生子类的前提下为其增添职责。在一些较简单的例子中,装饰者模式会透明而不加修改地传递所有方法调用,

不过,创建装饰者模式的目的就在于对方法进行修改。

尽管简单的组合对象与简单的装饰者对象是相同的,但二者却有着不同的焦点。组合对象并不修改方法调用,其着眼在点于组织子对象。而装饰者模式存在的唯一目的就是修改方法调用而不是组织子对象,因为子对象只有一个。

设计模式 – 组合模式

这个···我就懒得自己敲代码了

找了些代表性强的例子 可能也不是php写的

模式嘛用其他语言也没有啥关系 理解模式就好

组合模式的原则是局部类和组合类具有同样的接口。

特殊对象越来越多,组合模式开始渐渐显得弊大于利。

在大部分局部对象可互换的情况下,组合模式才最适用。

虽然组合模式是一个优雅的模式,但是它并不能将自身轻松的存储到关系数据库里。

如果你想像对待单个对象一样对待组合对象,那么组合模式才十分有用。

组合模式又依赖于其他组成部分的简单性。

随着我们引入复杂的规则,代码会变得越来越难以维护。

组合模式不能很好地在关系数据库中保存数据,但却非常合适使用XML持久化。

==============================

定义:将对象组合成树形结构,表示层次结构关系,并且让对象能够以同样的方式呈现给客户端程序。

举例:

家族谱的编写:

男性:可传宗接代,也有权利把一些人剔除族谱。

女性:记录到家谱中,但不能传宗接代。

理解:每一个小家庭中,爸爸妈妈和我,都是爸爸做主,可踢出我跟妈妈中的任何一个,也可增加任何一个。组件模式中的组件可以是单独一个对象组成,也可以是多个组件组成(一个家庭,甚至一个家庭的多级延续);

类图:

271817591881210.png

族员共性代码:

//// <summary>
    /// //族人 抽象出来的族人共性
    /// </summary>
    public abstract class Father
    {
        //族人的姓名
        protected string name = string.Empty;
        public string Name
        {
            get
            {
                return name;
            }
        }

        //增加后代
        public abstract void Add(Father boy);
        //逐出家谱
        public abstract void Remove(Father boy);

        //定义所有族人,做个简介
        public abstract void Intro();
    }

家族成员代码

//男性后代
    public class Boy : Father
    {
        //构造函数
        public Boy() { }
        public Boy(string Name)
        {
            this.name = Name;
        }

        List<Father> myFamily = new List<Father>();

        //自我简介
        public override void Intro()
        {
            Console.WriteLine("我是:{0};", Name);
            foreach (Father f in myFamily)
            {
                f.Intro();
            }
        }

        //增加后代
        public override void Add(Father boy)
        {
            myFamily.Add(boy);
        }

        //逐出家谱
        public override void Remove(Father boy)
        {
            myFamily.Remove(boy);
        }
    }

    //女性后代 
    public class Gril : Father
    {
        //构造函数
        public Gril() { }
        public Gril(string Name)
        {
            this.name = Name;
        }
        //自我简介
        public override void Intro()
        {
            Console.WriteLine("我是:{0};", Name);
        }
        //不能添加
        public override void Add(Father store)
        {
            throw new NotImplementedException();
        }
        //不能删除
        public override void Remove(Father store)
        {
            throw new NotImplementedException();
        }
    }

客户端代码:

public static void Main()
        {
            //爷爷取老婆
            Boy yeye = new Boy("爷爷");
            Gril nainai = new Gril("奶奶");
            yeye.Add(nainai);

            //爷爷要孩子
            Boy baba = new Boy("爸爸");
            Gril gugu = new Gril("姑姑");            
            yeye.Add(gugu);
            yeye.Add(baba);

            //爸爸要我
            Boy me = new Boy("me");
            baba.Add(me);

            //我要孩子
            Boy son = new Boy("son");
            me.Add(son);

            //爷爷的大家庭,族谱做介绍
            yeye.Intro();

            Console.Read();
        }

总结:

有组合模式的案例可知:客户端代码,很容易组建层次结构,并且层次分明,同时又很容易遍历所有的组件集合。但要注意理解组合模式中的节点与页,节点可删可加,页则不能增删。

大家都好好理解,消化下这个模式,想想都在哪里,种情况下能使这个模式的特性发挥的淋漓尽职。