015.组合模式

公司的人事管理是一个典型的树状结构:

我们今天的任务就是要把这个树状结构实现出来,并且还要把它遍历一遍.

从这个树状结构上分析,有两种节点:有分支的节点(如研发部经理)和无分支的节点(如员工A、员工D等),总经理叫做根节点,类似研发部经理有分支的节点叫做树枝节点,类似员工A的无分支的节点叫做树叶节点,三个类型的的节点,那是不是定义三个类就可以?好,我们按照这个思路走下去,先看我们自己设计的类图:

以下是上述类图的实现:

/**
 * 定义一个根节点,就为总经理服务
 */
public interface IRoot {

    // 得到总经理的信息
    String getInfo();

    // 总经理下边要有小兵,那要能增加小兵,比如研发部经理,这是个树枝节点
    void add(IBranch branch);

    // 增加树叶节点
    void add(ILeaf leaf);

    // 遍历下属
    ArrayList<Object> getSubordinateInfo();

}

/**
 * 树枝节点,也就是各个部门经理和组长的角色
 */
public interface IBranch {

    // 获取信息
    String getInfo();

    // 增加数据节点,例如研发部下的研发一组
    void add(IBranch branch);

    // 增加树叶节点
    void add(ILeaf leaf);

    // 获取下级信息
    ArrayList<Object> getSubordinateInfo();

}

/**
 * 叶子节点,也就是最小的小兵了,只能自己干活,不能指派别人了
 */
public interface ILeaf {

    // 获得自己的信息
    String getInfo();

}

/**
 * 根节点的实现类
 */
public class Root implements IRoot {

    // 保存根节点下的树枝节点和树叶节点,subordinate是下级的意思
    private ArrayList<Object> subordinateList = new ArrayList<>();
    // 根节点的名称
    private String name = "";
    // 根节点的职位
    private String position = "";
    // 根节点的薪水
    private int salary = 0;

    // 通过构造函数传递进来总经理的信息
    public Root(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 得到自己的信息
    @Override
    public String getInfo() {
        return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
    }

    // 增加树枝节点
    @Override
    public void add(IBranch branch) {
        this.subordinateList.add(branch);
    }

    // 增加叶子节点,比如秘书,直接隶属于总经理
    @Override
    public void add(ILeaf leaf) {
        this.subordinateList.add(leaf);
    }

    // 获得下级的信息
    @Override
    public ArrayList<Object> getSubordinateInfo() {
        return this.subordinateList;
    }
}

/**
 * 树枝节点,就是各个部门经理和组长的角色
 */
public class Branch implements IBranch {

    // 存储子节点的信息
    private ArrayList<Object> subordinateList = new ArrayList<>();
    // 树枝节点的名称
    private String name = "";
    // 树枝节点的职位
    private String position = "";
    // 树枝节点的薪水
    private int salary = 0;

    // 通过构造函数传递树枝节点的参数
    public Branch(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 获得自己树枝节点的信息
    @Override
    public String getInfo() {
        return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
    }

    // 增加一个子树枝节点
    @Override
    public void add(IBranch branch) {
        this.subordinateList.add(branch);
    }

    // 增加一个叶子节点
    @Override
    public void add(ILeaf leaf) {
        this.subordinateList.add(leaf);
    }

    // 获得下级的信息
    @Override
    public ArrayList<Object> getSubordinateInfo() {
        return this.subordinateList;
    }
}

/**
 * 最小的叶子节点
 */
public class Leaf implements ILeaf {

    // 叶子叫什么名字
    private String name = "";
    // 叶子的职位
    private String position = "";
    // 叶子的薪水
    private int salary = 0;

    // 通过构造函数传递信息
    public Leaf(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 最小的小兵只能获得自己的信息了
    @Override
    public String getInfo() {
        return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
    }
}

好了,所有的根节点,树枝节点和叶子节点都已经实现了,从总经理、部门经理到最终的员工都已经实现了,然后的工作就是组装成一个树状结构和遍历这个树状结构,看Client类:

/**
 * Client的作用是组装这棵树,并遍历一遍
 */
public class Client {

