Jupyterhub安装流程

因业务需要,需要搭建JupyterHub的服务,按照官方的文档和其它教程,进行了一下Demo实践,过程中也踩了许多的坑,本文对安装流程进行整理,便于后期使用。
注:本文测试环境为Ubuntu16.04

安装Anaconda

JupyterHub是基于Python的,所以我们需要安装一下Python的相关环境。Anaconda是一个非常成熟的Python包管理工具,因此本文档选用该工具进行基础环境的安装,首先就是安装该工具。

下载安装文件

wget https://repo.continuum.io/archive/Anaconda3-4.4.0-Linux-x86_64.sh

执行安装命令

bash Anaconda3-4.4.0-Linux-x86_64.sh

安装过程中会询问你是否将anaconda的路径加入到环境变量中,默认是no,所以如果在安装的过程中手太快,一键到底了的话,可以通过手动添加的方式进行设置。

将anaconda加入环境变量

//1 打开
vi ~/.bashrc
//2 修改
在bashrc文件的最后添加:export PATH="路径/anaconda3/bin:$PATH"。(vi编辑器中按i进入编辑模式)
//3 保存
添加完按esc退出编辑模式,并按:x(保存并退出)
//4 更新环境变量
source ~/.bashrc

检测是否安装成功

检测Anaconda是否安装成功:

conda list

如果提示conda: command not found,请参考是否将Anaconda加入环境变量,并且更新生效。

Jupyterhub安装

只需要一条命令:

conda install -c conda-forge jupyterhub

检测安装是否成功

jupyterhub -h
configurable-http-proxy -h

创建配置文件

执行如下命令,可以在当前目录下生成默认配置文件:

jupyterhub --generate-config

一个简单的配置文件demo:

c.JupyterHub.ip = '你的IP地址'
c.JupyterHub.port = 所使用的端口
c.PAMAuthenticator.encoding = 'utf8'

//白名单
c.Authenticator.whitelist = {'jupyter1', 'jupyter2', 'jupyter3'} 

//管理用户
c.LocalAuthenticator.create_system_users = True
c.Authenticator.admin_users = {'jupyter1'}

c.Spawner.cmd=['jupyterhub-singleuser']
c.JupyterHub.statsd_prefix = 'jupyterhub'

注意:这里在白名单和管理用户都提到的访问的用户,这里的用户实际上就是我们Ubuntu服务器的用户,如果我们配置的用户没在系统当中,则会自动创建,创建后需要我们手工的指定用户密码,用于用户后期登录jupyterhub。

开启服务

jupyterhub --config=/etc/jupyterhub/jupyterhub.py --no-ssl

参数说明:

  • --config:读取配置文件的地址,默认会检测当前目录
  • --no-ssl:在不使用ssl的情况下启动,因为目前实验较为简单,ssl相关暂未配置,这个坑后面补。

至此,我们就可以通过浏览器对jupyterhub进行访问了。进一步的配置和使用,将会逐步更新。

2017/10/26 posted in  其它
 

2017/10/26
 

Java研发工程师的面试宝典

Java基础

面向对象和面向过程的区别?

面向过程

  • 定义:
  • 优点:性能比面向对象高,因为类调用时需要实例化
  • 缺点:没有面向对象易维护,易复用,易扩展

面向对象

  • 定义:面向对象程序设计是种具有对象概念的程序编程范型,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象。

Java的四个基本特性?

  • 抽象:就是把现实生活中的某一类东西提取出来,用程序代码进行表示,通常叫做类或者接口。抽象包括于两个方面:一个是数据抽象,一个是过程抽象。数据抽象也就是对象的属性,过程抽象是对象的行为特征。
  • 封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行隐藏。封装也分为属性的封装和方法的封装。
  • 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。
  • 多态:允许不同的子类对象对同一消息做出响应。即父类引用指向子类对象。

重载(Overload)和重写(Override)的区别?

  • 重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
  • 重写:发生在父子继承关系中,方法名和参数列表必须相同,返回值可以小于等于父类定义的返回值,抛出异常小于等于父类,访问修饰符大于等于父类。

构造器Constructor是否可被重写

不可以。构造器不能被重写,且不能用static修饰,只能添加private、protected、public在三个权限修饰符,且不能有返回语句。

访问控制符private、default、protected、public的区别

  • private修饰的方法和属性只能在本类中被访问
  • default修饰的方法和属性在同包下可访问
  • protected修饰的方法和属性在同包和子类中可访问
  • public修饰的方法和属性是公开的

是否可以继承String类?为什么?

