威尼斯www.9778.com-威尼斯正版官方网站

《大设》第二十一篇之组合模式

日期:2020-01-06编辑作者:服务器&运维

我们平时开发过程中,一定会遇到这种情况:同时处理简单对象和由简单对象组成的复杂对象,这些简单对象和复杂对象会组合成树形结构,在客户端对其处理的时候要保持一致性。比如电商网站中的产品订单,每一张产品订单可能有多个子订单组合,比如操作系统的文件夹,每个文件夹有多个子文件夹或文件,我们作为用户对其进行复制,删除等操作时,不管是文件夹还是文件,对我们操作者来说是一样的。在这种场景下,就非常适合使用组合模式来实现。

今天就不国际惯例了,直接进入正文吧,昨天我们学习了单例模式,今天我们来学习一个常见的设计模式,叫做组合模式

基本知识

  组合模式是个什么鬼呢?听这个名字可能觉得高大上,但是对于作为码农的我们肯定是比较熟悉下面的内容的:

组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。组合模式主要有三个角色::抽象类,主要定义了参与组合的对象的公共接口:组成组合对象的最基本对象:由子对象组合起来的复杂对象理解组合模式的关键是要理解组合模式对单个对象和组合对象使用的一致性,我们接下来说说组合模式的实现加深理解。组合模式算是为在页面动态创建UI量身定做的,你可以只使用一条命=命令为许多对象初始化一些复杂的或者递归的操作.组合模式提供了两个有点:允许你将一组对象当成特定的对象.组合对象和组成它的子对象实现相同的操作.对组合对象执行某一个操作将会使该对象下的所有子对象执行相同的操作.因此你不仅可以无缝的替换单个对象为一组对象集合,反过来也一样.这些独立的对象之间即所谓松散耦合的.组合模式会将子对象集组合成树结构并且允许遍历整个树.这样可以隐藏内部实现并且允许你以任意的方式组织子对象.这个对象的任何代码将不会依赖内部子对象的实现.

  图片 1

组合模式的实现

  有人会说,博主你不要逗好不好,这不就是一个再普通不过的JAVA Project吗?这有啥好看的?我天天都能看到,对于此,博主想说,你说的对,这个就是作为程序猿的我们天天见到的东西,但是这个东西跟我们今天所说的组合模式有什么关系呢?暂且保密,我们先来看一下对于组合模式的定义:

最简单的组合模式

  (GOF《设计模式》):将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

HTML文档的DOM结构就是天生的树形结构,最基本的元素醉成DOM树,最终形成DOM文档,非常适用适用组合模式。我们常用的jQuery类库,其中组合模式的应用更是频繁,例如经常有下列代码实现:

  看到这里,大家就应该明白上面的图片的意思了,没错,就是树形结构,我们的一个普通的JAVA项目中首先会有一个src资源包(source folder),在这个资源包下面我们可以再创建多个package,在package中既可以创建新的package,也可以创建java文件,从而组成一个树形结构。

$.addClass.remove;

  如果说上面的例子感触不深的话,还有一个更明显的例子就是我们windows系统中的文件目录,我的电脑中有C/D/E/F四个分区,每个分区下又可以有多个文件或者文件夹,文件夹中又可以有文件和文件夹,这也是一个明显的树形结构。

这句简单的代码就是获取class包含test的元素,然后进行addClass和removeClass处理,其中不论$是一个元素,还是多个元素,最终都是通过统一的addClass和removeClass接口进行调用。我们简单模拟一下addClass的实现:

  在这种树形结构中,单个对象可以组合成复杂对象,复杂对象又可以组合成更加复杂的对象,理论上是可以无限组合下去的,如果你想要客户端可以忽略这种复杂的层次结构,可以使用统一的方式去操作层次结构中的对象,那么使用组合模式将是你不可多得的选择。

