zoukankan      html  css  js  c++  java
  • 设计模式 #5 (策略模式、代理模式)

    设计模式 #5 (策略模式、代理模式)


    文章中所有工程代码和UML建模文件都在我的这个GitHub的公开库--->DesignPatternStar来一个好吗?秋梨膏!


    策略模式

    简述: 一个类的行为或其算法可以在运行时更改。

    还有这种好事?运行时可以更改?

    需求:现在游戏中有数种鸟,要求实现鸟的叫,展示功能。

    反例 #1:

    public abstract  class Bird {
        public abstract void display();
    
        public void yell() {
            System.out.println("吱吱吱.....");
        }
    }
    
    public class RubberBird extends Bird{
        @Override
        public void display() {
            System.out.println("这是橡皮鸟-----------");
        }
    }
    
    public class RedHeadBird extends Bird{
        @Override
        public void display() {
            System.out.println("这是  红头鸟。。。");
        }
    }
    
    public class negtive_01 {
    /*===============客户端========================*/
        public static void main(String[] args) {
            RedHeadBird redHeadBird = new RedHeadBird();
            redHeadBird.display();
            redHeadBird.yell();
    
            System.out.println("     ");
    
            RubberBird rubberBird = new RubberBird();
            rubberBird.display();
            rubberBird.yell();
        }
    }
    

    好,现在产品笑嘻嘻地来改需求,咱们都是文明人,别拿刀出来。

    变化:现在要求为游戏中的某些鸟添加飞的功能。

    反例 #2:

    产品说了,“哥,咱首先明确,游戏里的某些鸟,比如橡皮鸟是飞不起来的。”

    通过改写Bird抽象类增加一个抽象fly方法,在各实现类中实现该抽象方法(因为和以下方法雷同,所以就不在此赘述),或者:

    编写一个Flyable接口,哪个鸟能飞,就让他实现这个接口即可。

    public interface Flyable {
        void fly();
    }
    
    public class RedHeadBird extends Bird implements Flyable{
        @Override
        public void display() {
            System.out.println("这是  红头鸟。。。");
        }
    
        @Override
        public void fly() {
            System.out.println("飞飞飞============");
        }
    }
    
    public class negtive_02 {
        /*===============客户端========================*/
        public static void main(String[] args) {
            RedHeadBird redHeadBird = new RedHeadBird();
            redHeadBird.display();
            redHeadBird.yell();
    
            redHeadBird.fly();
        }
    }
    

    这种设计确实实现了需求,但是,这会导致代码的重复,比如:不同的鸟有不同的飞行高度,但是相当部分的鸟又具有相同的高度。这就带来代码重用的问题。

    变化:游戏中的鸟可以变化形态,改变飞的方式。这就要求在运行时可以改变Bird类中飞的行为。

    正例 #1:

    public interface FlyBehavior {
        void fly();
    }
    
    public class FlyByKick implements FlyBehavior{
        @Override
        public void fly() {
            System.out.println("被踢飞了!!!!!!");
        }
    }
    
    public class FlyByWings implements  FlyBehavior{
        @Override
        public void fly() {
            System.out.println("用翅膀飞~~~~~~~~~~~");
        }
    }
    
    public abstract  class Bird {
    
        protected FlyBehavior flyBehavior;
    
        public FlyBehavior getFlyBehavior() {
            return flyBehavior;
        }
    
        public void setFlyBehavior(FlyBehavior flyBehavior) {
            this.flyBehavior = flyBehavior;
        }
    
        public abstract void display();
    
        public void yell() {
            System.out.println("吱吱吱.....");
        }
    }
    
    public class RedHeadBird extends Bird  {
    
        public RedHeadBird() {
            this.flyBehavior = new FlyByWings();
        }
    
        @Override
        public void display() {
            System.out.println("这是  红头鸟。。。");
        }
    
        public void doFly(){
            this.flyBehavior.fly();
        }
    
    }
    
    public class postive {
        /*===============客户端========================*/
        public static void main(String[] args) {
            RedHeadBird redHeadBird = new RedHeadBird();
            redHeadBird.display();
            redHeadBird.yell();
            redHeadBird.doFly();
    
            System.out.println("         ");
            System.out.println("靠近人群中.......");
    
            redHeadBird.setFlyBehavior(new FlyByKick());
            redHeadBird.doFly();
        }
    }
    

    此时,才是真正的策略模式。通过关联另一个接口FlyBehavior,封装飞的行为,同时保证了代码的重用性,接口还可以对扩展保持开放。

    UML类图如下:

    image-20200919202001281

    总结:

    • 当你想让某个类中的某一行为能在运行中可以变化,就将这一行为拿出来进行封装,类再通过关联的方式获取到这一行为即可。
    • 需要在运行时改变类的行为时,可以使用策略模式进行设计。

    代理模式

    简述:代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。

    需求:现在需要实现加减乘除功能。

    反例 #1:

        interface Calculator{
            int add(int a ,int b);
            int sub(int a ,int b);
            int mul(int a ,int b);
            int div(int a ,int b);
        }
    
         class MyCalculator implements Calculator{
    
            @Override
            public int add(int a, int b) {
                return a + b;
            }
    
            @Override
            public int sub(int a, int b) {
                return a - b;
            }
    
            @Override
            public int mul(int a, int b) {
                return a * b;
            }
    
            @Override
            public int div(int a, int b) {
                return a / b;
            }
        }
    
    /*===================客户端=============*/
    public class negtive {
            public static void main(String[] args) {
               Calculator c = new MyCalculator();
               System.out.println(c.add(2, 3));
               System.out.println(c.sub(10, 3));
               System.out.println(c.mul(8, 3));
               System.out.println(c.div(99, 3));
        }
    }
    

    这不是信手拈来的事情?

    有请程序猿的好同事--产品经理出场提出需求变化:“这样太简单了,我想要加入一些输出提示。”

    我心想,你再改需求,我就给你头一顿输出。

    动态代理

    image-20200920144859139

    这时候,不能改动源代码,否则违反开闭原则,这时候先明确---动态代理API

    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
    
    • 第1个参数:ClassLoader(动态代理的对象的类加载器)
      我们都知道,要实例化一个对象,是需要调用类的构造器的,在程序运行期间第一次调用构造器时,就会引起类的加载,加载类的时候,就是jvm拿着ClassLoader去加载类的字节码的,只有字节码被加载到了内存中,才能进一步去实例化出类的对象。简单来说,就是只要涉及实例化类的对象,就一定要加载类的字节码,而加载字节码就必须使用类加载器!下面我们使用的是动态代理的api来创建一个类的对象,这是一种不常用的实例化类对象的方式,尽管不常用,但毕竟涉及实例化类的对象,那就一定也需要加载类的字节码,也就一定需要类加载器,所以我们手动把类加载器传入!
    • 第2个参数:Class[]需要调用其方法的接口
      我们已经知道,下面的代码,是用来实例化一个对象的,实例化对象,就一定是实例化某一个类的对象,问题是,到底是哪个类呢?类在哪里?字节码又在哪里?这个类,其实并不在硬盘上,而是在内存中!是由动态代理在内存中"f动态生成的!要知道,这个在内存中直接生成的字节码,会去自动实现下面方法中的第2个参数中,所指定的接口!所以,利用动态代理生成的代理对象,就能转成Calculator接口类型!那么这个代理对象就拥有addsubmuldiv方法!
    • 第3个参数:InvocationHandler调用方法时的处理程序
      我们已经知道,下面的代理对象porxy所属的类,实现了Calculator接口,所以,这个代理对象就拥有addsubmuldiv方法!我们就可以通过代理对象调用addsubmuldiv方法!注意,每次对代理对象任何方法的调用,都不会进入真正的实现方法中。而是统统进入第3个参数的invoke方法中!
    @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
        }
    
    • Object proxy:代理对象
    • Method:代理对象调用的方法
    • Object[] args:调用方法的参数

    正例 #1:

    public class MyHandler implements InvocationHandler {
        private Calculator calculator ;
        public MyHandler(Calculator c){
            this.calculator = c;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("调用"+method.getName()+",  参数是"+ Arrays.toString(args));
            int res = (int) method.invoke(calculator, args);
            System.out.println("结果是 "+res);
            return res;
        }
    }
    

    先把InvocationHandler的实现类设计好。在实现类的内部关联Calculator,用于调用Calculator的方法。

    public class postive {
        public static void main(String[] args) {
            Calculator c = new MyCalculator();
    
            ClassLoader loader = postive.class.getClassLoader();
            Calculator proxy = (Calculator)Proxy.newProxyInstance(loader, new Class[]{Calculator.class}, new MyHandler(c));
    
            proxy.add(22,33);
            proxy.sub(55,22);
            proxy.div(10,2);
            proxy.mul(50,5);
        }
    }
    

    image-20200920144746386

    总结:代理模式是代理对象通过在其内部关联被代理对象,对被代理对象的方法实施扩展。

  • 相关阅读:
    代码重构(转)
    Apache负载均衡 配置
    恒久的忍耐
    setInterval全面的介绍
    引用 110个Oracle 常用函数的总结
    ssl和tls
    JSTL
    java异常处理的陋习(转载)
    Java 6 JVM参数选项大全(中文版)
    liunx基础常用命令
  • 原文地址:https://www.cnblogs.com/l1ng14/p/13700457.html
Copyright © 2011-2022 走看看