不能。String类是final关键字修饰的,所以不可继承。

String、StringBuilder、StringBuffer的区别?

  • 可变性:
    • 不可变对象:String
    • 可变对象:StringBuilder、StringBuffer
  • 线程安全性:
    • 线程安全的:StringBuffer
    • 线程不安全:StringBuilder

hashCode()和equals()的关系

如果两个对象equals()的返回值为true,那么他们应该有同样的hashCode值。反之,若两个对象由相同的hashCode他们可能不相等(equals()返回false)。

抽象类和接口的区别

  • 语法层次:抽象类和接口的定义方式有区别
  • 抽象层次:抽象类是对对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。
  • 跨域不同:抽象类所体现的是一种继承关系,想要使得继承关系合理,父类和子类之间必须存在“is-a”的关系,即父类和子类在概念本质上应该是相同的。对于接口来说,并不要求接口的实现者和接口定义在概念本质上是一致的,仅仅是实现了接口定义的契约而已,表达“like-a”关系。

Java中的基本类型?自动装箱与拆箱?

  • Java中的基本类型有8种:byte、short、int、long、float、double、boolean、char;
  • 对应的包装类类别:Byte、Short、Integer、Long、Float、Double、Boolean、Character
  • 装箱:将基本应用类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本类型;
  • Java使用自动装箱和拆箱机制,节省了常用数值的内存开销和创建对象的开销,提升了效率。由编译器来完成,编译器在编译期间根据语法决定是否进行装箱和拆箱操作。

什么是泛型?为什么要使用泛型?什么是类型擦除?

  • 泛型即参数化类型。创建集合时就指定集合元素的类型,确保该集合只能保存其指定的类型元素,避免使用强制类型转换。
  • Java编译器生成的字节码是不包含泛型信息的,泛型类型信息将在编译处理时被擦除,这个过程被称为类型擦除。类型擦除的主要过程如下:
    1. 将所有的泛型参数用其最左边(最顶级的父类型)类型替换。
    2. 移除所有的泛型信息。

Java中的集合类及关系图

  • List和Set继承自Collection接口
    • Set无序、不允许重复元素。HashSet和TreeSet是两个主要的实现类;
    • List有序,运行重复元素。ArrayList和LinkedList是两个主要的实现类;
  • Map也属于集合系统,但是没有实现Collection接口。Map主要表示Key对Value的映射集合。Key值不可重复,但是Vaule可以重复。HashMap和TreeMap是两个主要的实现类。

HashMap的实现原理?

待填坑

Hashtable的实现原理?

待填坑

HashMap和Hashtable的区别?

Hashtable如何实现线程安全?

ArrayList和Vector的区别?

ArrayList和LinkedList的区别和使用场景?

区别

  • ArrayList基于数组实现,可以简单的认为ArrayList是一个大小可变的数组,随着越来越多的元素被添加到ArrayList中,其规模动态增加。
  • LinkedList基于双向链表实现,相较于ArrayList,元素增删的速度较快,但是随机访问的速度较慢,且LinkedList实现了Queue接口,还可以作为队列使用。

使用场景

  • LinkedList适用于频繁增删的场景
  • ArrayList适用于检索频繁的场景

Collection和Collections的区别?

  • java.util.Collection是一个集合接口。他提供了对集合对象进行基本操作的通用接口方法,在Java类库中很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
  • java.util.Collections是一个包装类。它包含各种有关机和操作的静态方法,此类不能实例化,就像一个工作类,服务于Java的集合框架

ConcurrentHashMap的实现原理

Java的异常机制?Error、Exception的区别?

在Java中,所有的异常都有一个共同的祖先Throwable。Throwable指定代码中可用异常传播机制通过Java应用程序传输的任何问题的共性。Throwable有两个重要的子类:

  • Error:一般指与虚拟机相关的问题,如系统崩溃、虚拟机错误、内存空间不足、方法调用栈溢出。对于这类错误导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误建议程序终止。
  • Exception:表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能的处理异常,使程序恢复运行,而不会随意终止。

Unchecked Exception和Checked Exception?

  • Unchecked Exception
    • 指的是程序的瑕疵或逻辑错误,并在运行时无法恢复
    • 包括Error和RuntimeException及其子类
    • 语法上不需要声明抛出异常
  • Checked Exception
    • 代表程序不能直接控制的无效界外情况(如用户输入、数据库问题、网络异常、文件丢失等)
    • 除了Error和RuntimeException及其子类之外
    • 需要使用try-catch处理或throws声明抛出异常

JavaEE

Servlet生命周期和各个方法?

Servlet的生命周期

