23种设计模式

23中设计模式

1、单例模式

  1.定义:

  单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。

  2.特点:

  1、单例类只能有一个实例;

  2、单例类必须自己创建自己的唯一实例;

  3、单例类必须给所有其他对象提供这一实例。

  3.单例模式的要点:

  1、私有的构造方法;

  2、指向自己实例的私有静态引用;

  3、以自己实例为返回值的静态的共有的方法。

  4.根据实例化对象实际的不同分为两种:

  一种是饿汉式单例,一种是懒汉式单例。

  饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;而懒汉式在调用取得实例方法的时候才会实例化对象。

  代码如下:

  饿汉式单例

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return singleton;
    }
}

  懒汉式单例

public class Singleton {
    private static Singleton singleton;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        if(singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

  单例模式还有一种比较常见的形式:双重锁的形式

public class Singleton{
    private static volatile Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

  双重校验锁模式将同步内容下放到 if 内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步,创建了以后就没必要了。

  这种模式中双重判断加同步的方式,比第一个例子中的效率大大提升,因为如果单层if判断,在服务器允许的情况下,假设有一百个线程,耗费的时间为100*(同步判断时间+if判断时间),而如果双重if判断,100的线程可以同时if判断,理论消耗的时间只有一个if判断的时间。

  所以如果面对高并发的情况,而且采用的是懒汉模式,最好的选择就是双重判断加同步的方式。

  5.单例模式的优点:

  1、在内存中只有一个对象,节省内存空间;

  2、避免频繁的创建销毁对象,可以提高性能;

  3、避免对共享资源的多重占用;

  4、可以全局访问。

  6.单例模式的缺点:

  1、扩展困难,由于getInstance 静态函数没法生成子类的实例。如果要拓展,只有重写那个类;

  2、隐式使用引用类结构不清晰;

  3、导致程序内存泄漏的问题。

  7.适用场景:

  由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。以下为使用单例模式的场景:

  1、需要频繁实例化然后销毁的对象;

  2、创建资源时耗时过多或者耗资源过多,但又经常用到的对象;

  3、资源共享的情况下,避免由于资源操作时导致的性能损耗;

  4、控制资源的情况下,方便资源之间的互相通信。

  8.单例模式注意事项:

  只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。

  不要做断开单例类对象与类中静态引用的危险操作。

  多线程使用单例使用共享资源时,注意线程安全问题。

  9.关于单例模式的一些常见问题:

  单例模式的对象长时间不用会被jvm垃圾收集器收集吗

  除非人为地断开单例中静态引用到单例对象的联接,否则jvm垃圾收集器是不会回收单例对象的。

  jvm卸载类的判定条件如下:

  1,该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。

  2,加载该类的ClassLoader已经被回收。

  3,该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

  只有三个条件都满足,jvm才会在垃圾收集的时候卸载类。显然,单例的类不满足条件一,因此单例类也不会被回收。

  在一个jvm中会出现多个单例吗

  在分布式系统、多个类加载器、以及序列化的的情况下,会产生多个单例,这一点是无庸置疑的。那么在同一个jvm中,会不会产生单例呢?使用单例提供的getInstance()方法只能得到同一个单例,除非是使用反射方式,将会得到新的单例。

  代码如下:

Class c = Class.forName(Singleton.class.getName());  
Constructor ct = c.getDeclaredConstructor();  
ct.setAccessible(true);  
Singleton singleton = (Singleton)ct.newInstance();

  这样,每次运行都会产生新的单例对象。所以运用单例模式时,一定注意不要使用反射产生新的单例对象。

  在getInstance()方法上同步有优势还是仅同步必要的块更有优势?

  因为锁定仅仅在创建实例时才有意义,然后其他时候实例仅仅是只读访问的,因此只同步必要的块的性能更优,并且是更好的选择。

  缺点:只有在第一次调用的时候,才会出现生成2个对象,才必须要求同步。而一旦singleton 不为null,系统依旧花费同步锁开销,有点得不偿失。

  单例类可以被继承吗

  根据单例实例构造的时机和方式不同,单例模式还可以分成几种。但对于这种通过私有化构造函数,静态方法提供实例的单例类而言,是不支持继承的。

  这种模式的单例实现要求每个具体的单例类自身来维护单例实例和限制多个实例的生成。但可以采用另外一种实现单例的思路:登记式单例,来使得单例对继承开放。

2、工厂模式

  1.定义:

  工厂模式是Java中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

  工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

  2.工厂模式根据抽象程度的不同分为三类:

  简单工厂模式(静态工厂模式)

  工厂方法模型(多形性工厂)

  抽象工厂模式(工具箱)

  简单工厂模式:

  实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。

  工厂方法模式:

  工厂方法是粒度很小的设计模式,因为模式的表现只是一个抽象的方法。 提前定义用于创建对象的接口,让子类决定实例化具体的某一个类,即在工厂和产品中间增加接口,工厂不再负责产品的创建,由接口针对不同条件返回具体的类实例,由具体类实例去实现。

  抽象工厂模式:

  当有多个抽象角色时使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品对象。它有多个抽象产品类,每个抽象产品类可以派生出多个具体产品类,一个抽象工厂类,可以派生出多个具体工厂类,每个具体工厂类可以创建多个具体产品类的实例。

  工厂方法模式实际中应用最多。以工厂方法模式举例:

  抽象的产品类:定义car  交通工具

public interface Car {    
    void gotowork();
}

  定义实际的产品类,总共定义两个,bike 和bus 分别表示不同的交通工具类

public class Bike implements Car {
    @Override
    public void gotowork() {
        System.out.println("骑自行车去上班!");
    }
}

public class Bus implements Car {
    @Override
    public void gotowork() {
        System.out.println("坐公交车去上班!");
    }
}

  定义抽象的工厂接口

public interface ICarFactory {
    Car getCar();
}

  具体的工厂子类,分别为每个具体的产品类创建不同的工厂子类

public class BikeFactory implements ICarFactory {
    @Override
    public Car getCar() {
        return new Bike();
    }
}

public class BusFactory implements ICarFactory {    
@Override
    public Car getCar() {        
        return new Bus();
    }
}

  简单的测试类,来验证不同的工厂能够产生不同的产品对象

public class TestFactory {
    @Test
    public void test() {
        ICarFactory factory = null;
        // bike
        factory = new BikeFactory();
        Car bike = factory.getCar();
        bike.gotowork();

        // bus
        factory = new BusFactory();
        Car bus = factory.getCar();
        bus.gotowork();
    }
}

  3.工厂模式的优点:

  1、一个调用者想创建一个对象,只要知道其名称即可,降低了耦合度;

  2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以,使得代码结构更加清晰;

  3、屏蔽产品的具体实现,调用者只关心产品的接口。

  4.工厂模式的缺点:

   每次增加一个产品时,都需要增加一个具体类和对象实现工厂(这里可以使用反射机制来避免),使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。所以对于简单对象来说,使用工厂模式反而增加了复杂度。

  5.工厂模式的适用场景:

  1、一个对象拥有很多子类;

  2、创建某个对象时需要进行额外的操作;

  3、系统后期需要经常扩展,它把对象实例化的任务交由实现类完成,扩展性好。

  6.关于Java中的工厂模式的一些常见问题:

  利用父类的向下转型(使用父类类型的引用指向子类的对象)是可以达到类似于工厂模式的效果的,那为什么还要用工厂模式呢?

  把指向子类对象的父类引用赋给子类引用叫做向下转型,如:

Class Student extends Person     
Person s = new Student();    
s = (Student)person ;

  使用向下转型在客户端实例化子类的时候,严重依赖具体的子类的名字。当我们需要更改子类的构造方法的时候,比如增加一个参数,或者更改了子类的类名,所有的new出来的子类都需要跟着更改。

  但如果我们使用工厂模式,我们仅仅需要在工厂中修改一下new的代码,其余项目中用到此实例的都会跟着改,而不需要我们手动去操作。(理解可参见2中示例)

  总结:

  无论是简单工厂模式、工厂模式还是抽象工厂模式,它们本质上都是将不变的部分提取出来,将可变的部分留作接口,以达到最大程度上的复用。究竟用哪种设计模式更适合,这要根据具体的业务需求来决定。

 3、原型模式

  1.定义:

  通过复制现有的对象实例来创建新的对象实例。

  2.实现:

  实现Cloneable接口:

  Cloneable接口的作用是在运行时通知虚拟机可以安全地在实现了该接口的类上使用clone方法。在Java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则咋运行时会抛出CloneNotSupportedException异常。

  重写Object类中的clone方法:

  Java中,所有类的父类都是Object类,Object类有一个clone方法,作用是返回对象的一个拷贝,但是其作用域是protected类型的,一般的类无法调用,因此,原型类需要将clone方法的作用域修改为public类型。

public class Object {
    ...
    protected native Object clone() throws CloneNotSupportedException;
    ...
}

  3.示例:

  例如,对于拿邮件发邀请函,邮件类大部分内容都是一样的:邀请原由、相邀地点,相聚时间等等,但对于被邀请者的名称和发送的邮件地址是不同的。

  定义Mail类:

public class Mail implements Cloneable {    
    private String receiver;    
    private String subject;    
    private String content;    
    private String tail;    
    public Mail(EventTemplate et) {        
        this.tail = et.geteventContent();        
        this.subject = et.geteventSubject();
    }    
    @Override
    public Mail clone() {
        Mail mail = null;        
        try {
            mail = (Mail) super.clone();            
        } catch (CloneNotSupportedException e) {            
        // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return mail;
    }
    //get、set.....
}

  测试方法:

public static void main(String[] args) {
    int i = 0;
    int MAX_COUNT = 10;
    EventTemplate et = 
new EventTemplate("邀请函(不变)", "婚嫁生日啥的....(不变部分)");
    Mail mail = new Mail(et);    
    while (i < MAX_COUNT) {
        Mail cloneMail = mail.clone();
        cloneMail.setContent("XXX先生(女士)(变化部分)"
     + mail.getTail());
        cloneMail.setReceiver("每个人的邮箱地址...com(变化部分)");
        sendMail(cloneMail);
        i++;
    }

}

  4.优点:

  1,使用原型模型创建一个对象比直接new一个对象更有效率,因为它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

  2,隐藏了制造新实例的复杂性,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。

  5.缺点:

  1,由于使用原型模式复制对象时不会调用类的构造方法,所以原型模式无法和单例模式组合使用,因为原型类需要将clone方法的作用域修改为public类型,那么单例模式的条件就无法满足了。

  2,使用原型模式时不能有final对象。

  3,Object类的clone方法只会拷贝对象中的基本数据类型,对于数组,引用对象等只能另行拷贝。这里涉及到深拷贝和浅拷贝的概念。

  6.深拷贝与浅拷贝

  浅拷贝:

  将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的(这样不安全)。

  深拷贝:

  将一个对象复制后,不论是基本数据类型还是引用类型,都是重新创建的。

  那么深拷贝如何具体实现呢?

  继续上例,增加一个ArrayList属性

private String receiver;
private String subject;
private String content;
private String tail;
private ArrayList<String> ars;

  此时,单mail = (Mail) super.clone();无法将ars指向的地址区域改变,必须另行拷贝:---对象拷贝时,其中包含的对象另行拷贝

try {
       mail = (Mail) super.clone();       
       mail.ars = (ArrayList<String>)this.ars.clone();
      } catch (CloneNotSupportedException e) {
          e.printStackTrace();
}

  7.适用场景:

  1,复制对象的结构和数据。

   2,希望对目标对象的修改不影响既有的原型对象。

  3,创建一个对象的成本比较大。

4、生成器模式

  1.定义:

  将一个复杂对象的构建与它的表示分离,使得同样的构造过程可以创建不同的表示。生成器模式利用一个导演者对象和具体建造者对象一个一个地创建出所有的零件,从而建造出完整的对象。(参见 Quartz 2.x 框架源码,其中定时作业调度各个组件的设置,采用Builder生成器 完成构建;同时,Spring Security 4 使用HttpSecurity构造DefaultSecurityFilterChain也使用到了生成器模式)

  2.四个要素:

  Builder:生成器接口,定义创建一个Product对象所需要的哥哥部件的操作。

  ConcreteBuilder:具体的生成器实现,实现各个部件的创建,并负责组装Product对象的各个部件,同时还提供一个让用户获取组装完成后的产品对象的方法。

  Director:指导者,也叫导向者,主要用来使用Builder接口,以一个统一的过程来构建所需要的Product对象。

  Product:产品,表示被生成器构建的复杂对象,包含多个部件。

  3.示例:

  网上有用KFC的例子来描述生成器模式,比较通俗易懂。

  假设KFC推出两种套餐:奥尔良鸡腿堡套餐和香辣鸡腿堡套餐。

  奥尔良套餐包括:一个奥尔良鸡腿堡、一个炸鸡翅、一杯雪碧。

  鸡腿堡套餐包括:一个香辣鸡腿堡、一份薯条、一杯可乐。

  每份套餐都是:主食、副食、饮料。

   KFC服务员要根据顾客的要求来提供套餐,那这个需求里面什么是固定的,什么是变化的呢?很明显顾客都是要的套餐,顾客的目的是一样的。 套餐里面都是主食、副食、饮料,这也是固定的。至于主食是什么、副食是什么、饮料是什么,这个是变化的。

  在实际的软件开发过程中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象采用一定的组合构成,由于需求的变化,这个复杂对象的各个部分或者其子对象经常要变化(例如,鸡腿堡套餐的顾客不喜欢可乐,要换奶茶),但是他们的结构却相对稳定(套餐都得是一份主食,副食及饮料)。当遇到这种场景时,使用生成器模式比较合适。

  定义一个产品类:

public class Entity1{...}
public class Entity2{...}
public class Entity3{...}
public class Product{
      Entity1 entity1;
      Entity2 entity2;
      Entity3 entity3;
}

  产品类中的各个小模块是不一样的,由他们建造组成产品。

  根据具体场景要求,定义n个生成器类:

public interface IBuild{      
    public void createEntity1();      
    public void createEntity2();     
    public void createEntity3();      
    public Product composite();      
    public Product create();    
}
public class BuildProduct implements IBuild{
      Product p = new Product();
      public void createEntity1(){ 
      //p.entity1 = ...  
      }      
      public Product create(){ 
         return composite();
      }  
      ......
}
public class BuildProduct1 implements IBuild{
      Product p = new Product();                       
      public void createEntity1(){ 
                //p.entity1 = ...  
      }  
      ......
}

  定义一个指挥者类,统一调度project:

public class Director{ 
     private IBuild build;
     public Director(IBuild build){ 
            this.build = buid;  
      }     
     public Product build(){
           build.create();
      }     
     public static void main(){
         IBuild build = new BuildProduct();
         Director direcotr = new Director(build);
         Prodcut p = director.build();   
      }
}

  4.优点:

  1,使用生成器模式可以使客户端不必知道产品内部组成的细节。

  2,具体的建造者类之间是相互独立的,对系统的扩展非常有利。

  3,由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。

  5.缺点:

  建造者模式的“加工工艺”是暴露的,这样使得建造者模式更加灵活,也使得工艺变得对客户不透明。

   6.应用场景

  1,需要生成一个产品对象有复杂的内部结构。每一个内部成分本身可以是对象,也可以使一个对象的一个组成部分。

  2,需要生成的产品对象的属性相互依赖。建造模式可以强制实行一种分步骤进行的建造过程。

  3,在对象创建过程中会使用到系统中的其他一些对象,这些对象在产品对象的创建过程中不易得到。

5、适配器模式

  1.定义:

  将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。(实际应用参见:Spring MVC - DispatcherServlet.doDispatch(...) 其中 HandlerAdapter.handle(...)的使用,对应对象适配器模式的使用,参见其实现类,如SimpleControllerHandlerAdapter,使用HandlerAdapter接口的方法调用Controller类的handlerRequest方法。)

  2.角色:

  目标(Target)角色:这就是所期待得到的接口,也就是这类的接口是符合我们要求的。

  源(Adapee)角色:我们要使用的接口,但是这个接口不符合我们的要求,也就是现在需要适配的接口。

  适配器(Adaper)角色:适配器类是适配器模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

  3.分类:

  1、类配置器模式

class Adaptee {         
    public void specificRequest() {
         System.out.println("特殊请求,这个是源角色");
    }
}
/*这个是目标角色,所期待的接口*/

interface Target {         
    public void request();
}

  现在想要实现这个Target接口,但是不想重构,想要用上已有的Adaptee类,这时可以定义一个适配器类,继承想要使用的类,并且实现期待的接口。

class Adapter extends Adaptee implementsTarget{
         public void request() {
                   super.specificRequest();
         }
}

  这样,使用适配器类和实现目标接口就完成了计划,测试:

public class Test{
         public static void main(String[] args) {
                   //使用特殊功能类,即适配类
                   Targetadapter = new Adapter();
                   adapter.request();
         }
}

  2、对象适配器模式

  适配器类关联已有的Adaptee类,并且实现标准接口,这样做的好处是不再需要继承。

class Adapter implements Target{
         private Adaptee adaptee;

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

         public void request() {
                   this.adaptee.specificRequest();
         }
}

  此时输出结果和类适配器模式是相同的,测试:

public class Test{
         publicstatic void main(String[] args) {
                   Targetadapter = new Adapter(new Adaptee());
                   adapter.request();
         }
}

  区别:

  对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。

  4.优点:

  复用性:

  系统需要使用现有的类,而此类的接口不符合系统的需要。那么需要通过适配器模式就可以让这些功能得到更好的复用。

  扩展性:

  在实现适配器功能的时候,可以自由调用自己开发的功能,从而自然地扩展系统的功能。

  5.缺点:

  过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现。所以适配器模式不适合在详细设计阶段使用它,它是一种补偿模式,专用来在系统后期扩展、修改时所用。

  6.适用场景:

  1、已经存在的类的接口不符合我们的需求;

  2、创建一个可以复用的类,使得该类可以与其他不相关的类或不可预见的类协同工作;

  3、使用一些已经存在的子类而不需要对其进行子类化来匹配接口。

  4、旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。

  小结:

  适配器模式不适合在详细设计阶段使用它,它是一种补偿模式,专用于在系统后期扩展、修改时所用,适配器模式更像是一种补救措施。

 6、装饰者模式

  1.定义:

  在不必改变原类文件和原类使用的继承的情况下,动态地扩展一个对象的功能。

  它是通过创建一个包装对象,也就是用装饰来包裹真实的对象来实现。

  2.角色:

  抽象构件角色(Project):给出一个接口,以规范准备接收附加责任的对象。

  具体构建角色(Employee):定义一个将要接收附加责任的类。

  装饰角色(Manager):持有一个构建对象的实例,并定义一个与抽象构件接口一致的接口。

  具体装饰角色(ManagerA、ManagerB):负责给构件对象“贴上”附加的责任。

  3.示例:

  公共接口:

public interface Person {  
    void eat();  
}

  被装饰对象:

public class OldPerson implements Person {  
    @Override  
    public void eat() {  
        System.out.println("吃饭");  
    }  
} 

  装饰对象:

public class NewPerson implements Person {  
    private OldPerson p;  

    NewPerson(OldPerson p) {  
        this.p = p;  
    }  

    @Override  
    public void eat() {  
        System.out.println("生火");  
        System.out.println("做饭");  
        p.eat();  
        System.out.println("刷碗");   
    }  
}  

  测试:

public class PersonDemo {  
    public static void main(String[] args) {  
        OldPerson old = new OldPerson();  
        //old.eat(); 
        NewPerson np = new NewPerson(old);  
        np.eat();  
    }  
} 

  通过例子可以看到,没有改变原来的OldPerson类,同时也没有定义它的子类而实现了Person的扩展,这就是装饰者模式的作用。

  4.优点:

  1,使用装饰者模式比使用继承更加灵活,因为它选择通过一种动态的方式来扩展一个对象的功能,在运行时可以选择不同的装饰器,从而实现不同的行为。

   2,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。

  3,具体构件类与具体装饰类可以独立变化,他能是低耦合的。用户可以根据需要来增加新的具体构件类和具体装饰类,在使用时再对其进行各种组合,原有代码无须改变,符合“开闭原则”。

  5.缺点:

       1,会产生很多的小对象,增加了系统的复杂性

  2,这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

  6.装饰者与适配者模式的区别:

  1、适配者模式主要用来兼容那些不能在一起工作的类,是它们转化为可以兼容目标接口,虽然也可以实现和装饰者一样的增加新职责,但目的不在此。

  装饰者模式主要是给被装饰者增加新职责的。

  2、适配者模式是用新接口来调用原接口,原接口对新系统是不可见或者不可用的。

  装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。

  3、适配器是知道被适配者的详细情况的(就是哪个类或哪个接口)

  装饰者只知道其接口是什么,至于其具体类型(是基类还是其它派生类)只有在运行期间才知道。

  7、装饰者和继承的区别:

  继承:

    优点:代码结构清晰,而且实现简单。

    缺点:对于每一个的需要增加的类都要创建具体的子类来帮助其增强,这样会导致继承体系过于庞大。

  装饰者:

    优点:内部可以通过多态技术对多个需要增强的类进行增强。

    缺点:需要内部通过多态技术维护需要增强的类的实例,进而使得代码稍微复杂。且需要实现接口的所有方法。

  8、适用场景:

  1、需要扩展一个类的功能,或给一个类添加附加职责;

  2、需要动态的给一个对象添加功能,这些功能可能不明确或者暂时的,可以随时很方便的动态撤销掉;

  3、需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实;

  4、当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

7、代理模式

  1.定义:

  为其它对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

   2.角色:

  1,抽象角色:声明真实对象和代理对象的共同接口;

  2,代理对象:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其它的操作,相当于对真实对象进行封装;

  3,真实角色:代理角色所代表的真实对象,即最终要引用的对象。

  3.分类

  3.1 静态代理

  静态代理也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

  示例:

  抽象对象,真实对象和代理对象共同的接口

public interface UserInfo{    
       public void queryUser ();    
       public void updateUser ();      
}

  真实角色

public class UserImpl implementsUserInfo{    

       @Override    
       public void queryUser() {    
           //查询方法略...          
       }    

       @Override    
       public void updateUser() {    
            //修改方法略...          
       }    

}

  代理角色

public class UserProxy implementsUserInfo{    
   private UserInfo userImpl;    

   public AccountProxy(UserInfo userImpl) {    
       this.userImpl = userImpl;    
   }    

   @Override    
   public void queryUser() { 
         //这里可以扩展,增加一些查询之前需要执行的方法   
       //查询方法略...   
         //这里可以扩展,增加一些查询之后需要执行的方法         
   }    

   @Override    
   public void updateUser() {  
         //这里可以扩展,增加一些修改之前需要执行的方法    
       //修改方法略...   
         //这里可以扩展,增加一些修改之后需要执行的方法         
   }  
}

  使用代理之后如何调用它的方法?

public class Test {    
   public static void main(String[] args) {    
       UserInfo userImpl = new UserImpl();     
       UserInfo userProxy = new UserProxy(userImpl);     
       userProxy.queryUser(); 
       userProxy.updateUser();        
   }    
}

  3.2 动态代理

  动态代理类的源码是程序在运行期间由JVM根据反射等机制动态生成的,所以不存在代理类的字节码文件。代理角色和真实角色的联系在程序运行时确定。

  示例:

  抽象角色,真实对象和代理对象共同的接口

public interface UserInfo{    
       public void queryUser ();    
       public void updateUser ();      
}

  真实角色

public class UserImpl implementsUserInfo{    

       @Override    
       public void queryUser() {    
           //查询方法略...          
       }    

       @Override    
       public void updateUser() {    
            //修改方法略...          
       }    

}

  代理角色处理器:

public class UserHandler implements InvocationHandler {
    private UserInfo userImpl;
    public UserHandler(UserInfo userImpl2) {
        this.userImpl = userImpl2;
    }  
    @Override
    public Object invoke(Object proxy,Method method,Object[] args)
            throws Throwable {
        Object object = null;
        // 方法开始前做一些事情
        ...
        if (method.getName().equals("queryUser")) {
            // 激活调用的方法
            object = method.invoke(userImpl,args);
        }
        // 方法结束后做一些事情
        ...
        return object;
    }
}

  如何调用?

public class Test {         
   public static void main(String[] args) {
         UserInfouserImpl =new UserImpl();
         UserHandlerhandler = new UserHandler(userImpl);
         UserInfouserProxy = (UserInfo)Proxy.newProxyInstance
                      (ClassLoader.getSystemClassLoader(),
                        newClass[]{UserInfo.class}, handler);
         userProxy.queryUser();
         }
}

  4.优点

  业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。

  能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。

  5.缺点

  由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。

  实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。

原文地址:https://www.cnblogs.com/nyatom/p/10598779.html