    public static void main(String[] args) {

        // 首先产生了一个根节点
        IRoot ceo = new Root("王大麻子", "CEO", 100000);

        // 产生三个部门经理,也就是树枝节点
        IBranch developDep = new Branch("刘大瘸子", "研发部经理", 10000);
        IBranch salesDep = new Branch("马儿拐子", "销售部经理", 20000);
        IBranch financeDep = new Branch("赵三驼子", "财务部经理", 30000);

        // 再把三个小组长产生出来
        IBranch firstDevGroup = new Branch("杨三乜斜", "开发一组组长", 5000);
        IBranch secondDevGroup = new Branch("吴大棒槌", "开发二组组长", 6000);

        // 剩下的就是我们这些小兵了,就是路人甲,路人乙
        ILeaf zhengLaoLiu = new Leaf("郑老六", "研发部副总", 20000);
        ILeaf a = new Leaf("A", "开发人员", 2000);
        ILeaf b = new Leaf("B", "开发人员", 2000);
        ILeaf c = new Leaf("C", "开发人员", 2000);
        ILeaf d = new Leaf("D", "开发人员", 2000);
        ILeaf e = new Leaf("E", "开发人员", 2000);
        ILeaf f = new Leaf("F", "开发人员", 2000);
        ILeaf g = new Leaf("G", "开发人员", 2000);
        ILeaf h = new Leaf("H", "销售人员", 5000);
        ILeaf i = new Leaf("I", "销售人员", 4000);
        ILeaf j = new Leaf("J", "财务人员", 5000);
        ILeaf k = new Leaf("K", "CEO秘书", 8000);

        // 组装这棵树
        // 首先是定义总经理下有三个部门经理
        ceo.add(developDep);
        ceo.add(salesDep);
        ceo.add(financeDep);
        // 总经理下还有一个秘书
        ceo.add(k);

        // 定义研发部门下的结构
        developDep.add(firstDevGroup);
        developDep.add(secondDevGroup);
        // 研发部经理下还有一个副总
        developDep.add(zhengLaoLiu);

        // 看看开发两个开发小组下有什么
        firstDevGroup.add(a);
        firstDevGroup.add(b);
        firstDevGroup.add(c);
        secondDevGroup.add(d);
        secondDevGroup.add(e);
        secondDevGroup.add(f);

        // 再看销售部下的人员情况
        salesDep.add(h);
        salesDep.add(i);
        // 最后一个财务
        financeDep.add(j);

        // 树状结构写完毕,然后我们打印出来
        System.out.println(ceo.getInfo());

        //打印出来整个树形
        getAllSubordinateInfo(ceo.getSubordinateInfo());

    }

    // 遍历所有的树枝节点,打印出信息
    private static void getAllSubordinateInfo(ArrayList<Object> subordinateList) {
        for (Object obj : subordinateList) {
            if (obj instanceof Leaf) {
                ILeaf leaf = (ILeaf)obj;
                System.out.println(leaf.getInfo());
            } else {
                IBranch branch = (IBranch)obj;
                System.out.println(branch.getInfo());
                getAllSubordinateInfo(branch.getSubordinateInfo());
            }
        }
    }

}

和我们期望要的结果一样,一棵完整的树就生成了,而且我们还能够遍历,但这样的类设计是有问题的,getInfo()每个接口都有为什么不能抽象出来?Root类和Branch类有什么差别?为什么要定义成两个接口两个类?如果我要加一个任职期限,是不是每个类都需要修改?如果我要后序遍历(从员工找到他的上级领导)能做吗?

问题很多,我们一个一个解决,先说抽象的问题,确实可以把IBranchIRoot合并成一个接口,这个我们先肯定下来,这是个比较大的改动,我们先画个类图:

这个类图还是有点问题的,接口的作用是什么?定义共性,那ILeafIBranch是不是也有共性呢?有getInfo(),我们是不是把这个共性也已经封装起来,再修改一下类图:

类图上有两个接口,ICorp是公司所有人员的信息的接口类,不管你是经理还是员工,你都有名字,职位,薪水,这个定义成一个接口没有错,IBranch 有没有必要呢?我们先实现出来然后再说:

/**
 * 公司类,定义每个员工都有信息
 */
public interface ICorp {

    // 获取信息
    String getInfo();

}

public class Leaf implements ICorp {

    // 小兵的名字
    private String name = "";
    // 小兵的职位
    private String position = "";
    // 小兵的薪水
    private int salary = 0;

    // 通过构造函数传递信息
    public Leaf(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 获得小兵的信息
    @Override
    public String getInfo() {
        return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
    }
}

/**
 * 树枝节点,有下属节点
 */
public interface IBranch {

    // 能够增加小兵(树叶节点)或者是经理(树枝节点)
    void addSubordinate(ICorp corp);

    // 获取下级信息
    ArrayList<ICorp> getSubordinateInfo();

}

/**
 * 树枝节点,就是各个部门经理和组长的角色
 */
public class Branch implements IBranch, ICorp {