var addClass = function  { if (eles instanceof NodeList) { for (var i = 0, length = eles.length; i < length; i++) { eles[i].nodeType === 1 && (eles[i].className += (' ' + className + ' ')); } } else if  { eles.nodeType === 1 && (eles.className += (' ' + className + ' ')); } else { throw "eles is not a html node"; }}addClass(document.getElementById;addClass(document.querySelectorAll;

  其次,根据组合模式的定义,我们知道,组合模式可以将对象组合成树形结构,用以表示“部分整体”的层次结构,也就是说,在你想表示“部分整体”的层次结构或者说要让客户端忽略复杂的层次结构,使用统一的方式去操作复杂结构中的对象,那么你可以选择使用组合模式。

这段代码简单的模拟了addClass的实现,很简单地先判断节点类型,然后根据不同类型添加className。对于NodeList或者是Node来说,客户端调用都是同样的使用了addClass这个接口,这个就是组合模式的最基本的思想,使部分和整体的使用具有一致性。

  那么,组合模式定义中所说的使单个对象和组合对象具有一致性又是什么意思呢?其实就是具有统一的接口,只要两者实现同样的接口,那么行为肯定是一致的。

典型的例子

  说了半天,我们来看一下组合模式的类图:

前面我们提到一个典型的例子:产品订单包含多个产品子订单,多个产品子订单组成一个复杂的产品订单。由于Javascript语言的特性,我们将组合模式的三个角色简化成2个角色:子对象:在这个例子中,子对象就是产品子订单组合对象:这里就是产品的总订单假设我们开发一个旅游产品网站,其中包含机票和酒店两种子产品,我们定义了子对象如下:

  图片 2

function FlightOrder() { }FlightOrder.prototyp.create = function () { console.log("flight order created");}function HotelOrder() { }HotelOrder.prototype.create = function () { console.log("hotel order created");}

  通过类图我们可以看到,在组合模式中存在三个类:

上面的代码定义了两个类:机票订单类和酒店订单类,每个类都有各自的订单创建方法。接下来我们创建一个总订单类:

  1.Conponent类:这个是节点的统一接口,也就是上面所说的单个对象和组合对象具有一致性的实现基础;

function TotalOrders() { this.orderList = [];}TotalOrders.prototype.addOrder = function  { this.orderList.push;}TotalOrders.prototype.create = function  { for (var i = 0, length = this.orderList.length; i < length; i++) { this.orderList[i].create(); }}

  2.Leaf类:这个是叶子节点,它不能再包含子节点,也就是上面定义中所说的单个对象;

这个对象主要有3个成员:订单列表,添加订单的方法,创建订单的方法。在客户端使用的时候如下:

  3.Composite类:这个是非叶子节点,它是可以包含子节点的,可以说是组合对象;

var flight = new FlightOrder;var orders = new TotalOrders();orders.addOrder;orders.addOrder;orders.create();

  理论部分的内容基本说完了,下面我们来举个具体的例子来说明组合模式,还是文章刚开始的时候给大家看的那个图片,我们知道在src下可以创建新的package,在package下又可以创建package和java文件,而且删除package下的java文件时只是简单的删除了java文件,但是在删除package时,会在删除掉package下的java文件和package后同时删除掉package。

客户端调用展示了两种方式,一种是单一的创建机票订单,一种是创建多张订单,但最终都是通过create方法进行创建,这就是一个很典型的组合模式的应用场景。

  那么怎么体现出定义中所说的单个对象和组合对象具有一致性呢?这里的一致性就在于在客户端不管你删除的是java文件还是package,客户端只管删除,而我们需要针对文件的不同进行相应的处理;

总结组合模式并不难理解,它主要解决的是单一对象和组合对象在使用方式上的一致性问题。如果对象具有明显的层次结构并且想要统一地使用它们,这就非常适合使用组合模式。在Web开发中,这种层次结构非常常见,很适合使用组合模式,尤其是对于JS来说,不用拘泥于传统面向对象语言的形式,灵活地利用JS语言的特性,达到对部分和整体使用的一致性。使用组合模式的场景在遇到下面两种情况的时候才使用组合模式A.含有某种层级结构的对象集合B.希望对这些对象或者其中的某些对象执行某种操作组合模式的缺点因为组合对象的任何操作都会对所有的子对象调用同样的操作,所以当组合的结构很大时会有性能问题。还有就是使用组合模式封装HTML时要选择合适的标签,比如table就不能用于组合模式,叶子节点不明显

  下面我们使用组合模式写出上面的代码,首先需要有一个一致性接口类,也就是Component接口,定义了java文件和package的一致性内容:

public interface IFile {

    /**
     * package拥有的方法
     */
    //新建java文件
    public void addFile(String fileName);

    //删除java文件
    public void removeFile(String fileName);

    //根据索引查询java文件
    public IFile getFile(int index);

    /**
     * java文件和package共有的方法
     */
    //删除方法
    public void delete();

    public String getName();
}

  对于一致性接口需要解释一下:

  首先,我们知道,对于package我们可以在其下面新增java文件或者package,也可以删除这个文件夹下的java文件但是一个单纯的java文件是不能再删除java文件或者再新建java文件(内部类不算,而且内部类经过编译以后class文件也是单独存在的),那么为什还要在这个接口中写add/remove/get方法呢?

  这就牵扯到两个新的概念透明方式和安全方式:

  上面我们使用的就是透明方式,这么做的结果就是只要是IFile接口的实现类,都具备了add/remove方法,这样的话叶子节点和枝节点对于外界就没有区别,他们具备完全一致的接口,但是这样做的缺点也是很明确的,叶子节点实现这样的add、remove方法完全没有意义;

  如果要使用安全方式,也就是在上面的接口中不声明add/remove方法,那么叶子节点就不需要去实现它们,只有在枝节点的类中声明add/remove方法,这样做虽然可以避免上面透明方式的问题,但是由于不够透明,所以叶子节点和枝节点将不具有相同的接口,客户端每次获取到一个节点都需要判断这个节点是叶子节点还是枝节点,还是比较麻烦的;

  好了,解释完之后我们继续看枝节点的实现类:

package com.pattern.combain;

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

public class PackageFile implements IFile{

    private String name;
    private IFile pack;
    private List<IFile> files;

    public PackageFile(String name){
        this(name,null);
    }


    public PackageFile(String name,IFile pack){
        super();
        this.name = name;
        this.pack = pack;
        files = new ArrayList<IFile>();
    }

    @Override
    public void addFile(String fileName) {
        if(fileName.indexOf("java")!=-1){
            files.add(new JavaFile(fileName,this));
        }else{
            files.add(new PackageFile(fileName, this));
        }
    }

    @Override
    public void removeFile(String fileName) {
        for(IFile file:files){
            if(fileName!=null&&fileName.equals(file.getName())){
                files.remove(file);
                break;
            }
        }
    }

    @Override
    public IFile getFile(int index) {
        return files.get(index);
    }

    @Override
    public void delete() {
        //1.删除package下的package和java文件
        List<IFile> list = new ArrayList<IFile>(files);
        for(IFile file:list){
            file.delete();
        }

        //2.删除当前package
        if(this.pack!=null){
            pack.removeFile(name);
        }
    }

    @Override
    public String getName(){
        return this.name;
    }


    public List<IFile> getFiles() {
        return files;
    }

}

  然后是叶子节点的实现类:

package com.pattern.combain;

public class JavaFile implements IFile{

    private String name;
    private IFile pack;

    public JavaFile(String name,IFile pack){
        super();
        this.name = name;
        this.pack = pack;
    }


    @Override
    public void addFile(String fileName) {
        throw new UnsupportedOperationException("java文件不支持创建新的文件");
    }

    @Override
    public void removeFile(String fileName) {
        throw new UnsupportedOperationException("java文件不支持移除文件");
    }

    @Override
    public IFile getFile(int index) {
        throw new UnsupportedOperationException("java文件不支持根据索引查询java文件");
    }

    @Override
    public void delete() {
        pack.removeFile(name);
    }

    @Override
    public String getName(){
        return this.name;
    }

}

  测试类的内容如下所示:

package com.pattern.combain;

public class Test {

    public static void main(String[] args) {
        IFile src = new PackageFile("src");
        src.addFile("com.pattern.combain");
        IFile combainPack = src.getFile(0);
        combainPack.addFile("IFile.java");
        combainPack.addFile("JavaFile.java");
        combainPack.addFile("PackageFile.java");
        combainPack.addFile("Test.java");
        display(null,src);
        System.out.println("----------------------------");
        combainPack.getFile(1).delete();
        display(null, src);
        System.out.println("---------------------------");
        combainPack.delete();
        display(null, src);
    }

    private static void display(String prefix,IFile file) {
        if (prefix == null) {
            prefix = "";
        }
        System.out.println(prefix+file.getName());
        if(file instanceof PackageFile){
            PackageFile pack = (PackageFile)file;
            for(int i = 0;i<pack.getFiles().size(); i++){
                if(file.getFile(i)!=null){
                    display(prefix+"--",file.getFile(i));
                }
            }
        }
    }
}

  在上述的测试类中,添加了一个展示文件目录的方法,然后通过创建一个src文件夹,然后在src文件夹下又创建了一个文件夹,最后展示文件目录结构,后面分别删除一个java文件和一个package,测试结果如下所示:

  图片 3

  最后我们来简单的总结一下组合模式,组合模式将对象组合成树形结构来展示“部分整体”的层次结构,并且使用户在处理单个对象和组合对象时具有一致的行为,另外,我们一般优先采用透明的方式来使用组合模式,虽然牺牲了安全性,但是对于单个对象和组合对象具有一致性,当然,也要按照实际情况来说,不能一味的使用透明策略,有时也是需要使用安全策略的。

  OK,组合模式暂时就到这里了,我们下期再见;

参考文章

本文由威尼斯www.9778.com发布于服务器&运维,转载请注明出处:《大设》第二十一篇之组合模式

关键词:

JavaScript 设计模式之组合模式解析_js面向对象_脚本之家

威尼斯www.9778.com,定义 怎么说呢?!就像是动物时,它的后代就有了某种功能;也像是一棵树,它有一个根然后是从...

详细>>

JQuery解析XML的方法小结,jqueryxml小结

用JavaScript解析XML数据是常见的编程任务,JavaScript能做的,JQuery当然也能做。下面我们来总结几个使用JQuery解析XML的例...

详细>>

给十八岁的自己

$.window('refresh','url01.php');$.window;$.window;$.window('refresh','url01.php'); 希望十八岁的你坦坦荡荡与所有的恶人无关 尽管一切...

详细>>

javascript 11个才具

这篇文章中将给大家分享12个有关于JavaScript的小技巧。这些小技巧可能在你的实际工作中或许能帮助你解决一些问题...

详细>>