• 设计模式 #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

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

  • 相关阅读:
    洛谷 P1508 Likecloud-吃、吃、吃
    Codevs 1158 尼克的任务
    2017.10.6 国庆清北 D6T2 同余方程组
    2017.10.6 国庆清北 D6T1 排序
    2017.10.3 国庆清北 D3T3 解迷游戏
    2017.10.3 国庆清北 D3T2 公交车
    2017.10.3 国庆清北 D3T1 括号序列
    2017.10.4 国庆清北 D4T1 财富
    2017.10.7 国庆清北 D7T2 第k大区间
    2017.10.7 国庆清北 D7T1 计数
  • 原文地址:https://www.cnblogs.com/l1ng14/p/13700457.html
Copyright © 2020-2023  润新知