    // 下级
    private ArrayList<ICorp> subordinateList = new ArrayList<>();
    //姓名
    private String name = "";
    // 职位
    private String position = "";
    // 薪水
    private int salary = 0;

    // 通过构造函数传递树枝节点的参数
    public Branch(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    // 增加一个下属,可能是小头目,也可能是个小兵
    @Override
    public void addSubordinate(ICorp corp) {
        this.subordinateList.add(corp);
    }

    @Override
    public ArrayList<ICorp> getSubordinateInfo() {
        return this.subordinateList;
    }

    // 获取自己的信息
    @Override
    public String getInfo() {
        return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
    }
}

/**
 * @author YangYunhe
 * @date 2020-12-28 10:20
 * @description 组装树形结构
 */
public class Client {

    public static void main(String[] args) {

        // 组装一个组织结构
        Branch ceo = compositeCorpTree();

        // 打印CEO的信息
        System.out.println(ceo.getInfo());

        // 打印所有员工的信息
        System.out.println(getTreeInfo(ceo));

    }

    // 遍历整棵树,只要给我根节点,我就能遍历出所有的节点
    public static String getTreeInfo(Branch root) {
        StringBuilder info = new StringBuilder();
        ArrayList<ICorp> subordinateList = root.getSubordinateInfo();
        for (ICorp iCorp : subordinateList) {
            if(iCorp instanceof Leaf) {
                info.append(iCorp.getInfo()).append("\n");
            } else {
                info.append(iCorp.getInfo()).append("\n").append(getTreeInfo((Branch)iCorp));
            }
        }
        return info.toString();
    }

    public static Branch compositeCorpTree() {
        // 首先产生了CEO
        Branch ceo = new Branch("王大麻子", "CEO", 100000);
        // 产生三个部门经理
        Branch developDep = new Branch("刘大瘸子", "研发部经理", 10000);
        Branch salesDep = new Branch("马儿拐子", "销售部经理", 20000);
        Branch financeDep = new Branch("赵三驼子", "财务部经理", 30000);
        // 再把三个小组长产生出来
        Branch firstDevGroup = new Branch("杨三乜斜", "开发一组组长", 5000);
        Branch secondDevGroup = new Branch("吴大棒槌", "开发二组组长", 6000);
        // 把所有的小兵都创建出来
        Leaf zhengLaoLiu = new Leaf("郑老六", "研发部副总", 20000);
        Leaf a = new Leaf("A", "开发人员", 2000);
        Leaf b = new Leaf("B", "开发人员", 2000);
        Leaf c = new Leaf("C", "开发人员", 2000);
        Leaf d = new Leaf("D", "开发人员", 2000);
        Leaf e = new Leaf("E", "开发人员", 2000);
        Leaf f = new Leaf("F", "开发人员", 2000);
        Leaf g = new Leaf("G", "开发人员", 2000);
        Leaf h = new Leaf("H", "销售人员", 5000);
        Leaf i = new Leaf("I", "销售人员", 4000);
        Leaf j = new Leaf("J", "财务人员", 5000);
        Leaf k = new Leaf("K", "CEO秘书", 8000);

        // 组装这棵树
        // 定义CEO下的三个部门经理和一个秘书
        ceo.addSubordinate(developDep);
        ceo.addSubordinate(salesDep);
        ceo.addSubordinate(financeDep);
        // 总经理下还有一个秘书
        ceo.addSubordinate(k);

        // 定义研发部门下的结构
        developDep.addSubordinate(firstDevGroup);
        developDep.addSubordinate(secondDevGroup);
        developDep.addSubordinate(zhengLaoLiu);

        // 定义两个开发小组下的结构
        firstDevGroup.addSubordinate(a);
        firstDevGroup.addSubordinate(b);
        firstDevGroup.addSubordinate(c);
        secondDevGroup.addSubordinate(d);
        secondDevGroup.addSubordinate(e);
        secondDevGroup.addSubordinate(f);

        // 定义销售部下的人员
        salesDep.addSubordinate(h);
        salesDep.addSubordinate(i);
        // 定义财务部下的人员
        financeDep.addSubordinate(j);

        return ceo;
    }

}

我们的程序还可以继续优化,LeafBranch中都有getInfo() 方法,可以抽象出来:

/**
 * 公司人员抽象类
 */
public abstract class Corp {

    // 姓名
    private String name = "";
    // 职位
    private String position = "";
    // 薪水
    private int salary = 0;

    public Corp(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    public String getInfo() {
        return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
    }

}

/**
 * 普通员工很简单,就写一个构造函数就可以了
 */
public class Leaf extends Corp {