Servlet的生命周期分为四个部分,分别是:加载->实例化->服务->销毁

主要方法

  • init():在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行,负责初始化Servlet对象。可以通过配置服务器配置,以在服务器启动或在客户机首次访问Servlet时装入Servlet对象。无论有多少客户机访问Servlet,都不会重复执行init()方法。
  • service():Servlet的核心方法,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的service()方法就要调用,而且传递给这个方法一个ServletRequest对象和一个ServletResponse对象作为参数。
  • destory():仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其它的线程,因此需要确认在调用destory()方法时,这些线程已经终止或者完成。

Servlet中如何定义Filter?

2017/8/26 posted in  Java
 

设计模式-适配器模式

将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

与电源适配器相似,在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。
适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无须修改原来的适配者接口和抽象目标类接口。

角色

在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。在实际开发中,对象适配器的使用频率更高,对象适配器模式结构如图所示:

在对象适配器模式结构图中包含如下几个角色:

  • Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
  • Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
  • Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

实现

根据对象适配器模式结构图,在对象适配器中,客户端需要调用request()方法,而适配者类Adaptee没有该方法,但是它所提供的specilRequest()方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的specilRequest()方法。因为适配器类与适配者类是关联关系(也可称之为委派关系),所以这种适配器模式称为对象适配器模式。典型的对象适配器代码如下所示:

public class Adapter implements Target {

    // 适配者
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        adaptee.specilRequest();
    }

}

类适配器

除了对象适配器模式之外,适配器模式还有一种形式,那就是类适配器模式,类适配器模式和对象适配器模式最大的区别在于适配器和适配者之间的关系不同,对象适配器模式中适配器和适配者之间是关联关系,而类适配器模式中适配器和适配者是继承关系,类适配器模式结构如图所示:

根据类适配器模式结构图,适配器类实现了抽象目标类接口Target,并继承了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,实现了适配。

class Adapter extends Adaptee implements Target {  
    public void request() {  
        specificRequest();  
    }  
}  

由于Java、C#等语言不支持多重类继承,因此类适配器的使用受到很多限制,例如如果目标抽象类Target不是接口,而是一个类,就无法使用类适配器;此外,如果适配者Adapter为最终(Final)类,也无法使用类适配器。在Java等面向对象编程语言中,大部分情况下我们使用的是对象适配器,类适配器较少使用。

双向适配器

在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器,其结构示意图如图所示:

class Adapter implements Target,Adaptee {  
    //同时维持对抽象目标类和适配者的引用  
    private Target target;  
    private Adaptee adaptee;  
      
    public Adapter(Target target) {  
        this.target = target;  
    }  
      
    public Adapter(Adaptee adaptee) {  
        this.adaptee = adaptee;  
    }  
      
    public void request() {  
        adaptee.specificRequest();  
    }  
      
    public void specificRequest() {  
        target.request();  
    }  
}  

总结

适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用,它是一种使用频率非常高的设计模式,在软件开发中得以广泛应用,在spring等开源框架、驱动程序设计(如JDBC中的数据库驱动程序)中也使用了适配器模式。

主要优点

无论是对象适配器模式还是类适配器模式都具有如下优点:

  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  2. 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  3. 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

具体来说,类适配器模式还有如下优点:

  1. 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还有如下优点:

  1. 一个对象适配器可以把多个不同的适配者适配到同一个目标;
  2. 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

主要缺点

类适配器模式的缺点如下:

  1. 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
  2. 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
  3. 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

对象适配器模式的缺点如下:

  1. 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

适用场景

在以下情况下可以考虑使用适配器模式:

  1. 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  2. 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

实现

相关代码Github地址
文章参考-刘伟

2017/3/16 posted in  Java
 

设计模式-命令模式

将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

在软件开发中,我们经常需要向某些对象发送请求(调用其中的某个或某些方法),但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,此时,我们特别希望能够以一种松耦合的方式来设计软件,使得请求发送者与请求接收者能够消除彼此之间的耦合,让对象之间的调用关系更加灵活,可以灵活地指定请求接收者以及被请求的操作。命令模式为此类问题提供了一个较为完美的解决方案。
命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。

命令模式的定义比较复杂,提到了很多术语,例如“用不同的请求对客户进行参数化”、“对请求排队”,“记录请求日志”、“支持可撤销操作”等,在后面我们将对这些术语进行一一讲解。
命令模式的核心在于引入了命令类,通过命令类来降低发送者和接收者的耦合度,请求发送者只需指定一个命令对象,再通过命令对象来调用请求接收者的处理方法,其结构如图所示:

