• 复习宝典之设计模式


    查看更多宝典,请点击《金三银四,你的专属面试宝典》

    第四章:设计模式

    设计模式是前人(一般都是大师)对程序设计经验的总结,学习并应用设计模式可以使我们开发的项目更加规范、便于扩展和维护。

    1)面向对象四大特征

    封装:即将对象封装成一个高度自治和相对封闭的个体,对象状态(属性)由这个对象自己的行为(方法)来读取和改变。

    张三这个人,他的姓名等属性,要有自己提供的获取或改变的方法来操作。

    private name setName getName

    抽象:就是找出一些事物的相似和共性之处,然后将这些事物归为一个类,这个类只考虑这些事物的相似和共性之处,并且会忽略与当前主题和目标无关的那些方面,将注意力集中在与当前目标有关的方面。 就是把现实生活中的对象,抽象为类。

    继承:在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并可以加入若干新的内容,或修改原来的方法使之更适合特殊的需要,这就是继承。遗产继承

    多态:是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

    Object obj = new xxx();

    UserDao userDao = new UserDaoJdbcImpl();

    UserDao userDao = new UserDaoHibernateImpl();

    靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象(里氏替换原则),而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。

    扩展:一个程序就是一个世界,世界由不同的种类组成,每个类别下的个体就是一个个对象。封装是对对象属性的私有化,然后通过公开的方法进行访问。继承是is-a的关系,子类会继承父类的非私有属性与方法。多态分静态的多态(构造函数的重载)与动态的多态(编译时多态,里氏替换原则),表面意义是一种类型在不同情况下的不同形态【如类的多态,行为(接口)的多态】。

     

    2)五大设计原则

    单一职责原则(即一个类只负责一项职责

      单一职责原则(Single Responsibility Principle,SRP):就一个类而言,应该仅有一个引起它变化的原因。即一个类应该只负责一个功能领域中的相应职责。

      单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。

    开闭原则(一个类或方法应该对扩展开放,对修改关闭

      开闭原则(Open-Closed Principle,OCP): 是指软件实体(类、模块、函数等等)应该可以扩展,但是不可修改。即软件实体应该尽量在不修改原有代码的情况下进行扩展。

      为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。

    里氏替换原则(子类可以扩展父类的功能,但不能改变父类原有的功能,子类对象实例化父类对象

      里氏替换原则(Liskov Substitution Principle,LSP):所有引用父类的地方必须能够透明的使用子类的对象。即子类型必须能够替换掉它们的父类型。

      里氏替换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。同时,里氏代换原则是实现开闭原则的重要方式之一。

    它包含以下含义:

    1) 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

    2) 子类的方法重载父类的方法时,方法的入参要比父类方法的入参更宽松。

    3) 子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。

    依赖倒置原则(上层模块不应该依赖低层模块的实现,而应该依赖其抽象

      依赖倒置原则(Dependency Inversion Principle,DIP):抽象不应该依赖细节,细节应该依赖于抽象。即应该针对接口编程,而不是针对实现编程。

      在大多数情况下,我们会同时使用开闭原则、里氏代换原则和依赖倒转原则,开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段。

    依赖倒置的优点:减少类之间的耦合,提高系统的稳定性,减少并行开发引起的风险。

    接口隔离原则(一个类对另一个类的依赖应该建立在最小的接口上

      接口隔离原则(Interface Segregation Principle,ISP):使用专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

      根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。

     

    3)设计模式基础

    一般地设计模式分为三类共23种:

    3.1. 创建型模式

    单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。

    这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

    3.2. 结构型模式

    适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

    这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

    3.3. 行为型模式

    模版模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式。

    这些设计模式特别关注对象之间的通信。

     

    4)单例模式

    确保一个类最多只有一个实例,并提供一个全局访问点。

    有些对象我们只需要一个:线程池,缓存,硬件设备,如果多个实例会造成冲突,结果不一致等问题,可以用静态变量来实现,或者程序员之间协商一个全局变量。

    具体实现原理:可以将一个类的构造函数私有化,然后在该类内部创建自己的对象。

    饱汉模式:不管是否用到,都先创建好

    /**
     * @author: 肖德子裕
     * @date: 2019/02/28 20:50
     * @description: 饱汉模式
     */
    public class Singleton2 {
        private static Singleton2 uniqeInstance=new Singleton2();
    ​
        private Singleton2() {
    ​
        }
    ​
        public static Singleton2 getInstance(){
            return uniqeInstance;
        }
    }

     

    饥汉模式:需要时才创建

    /**
     * @author: 肖德子裕
     * @date: 2019/02/28 20:50
     * @description: 饥汉模式
     */
    public class Singleton {
        private static Singleton uniqeInstance=null;
    ​
        private Singleton() {
    ​
        }
    ​
        public static Singleton getInstance(){
            if (uniqeInstance==null){
                uniqeInstance=new Singleton();
            }
            return uniqeInstance;
        }
    }

     

    在多线程的情况下,可能2个线程同时访问该方法,这样的话就会创建2个实例对象,所以使用双重检查加锁解决该问题

    public static ChocolateFactory getInstance() {
        if (uniqueInstance == null) {
            synchronized (ChocolateFactory.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new ChocolateFactory();
                }
            }
        }
        return uniqueInstance;
    }

     

    扩展知识:

    synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

    1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

    2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

    3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

    4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

    修饰一个代码块:一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。

    当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

    创建了两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象中的synchronized代码(run);我们知道synchronized锁定的是对象,这时会有两把锁分别锁定syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。

    当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

    Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。

    在用synchronized修饰方法时要注意以下几点:

    1. synchronized关键字不能继承。

    2. 在定义接口方法时不能使用synchronized关键字。

    3. 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。

     

    5)工厂模式

    遵循依赖抽象原则:

    变量不要持有具体类的引用;不要让类继承自具体类,要继承自抽象类或接口;不要覆盖基类中已经实现的方法。

    5.1 简单工厂模式:

    定义了一个创建对象的类,由这个类来封装实例化对象的行为

    问题:如一家披萨店,在下订单时会根据不同的披萨实例化不同的对象,这样以后每次提出新品种或者下架某品种,就要去修改订单里面的实例化操作。

    如下可以定义一个简单的工厂类来封装实例化过程:

    /**
     * 简单工厂模式
     */
    public class SimplePizzaFactory {
        /**
         * 传入披萨的类型
         *
         * @param ordertype
         * @return
         */
        public Pizza CreatePizza(String ordertype) {
            Pizza pizza = null;
    ​
            if (ordertype.equals("cheese")) {
                pizza = new CheesePizza();
            } else if (ordertype.equals("greek")) {
                pizza = new GreekPizza();
            } else if (ordertype.equals("pepper")) {
                pizza = new PepperPizza();
            }
            return pizza;
        }
    }

     

    /**
     * 披萨订单模块
     */
    public class OrderPizza {
       SimplePizzaFactory mSimplePizzaFactory;
    ​
       public OrderPizza(SimplePizzaFactory mSimplePizzaFactory) {
          setFactory(mSimplePizzaFactory);
       }
    ​
       public void setFactory(SimplePizzaFactory mSimplePizzaFactory) {
          Pizza pizza = null;
          String ordertype;
          this.mSimplePizzaFactory = mSimplePizzaFactory;
          //工厂生产披萨并进行包装发货
          do {
             ordertype = gettype();
             pizza = mSimplePizzaFactory.CreatePizza(ordertype);
             if (pizza != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
             }
          } while (true);
       }
    ​
       private String gettype() {
          try {
             BufferedReader strin = new BufferedReader(new InputStreamReader(
                   System.in));
             System.out.println("input pizza type:");
             String str = strin.readLine();
             return str;
          } catch (IOException e) {
             e.printStackTrace();
             return "";
          }
       }
    }

     

    /**
     * 披萨店中购买披萨
     */
    public class PizzaStroe {
       public static void main(String[] args) {
          SimplePizzaFactory mSimplePizzaFactory;
          OrderPizza mOrderPizza;
          mOrderPizza=new OrderPizza(new SimplePizzaFactory());
       }
    }

     

    5.2 方法工厂模式:

    定义了一个创建对象的抽象方法,由子类决定要实例化的类。

    问题:如果一家披萨店要在全国开连锁,这样地方的配料肯定不一样,所以单纯的将工厂复制过去是不行的。

    如下可以将创建披萨的方法抽象化,让子类继承后去实例化具体的对象:

    /**
     * 抽象化下订单的模块
     */
    public abstract class OrderPizza {
        public OrderPizza() {
            Pizza pizza = null;
            String ordertype;
            do {
                ordertype = gettype();
                pizza = createPizza(ordertype);
    ​
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            } while (true);
        }
    ​
        abstract Pizza createPizza(String ordertype);
    ​
        private String gettype() {
            try {
                BufferedReader strin = new BufferedReader(new InputStreamReader(
                        System.in));
                System.out.println("input pizza type:");
                String str = strin.readLine();
                return str;
            } catch (IOException e) {
                e.printStackTrace();
                return "";
            }
        }
    }

     

    /**
     * 伦敦的披萨店
     */
    public class LDOrderPizza extends OrderPizza {
        @Override
        Pizza createPizza(String ordertype) {
            Pizza pizza = null;
            if (ordertype.equals("cheese")) {
                pizza = new LDCheesePizza();
            } else if (ordertype.equals("pepper")) {
                pizza = new LDPepperPizza();
            }
            return pizza;
        }
    }
    
    
    /**
     * 纽约的披萨店
     */
    public class NYOrderPizza extends OrderPizza {
        @Override
        Pizza createPizza(String ordertype) {
            Pizza pizza = null;
            if (ordertype.equals("cheese")) {
                pizza = new NYCheesePizza();
            } else if (ordertype.equals("pepper")) {
                pizza = new NYPepperPizza();
            }
            return pizza;
        }
    }
    
    
    /**
     * 披萨店中购买披萨
     */
    public class PizzaStroe {
       public static void main(String[] args) {
          OrderPizza mOrderPizza;
          mOrderPizza=new NYOrderPizza();
       }
    }

     

    5.3 抽象工厂模式

    定义了一个接口用于创建相关或有依赖关系的对象族,而无需明确指定具体类。

    /**
     * 抽象工厂模式
     */
    public interface AbsFactory {
       public Pizza CreatePizza(String ordertype) ;
    }

     

    /**
     * 伦敦工厂
     */
    public class LDFactory implements AbsFactory {
       @Override
       public Pizza CreatePizza(String ordertype) {
          Pizza pizza = null;
          if (ordertype.equals("cheese")) {
             pizza = new LDCheesePizza();
          } else if (ordertype.equals("pepper")) {
             pizza = new LDPepperPizza();
          }
          return pizza;
       }
    }

     

    /**
     * 纽约工厂
     */
    public class NYFactory implements AbsFactory {
       @Override
       public Pizza CreatePizza(String ordertype) {
          Pizza pizza = null;
          if (ordertype.equals("cheese")) {
             pizza = new NYCheesePizza();
          } else if (ordertype.equals("pepper")) {
             pizza = new NYPepperPizza();
          }
          return pizza;
       }
    }
    
    
    /**
     * 下订单的模块
     */
    public class OrderPizza {
       AbsFactory mFactory;
    ​
       public OrderPizza(AbsFactory mFactory) {
          setFactory(mFactory);
       }
    ​
       public void setFactory(AbsFactory mFactory) {
          Pizza pizza = null;
          String ordertype;
          this.mFactory = mFactory;
          do {
             ordertype = gettype();
             pizza = mFactory.CreatePizza(ordertype);
             if (pizza != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
             }
          } while (true);
       }
    ​
       private String gettype() {
          try {
             BufferedReader strin = new BufferedReader(new InputStreamReader(
                   System.in));
             System.out.println("input pizza type:");
             String str = strin.readLine();
             return str;
          } catch (IOException e) {
             e.printStackTrace();
             return "";
          }
       }
    }

     

    /**
     * 披萨店中购买披萨
     */
    public class PizzaStroe {
       public static void main(String[] args) {
          OrderPizza mOrderPizza;
          mOrderPizza=new OrderPizza(new LDFactory());
       }
    }

     

    6)策略模式

    分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设置行为对象。

    策略模式注重功能实现。

    原则:分离变化部分,封装接口,基于接口编程各种功能,此模式让行为算法的变化独立于算法的使用者。

    将行为定义为接口:

    /**
     * 飞行
     */
    public interface FlyBehavior {
       void fly();
    }

     

    具体的实现类:

    /**
     * 会飞
     */
    public class GoodFlyBehavior implements FlyBehavior {
        @Override
        public void fly() {
            System.out.println("--GoodFly--");
        }
    }

     

    /**
     * 不会飞
     */
    public class NoFlyBehavior implements FlyBehavior {
        @Override
        public void fly() {
            System.out.println("--NoFly--");
        }
    }

     

    如果还有其他行为也可以向上面一样,先定义接口,在实现,比如叫声可以分呱呱叫还有其他叫。

    定义一个鸭子类:

    /**
     * 鸭子
     */
    public abstract class Duck {
       /**
        * 飞行
        */
       FlyBehavior mFlyBehavior;
       
       /**
        * 叫声
        */
       QuackBehavior mQuackBehavior;
    ​
       public abstract void display();
    ​
       public Duck() {
    ​
       }
    ​
       public void Fly() {
          mFlyBehavior.fly();
       }
    ​
       public void Quack() {
          mQuackBehavior.quack();
       }
    ​
       public void SetQuackBehavoir(QuackBehavior qb) {
          mQuackBehavior = qb;
       }
    ​
       public void SetFlyBehavoir(FlyBehavior fb) {
          mFlyBehavior = fb;
       }
    ​
       public void swim() {
          System.out.println("~~im swim~~");
       }
    }

     

    鸭子下的子类:

    /**
     * 绿头鸭
     */
    public class GreenHeadDuck extends Duck {
        public GreenHeadDuck() {
            //实例化具体的飞行方式
            mFlyBehavior = new GoodFlyBehavior();
            //实例化具体的叫声
            mQuackBehavior = new GaGaQuackBehavior();
        }
    ​
        @Override
        public void display() {
            System.out.println("**GreenHead**");
        }
    }

     

    这样就能实现按不同需求实现:

    public class StimulateDuck {
        public static void main(String[] args) {
            GreenHeadDuck mGreenHeadDuck = new GreenHeadDuck();
            RedHeadDuck mRedHeadDuck = new RedHeadDuck();
    ​
            mGreenHeadDuck.display();
            mGreenHeadDuck.Fly();
            mGreenHeadDuck.Quack();
            mGreenHeadDuck.swim();
    ​
            mRedHeadDuck.display();
            mRedHeadDuck.Quack();
            mRedHeadDuck.swim();
            mRedHeadDuck.Fly();
        }
    }

     

    7)模板模式

    封装了一个算法步骤,并允许子类为一个或多个步骤方法提供实现;模板模式可以使子类在不改变算法结构的情况下,重新定义算法中的某些步骤。

    模板模式注重步骤实现。

    /**
     * @author: 肖德子裕
     * @date: 2019/03/03 19:31
     * @description: 模板模式
     */
    public abstract class HotDrinkTemplate {
        /**
         * 这是一个模板方法
         */
        public final void prepareRecipe() {
            //烧水,固定
            boilWater();
            //喝什么,由子类实现
            brew();
            //加水,固定
            pourInCup();
            //通过钩子方法,让子类确定是否配料
            if (wantCondimentsHook()) {
                //加配料
                addCodiments();
            } else {
                System.out.println("不加配料");
            }
        }
    ​
        /**
         * 钩子方法
         *
         * @return
         */
        public boolean wantCondimentsHook() {
            return true;
        }
    ​
        public final void boilWater() {
            System.out.println("Boiling water");
        }
    ​
        public abstract void brew();
    ​
        public final void pourInCup() {
            System.out.println("put in cup");
        }
    ​
        public abstract void addCodiments();
    }
    
    
    /**
     * @author: 肖德子裕
     * @date: 2019/03/03 19:52
     * @description: 泡茶
     */
    public class TeaWithHook extends HotDrinkTemplate{
        @Override
        public boolean wantCondimentsHook() {
            System.out.println("y/n:");
            BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
            String result="";
            try {
                result=in.readLine();
            }catch (IOException e){
                e.printStackTrace();
            }
            if (result.equals("n")){
                return false;
            }
            return true;
        }
    ​
        @Override
        public void brew() {
            System.out.println("泡茶");
        }
    ​
        @Override
        public void addCodiments() {
            System.out.println("加柠檬");
        }
    }

     

    /**
     * @author: 肖德子裕
     * @date: 2019/03/03 19:58
     * @description: 测试
     */
    public class Main {
        public static void main(String[] args) {
            TeaWithHook tea=new TeaWithHook();
            tea.prepareRecipe();
        }
    }

     

    模板方法模式的优点

    • 封装不变的内容,扩展可变部分,如果我们要添加一个H3悍马模型,只需要继承父类就可以了。

    • 提取公共部分代码,便于维护

    • 行为由父类控制,子类实现。基本方法是由子类实现的,因此子类可以通过拓展的方法增加相应的功能,符合开闭原则。

    模板方法模式的使用场景

    • 多个子类有公有的方法,并且逻辑基本相同时

    • 重复、复杂的算法,可以把核心算法设计为模板方法,周边的细节则有各个子类实现

    • 代码重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后用钩子方法约束其行为。

     

    钩子方法源于设计模式中模板方法模式,模板方法模式中分为两大类:模版方法和基本方法,而基本方法又分为:抽象方法,具体方法,钩子方法。

    好莱坞原则:明星无需联系经纪人问名具体流程,只需做好自己的事即可,经纪人会通知明星去哪里做什么。高层无需知道调用底层的细节,利于解耦。好比模板模式的子类无需知道模板的步骤,只需做好自己该实现的方法。

     

    8)代理模式

    为一个对象提供一个替身,以控制这个对象的访问;被代理的对象可以是远程对象,创建开销大的对象或需要安全控制的对象;代理模式有很多变体,都是为了控制与管理对象访问。

    扩展:RMI

    RMI远程方法调用是计算机之间通过网络实现对象调用的一种通讯机制;使用这种机制,一台计算机上的对象可以调用另外一台计算机上的对象来获取远程数据;在过去,TCP/IP通讯是远程通讯的主要手段,面向过程开发;而RPC使程序员更容易地调用远程程序,但在复杂的信息传讯时,RPC依然未能很好的支持;RMI被设计成一种面向对象开发方式,允许程序员使用远程对象来实现通信。

    代理模式分类:

    静态代理:在软件设计时常用的代理一般是指静态代理,也就是在代码中显式指定的代理。

    /**
     * @author: 肖德子裕
     * @date: 2018/10/12 11:23
     * @description: 用户登录接口
     */
    public interface UserServer {
        void login();
    }
    
    
    /**
     * @author: 肖德子裕
     * @date: 2018/10/12 11:24
     * @description: 用户登录实现类
     */
    public class UserServerImpl implements UserServer{
        @Override
        public void login() {
            System.out.println("用户登录");
        }
    }
    
    
    /**
     * @author: 肖德子裕
     * @date: 2018/10/12 11:27
     * @description: 静态代理模式
     */
    public class UserServerProxy implements UserServer{
        private UserServer userServer;
    ​
        public UserServerProxy(UserServer userServer){
            this.userServer=userServer;
        }
    ​
        @Override
        public void login() {
            before();
            userServer.login();
            after();
        }
    ​
        public void before(){
            System.out.println("登录之前");
        }
    ​
        public void after(){
            System.out.println("登录之后");
        }
    }
    
    
    /**
     * @author: 肖德子裕
     * @date: 2018/10/12 11:32
     * @description: 测试静态代理
     */
    public class Main {
        public static void main(String[] args) {
            UserServer userServer=new UserServerProxy(new UserServerImpl());
            userServer.login();
        }
    }

     


    静态代理的缺点很明显:一个代理类只能对一个业务接口的实现类进行包装,如果有多个业务接口的话就要定义很多实现类和代理类才行。而且,如果代理类对业务方法的预处理、调用后操作都是一样的(比如:调用前输出提示、调用后自动关闭连接),则多个代理类就会有很多重复代码。这时我们可以定义这样一个代理类,它能代理所有实现类的方法调用:根据传进来的业务实现类和方法名进行具体调用,那就是动态代理。

     

    JDK动态代理:JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的业务实现类对象以及方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。

    1)被代理对象必须要实现接口,才能产生代理对象,如果没有接口将不能使用动态代理技术;

    2)动态代理可对方法进行增强,如增加事务的打开与提交;

    3)在不破坏原有结构的情况下,生成动态代理对象,对原有方法进行增强。

    注意: JDK动态代理的代理对象在创建时,需要使用业务实现类所实现的接口作为参数(因为在后面代理方法时需要根据接口内的方法名进行调用)。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。

    public interface BookFacade {  
        public void addBook();  
    } 

     

    public class BookFacadeImpl implements BookFacade {   
        @Override  
        public void addBook() {  
            System.out.println("增加图书方法。。。");  
        }  
    } 
    
    
    public class BookFacadeProxy implements InvocationHandler {  
        //这其实业务实现类对象,用来调用具体的业务方法
        private Object target; 
        
        /** 
         * 绑定业务对象并返回一个代理类  
         */  
        public Object bind(Object target) {  
            this.target = target;  //接收业务实现类对象参数
    //通过反射机制,创建一个代理类对象实例并返回。用户进行方法调用时使用
           //创建代理对象时,需要传递该业务类的类加载器(用来获取业务实现类的元数据,在包装方法是调用真正的业务方法)、接口、handler实现类
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
                    target.getClass().getInterfaces(), this); }  
        /** 
         * 包装调用方法:进行预处理、调用后处理 
         */  
        public Object invoke(Object proxy, Method method, Object[] args)  
                throws Throwable {  
            Object result=null;  
    ​
            System.out.println("预处理操作——————");  
            //调用真正的业务方法  
            result=method.invoke(target, args);  
    ​
            System.out.println("调用后处理——————");  
            return result;  
        }  
    }  
    
    
    public static void main(String[] args) {  
            BookFacadeImpl bookFacadeImpl=new BookFacadeImpl();
            BookFacadeProxy proxy = new BookFacadeProxy();  
            BookFacade bookfacade = (BookFacade) proxy.bind(bookFacadeImpl);  
            bookfacade.addBook();  
    }  

     

    在列举一个动态代理的例子:

    public interface UserDao {
        public String creatSqlSession();
    }
    
    
    public class UserDaoImpl implements UserDao{
        @Override
        public String creatSqlSession() {
           return "创建SQLSESSION对象";
        }
    }
    
    
    public class UserDaoProxyFactory implements InvocationHandler{
        private UserDao userDao;
    ​
        public UserDaoProxyFactory(UserDao userDao){
            this.userDao=userDao;
        }
    ​
        public UserDao getUserDaoProxy(){
            //生成动态代理
            UserDao userProxy=(UserDao) Proxy.newProxyInstance(UserDaoProxyFactory.class.getClassLoader(),
                    UserDaoImpl.class.getInterfaces(),
                    this);
            //返回一个动态代理对象
            return userProxy;
        }
    ​
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object invoke=method.invoke(userDao,args);
            return invoke;
        }
    }

     

    public class Main {
        public static void main(String[] args) {
            UserDao userDao=new UserDaoImpl();
            UserDaoProxyFactory factory=new UserDaoProxyFactory(userDao);
            UserDao userProxy=factory.getUserDaoProxy();
            System.out.println(userProxy.creatSqlSession());
            System.out.println(userProxy instanceof UserDaoImpl);
        }
    }

     


    CGLIB动态代理:cglib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。

    public class BookFacadeImpl1 {  
        public void addBook() {  
            System.out.println("新增图书...");  
        }  
    }  
    
    
    public class BookFacadeCglib implements MethodInterceptor {  
        private Object target;//业务类对象,供代理方法中进行真正的业务方法调用
      
        //相当于JDK动态代理中的绑定
        public Object getInstance(Object target) {  
            this.target = target;  //给业务对象赋值
            Enhancer enhancer = new Enhancer(); //创建加强器,用来创建动态代理类
            enhancer.setSuperclass(this.target.getClass());  //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
            //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
            enhancer.setCallback(this); 
           // 创建动态代理类对象并返回  
           return enhancer.create(); 
        }
        // 实现回调方法 
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 
            System.out.println("预处理——————");
            proxy.invokeSuper(obj, args); //调用业务类(父类中)的方法
            System.out.println("调用后操作——————");
            return null; 
        } 
     
    
        public static void main(String[] args) {      
            BookFacadeImpl1 bookFacade=new BookFacadeImpl1();
            BookFacadeCglib  cglib=new BookFacadeCglib();  
            BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(bookFacade);  
            bookCglib.addBook();  
    }  

     


    总结:

    静态代理是通过在代码中显式定义一个业务实现类一个代理,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法;

    JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;

    CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;

     

  • 相关阅读:
    运算符(C# 参考)
    virtualbox修改主机名
    偏前端HTML5 sessionStorage会话存储
    偏前端纯css,手写轮播(焦点切换 和 自动轮播 只可选择一种,两者不可共存)
    偏前端 div+mui+vue.js 制作问卷调查单页 ——题目答案由后台随机给出10道
    偏前端 jqueryiframe内触发父窗口自定义事件
    TWAIN扫描打印图像编辑处理控件ImagXpress功能大全
    实时温度计湿度计高效仪器控件Iocomp使用教程
    Essential Diagram for Windows Forms绘图控件图解及下载地址
    Code39 Fontware条形码控件
  • 原文地址:https://www.cnblogs.com/xdzy/p/10485406.html
Copyright © 2020-2023  润新知