    public Leaf(String name, String position, int salary) {
        super(name, position, salary);
    }
}

/**
 * 节点类,也简单了很多
 */
public class Branch extends Corp {

    // 领导下边有那些下级领导和小兵
    private ArrayList<Corp> subordinateList = new ArrayList<>();

    public Branch(String name, String position, int salary) {
        super(name, position, salary);
    }

    // 增加一个下属,可能是小头目,也可能是个小兵
    public void addSubordinate(Corp corp) {
        this.subordinateList.add(corp);
    }

    // 我有哪些下属
    public ArrayList<Corp> getSubordinateInfo() {
        return this.subordinateList;
    }

}

public class Client {

    public static void main(String[] args) {

        // 组装一个组织结构
        Branch ceo = compositeCorpTree();

        // 打印CEO的信息
        System.out.println(ceo.getInfo());

        // 打印所有员工的信息
        System.out.println(getTreeInfo(ceo));

    }

    // 遍历整棵树,只要给我根节点,我就能遍历出所有的节点
    public static String getTreeInfo(Branch root) {
        StringBuilder info = new StringBuilder();
        ArrayList<Corp> subordinateList = root.getSubordinateInfo();
        for (Corp corp : subordinateList) {
            if(corp instanceof Leaf) {
                info.append(corp.getInfo()).append("\n");
            } else {
                info.append(corp.getInfo()).append("\n").append(getTreeInfo((Branch)corp));
            }
        }
        return info.toString();
    }

    public static Branch compositeCorpTree() {
        // 首先产生了CEO
        Branch ceo = new Branch("王大麻子", "CEO", 100000);
        // 产生三个部门经理
        Branch developDep = new Branch("刘大瘸子", "研发部经理", 10000);
        Branch salesDep = new Branch("马儿拐子", "销售部经理", 20000);
        Branch financeDep = new Branch("赵三驼子", "财务部经理", 30000);
        // 再把三个小组长产生出来
        Branch firstDevGroup = new Branch("杨三乜斜", "开发一组组长", 5000);
        Branch secondDevGroup = new Branch("吴大棒槌", "开发二组组长", 6000);
        // 把所有的小兵都创建出来
        Leaf zhengLaoLiu = new Leaf("郑老六", "研发部副总", 20000);
        Leaf a = new Leaf("A", "开发人员", 2000);
        Leaf b = new Leaf("B", "开发人员", 2000);
        Leaf c = new Leaf("C", "开发人员", 2000);
        Leaf d = new Leaf("D", "开发人员", 2000);
        Leaf e = new Leaf("E", "开发人员", 2000);
        Leaf f = new Leaf("F", "开发人员", 2000);
        Leaf g = new Leaf("G", "开发人员", 2000);
        Leaf h = new Leaf("H", "销售人员", 5000);
        Leaf i = new Leaf("I", "销售人员", 4000);
        Leaf j = new Leaf("J", "财务人员", 5000);
        Leaf k = new Leaf("K", "CEO秘书", 8000);

        // 组装这棵树
        // 定义CEO下的三个部门经理和一个秘书
        ceo.addSubordinate(developDep);
        ceo.addSubordinate(salesDep);
        ceo.addSubordinate(financeDep);
        // 总经理下还有一个秘书
        ceo.addSubordinate(k);

        // 定义研发部门下的结构
        developDep.addSubordinate(firstDevGroup);
        developDep.addSubordinate(secondDevGroup);
        developDep.addSubordinate(zhengLaoLiu);

        // 定义两个开发小组下的结构
        firstDevGroup.addSubordinate(a);
        firstDevGroup.addSubordinate(b);
        firstDevGroup.addSubordinate(c);
        secondDevGroup.addSubordinate(d);
        secondDevGroup.addSubordinate(e);
        secondDevGroup.addSubordinate(f);

        // 定义销售部下的人员
        salesDep.addSubordinate(h);
        salesDep.addSubordinate(i);
        // 定义财务部下的人员
        financeDep.addSubordinate(j);

        return ceo;
    }

}

经过这样一步步的改造,类、接口减少了很多,而且程序也简单了很多。

上面我们讲到的就是组合模式(也叫合成模式),有时又叫做部分-整体模式(Part-Whole),主要是用来描述整体与部分的关系,用的最多的地方就是树形结构。组合模式通用类图如下:

我们先来说说组合模式的几个角色:

  • 抽象构件角色(Component):定义参加组合的对象的共有方法和属性,可以定义一些默认的行为或属性;比如我们例子中的getInfo() 就封装到了抽象类中。