角色

在命令模式结构图中包含如下几个角色:

  • Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
  • ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。
  • Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
  • Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。

命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。在最简单的抽象命令类中只包含了一个抽象的execute()方法,每个具体命令类将一个Receiver类型的对象作为一个实例变量进行存储,从而具体指定一个请求的接收者,不同的具体命令类提供了execute()方法的不同实现,并调用不同接收者的请求处理方法。

实现

典型的抽象命令类代码如下所示:

public abstract class Command {
    public abstract void execute();
}

对于请求发送者即调用者而言,将针对抽象命令类进行编程,可以通过构造注入或者设值注入的方式在运行时传入具体命令类对象,并在业务方法中调用命令对象的execute()方法,其典型代码如下所示:

public class Invoker {
    private Command command;

    public Invoker(Command command) {
        this.command = command;
    }

    public void setCommand(Command command) {
        this.command = command;
    }

    public void call() {
        this.command.execute();
    }
}

具体命令类继承了抽象命令类,它与请求接收者相关联,实现了在抽象命令类中声明的execute()方法,并在实现时调用接收者的请求响应方法action(),其典型代码如下所示:

public class ConcreteCommand extends Command {
    /**
     * 命令接收者
     */
    private Receiver receiver;

    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }

    public void setReceiver(Receiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        receiver.action();
    }

}

请求接收者Receiver类具体实现对请求的业务处理,它提供了action()方法,用于执行与请求相关的操作,其典型代码如下所示:

public class Receiver {
    public void action() {
        System.out.println("做点有用的事情");
    }
}

命令队列

有时候我们需要将多个请求排队,当一个请求发送者发送一个请求时,将不止一个请求接收者产生响应,这些请求接收者将逐个执行业务方法,完成对请求的处理。此时,我们可以通过命令队列来实现。
命令队列的实现方法有多种形式,其中最常用、灵活性最好的一种方式是增加一个CommandQueue类,由该类来负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者,CommandQueue类的典型代码如下所示:

public class CommandQueue {
    private ArrayList<Command> commands = new ArrayList<Command>();

    public void addCommand(Command command) {
        commands.add(command);
    }

    public void removeCommand(Command command) {
        commands.remove(command);
    }

    // 循环调用每一个命令对象的execute()方法
    public void execute() {
        for (Command command : commands) {
            command.execute();
        }
    }
}

在增加了命令队列类CommandQueue以后,请求发送者类Invoker将针对CommandQueue编程,代码修改如下:

public class CommandQueueInvoker {
    private CommandQueue commandQueue; // 维持一个CommandQueue对象的引用

    // 构造注入
    public CommandQueueInvoker(CommandQueue commandQueue) {
        this.commandQueue = commandQueue;
    }

    // 设值注入
    public void setCommandQueue(CommandQueue commandQueue) {
        this.commandQueue = commandQueue;
    }

    // 调用CommandQueue类的execute()方法
    public void call() {
        commandQueue.execute();
    }
}

命令队列与我们常说的“批处理”有点类似。批处理,顾名思义,可以对一组对象(命令)进行批量处理,当一个发送者发送请求后,将有一系列接收者对请求作出响应,命令队列可以用于设计批处理应用程序,如果请求接收者的接收次序没有严格的先后次序,我们还可以使用多线程技术来并发调用命令对象的execute()方法,从而提高程序的执行效率。

撤销

在命令模式中,我们可以通过调用一个命令对象的execute()方法来实现对请求的处理,如果需要撤销(Undo)请求,可通过在命令类中增加一个逆向操作来实现。
除了通过一个逆向操作来实现撤销(Undo)外,还可以通过保存对象的历史状态来实现撤销,后者可使用备忘录模式(Memento Pattern)来实现。

总结

命令模式是一种使用频率非常高的设计模式,它可以将请求发送者与接收者解耦,请求发送者通过命令对象来间接引用请求接收者,使得系统具有更好的灵活性和可扩展性。在基于GUI的软件开发,无论是在电脑桌面应用还是在移动应用中,命令模式都得到了广泛的应用。

主要优点

命令模式的主要优点如下:

  1. 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
  2. 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
  3. 可以比较容易地设计一个命令队列或宏命令(组合命令)。
  4. 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。

主要缺点

命令模式的主要缺点如下:

使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。

适用场景

在以下情况下可以考虑使用命令模式:

  1. 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
  2. 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
  3. 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
  4. 系统需要将一组操作组合在一起形成宏命令。

实现

相关代码Github地址
文章参考-刘伟

2017/3/14 posted in  Java