  • 叶子构件(Leaf):叶子对象,其下再也没有其他的分支。

  • 树枝构件(Composite):树枝对象,它的作用是组合树枝节点和叶子节点;

组合模式有两种模式,透明模式和安全模式,这两个模式有什么区别呢?先看类图:

这两种模式各有优缺点,透明模式是把用来组合使用的方法放到抽象类中,比如add()remove()以及getChildren() 等方法,(顺便说一下,getChildren() 一般返回的结果为Iterable的实现类),不管叶子对象还是树枝对象都有相同的结构,通过判断getChildren 的返回值确认是叶子节点还是树枝节点,如果处理不当,这个会在运行期出现问题,不建议使用这种方式;安全模式把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全,我们的例子使用了安全模式。

组合模式的优缺点:

  • 只要是树形结构,就要考虑使用组合模式,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,就可以考虑使用组合模式吧

  • 组合模式有一个非常明显的缺点,在Client类中的的定义树叶和树枝使用时使用了如下代码:

    Branch developDep = new Branch("刘大瘸子","研发部门经理",10000);
    Leaf g = new Leaf("g","开发人员",2000);
    

    直接使用了实现类去创建对象,这个在面向接口编程上是很不恰当的。

我们在上面也还提到了一个问题,就是树的遍历问题,从上到下遍历没有问题,但是我要是从下往上遍历呢?比如在人力资源这颗树上,我从中抽取一个用户,要找到它的上级有哪些,下级有哪些,怎么处理?先看类图:

看类图中的红色方框,只要增加两个方法就可以了,一个是设置父节点是谁,一个是查找父节点是谁,我们来看一下程序的改变:

/**
 * 公司人员抽象类
 */
public abstract class Corp {

    // 姓名
    private String name = "";
    // 职位
    private String position = "";
    // 薪水
    private int salary = 0;
    // 父节点
    private Corp parent = null;

    public Corp(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    public String getInfo() {
        return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
    }

    /**
     * 增加了以下两个方法
     */
    // 设置父节点
    protected void setParent(Corp parent) {
        this.parent = parent;
    }

    // 得到父节点
    public Corp getParent() {
        return this.parent;
    }

}

/**
 * 节点类
 */
public class Branch extends Corp {

    // 领导下边有那些下级领导和小兵
    private ArrayList<Corp> subordinateList = new ArrayList<>();

    public Branch(String name, String position, int salary) {
        super(name, position, salary);
    }

    // 增加一个下属,可能是小头目,也可能是个小兵
    public void addSubordinate(Corp corp) {
        // 重要的是这行,添加下属的时候给下属设置父节点为自己
        corp.setParent(this);
        this.subordinateList.add(corp);
    }

    // 我有哪些下属
    public ArrayList<Corp> getSubordinateInfo() {
        return this.subordinateList;
    }

}

每个节点无论是树枝节点还是树叶节点,都增加了一个属性:父节点对象,这样在树枝节点增加子节点或叶子的时候设置父节点,然后整棵树除了根节点外每个
节点都一个父节点,这样每个节点上都有父节点了,有了这个parent 属性,后序遍历(从下往上找)、中序遍历(从中间某个环节往上或往下遍历)都解决了,这个就不多说了。再提一个扩展问题,树叶节点和树枝节点是有顺序的,比如我们上面的例子,研发一组下边有三个成员,这三个成员是要进行排序的,这种情况怎么处理?

本文原书:

《您的设计模式》 作者:CBF4LIFE

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容

  • 组合模式 在DebugMybatis的源码时,在DynamicSqlSource.getBoundSql动态获取s...
    不学无数的程序员阅读 1,022评论 0 6
  • 大家在上学的时候应该都学过“数据结构”这门课程吧,还记得其中有一节叫“二叉树”吧,我们上学那会儿这一章节是必考内容...
    辽A丶孙悟空阅读 222评论 0 8
  • 公司的人事管理就是一个典型的树状结构,老大,往下一层一层的管理,最后到我们这层小兵,很典型的树状结构 今天的任务就...
    凉快先生阅读 212评论 0 1
  • 概述 UML类图 代码栗子 总结 概述概念 组合模式是指将对象组合成树形结构以表示“部分-整体”的层次结构,组合模...
    tanoak阅读 351评论 0 0
  • 渐变的面目拼图要我怎么拼? 我是疲乏了还是投降了? 不是不允许自己坠落, 我没有滴水不进的保护膜。 就是害怕变得面...
    闷热当乘凉阅读 4,191评论 0 13