java内功设计模式

设计模式

基础核心在于多态的使用

多态分为3种

1、实体类的多态
2、抽象类的多态
3、接口的多态

文章在《设计模式之禅》上加入对关键技术的分析,以及个人的理解,方便您理解模式的妙用,持续更新

01策略模式

常用的模式,主要使用接口的多态性,由一个小故事引入

刘备要到江东娶老婆了,走之前诸葛亮给赵云(伴郎)三个锦囊妙计,说是按天机拆开解决棘手问题,嘿,还别说,真是解决了大问题,搞到最后是周瑜陪了夫人又折兵呀,那咱们先看看这个场景是什么样子的。

先说这个场景中的要素:三个妙计,一个锦囊,一个赵云,妙计是小亮同志给的,妙计是放置在锦囊里,俗称就是锦囊妙计嘛,那赵云就是一个干活的人,从锦囊中取出妙计,执行,然后获胜,用 JAVA 程序

怎么表现这个呢?我们先看类图:

在这里插入图片描述

1、不浪费锦囊的空间 毕竟只能有一个锦囊

package StrategyParten;

public class ZhaoYun {
    public static void main(String[] args) {
       //每使用一个锦囊都有新的对象产生,浪费空间,且锦囊命名不能做到同一命名
//        System.out.println("打开锦囊");
//        System.out.println("第一个方法");
//        Strategy strategy = new KaiHouMen();
//        Context context = new Context(strategy);
//        context.operator();
//        System.out.println("第二个方法");
//        Strategy strategy1 = new KaiLuDeng();
//        Context context1 = new Context(strategy1);
//        context1.operator();
//        System.out.println("第三个方法");
//        Strategy strategy2 = new DuZuiBing();
//        Context context2 = new Context(strategy2);
//        context2.operator();
      //优点
       // 1、不浪费锦囊的空间 毕竟只能有一个锦囊
        // 2、可以重复命名  使用属性+多态
        Context context; //属性 和 对象的区分 显然这里没有调用构造方法
        context = new Context(new KaiHouMen());
        context.operator();
        System.out.println();
        context = new Context(new KaiLuDeng());
        context.operator();
        System.out.println();
        context = new Context(new DuZuiBing());
        context.operator();
        System.out.println();
    }
}

成员变量的声明和定义

声明没有分配空间也没有初始化(相当于一份名单)

定义分配+初始化

02代理者模式

代理模式的故事有点黄暴,怕被河蟹,看代码就行

优点:

1、用户不需要知道被代理者是谁,只需要吩咐代理者这个中介干活就好了

2、代理者手中有很多被代理者人的名单,根据情况调用就好

package ProxyPattern;
/**
 * 代理者作为中间商给用户提供服务,所以代理者的方法和被代理者的方法一致
 *  代理者手中有被代理者的名单,在构造方法时就请到了被代理者过来干活
 *  用户不需要知道被代理者是谁,只需要吩咐代理者这个中介干活就好了
*/
public class WangPo implements KindOfWoman {
    //私人拥有的电话号码本,避免别人拿到
    private KindOfWoman kindOfWoman;

    public WangPo(){
        this.kindOfWoman = new PanJinLian();
    }
    public  WangPo(KindOfWoman kindOfWoman){
        this.kindOfWoman = kindOfWoman;
    }

    @Override
    public void makeLove() {
        this.kindOfWoman.makeLove();
    }

    @Override
    public void makeEyes() {
    this.kindOfWoman.makeEyes();
    }
}

代理模式主要使用了 Java 的多态,干活的是被代理类,代理类主要是

接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代理类能不能干呢?同根就成,

大家知根知底,你能做啥,我能做啥都清楚的很,同一个接口呗。

03单例模式

这个模式是很有意思,而且比较简单,但是我还是要说因为它使用的是如此的广泛,如此的有人缘,

单例就是单一、独苗的意思,那什么是独一份呢?你的思维是独一份,除此之外还有什么不能山寨的呢?

我们举个比较难复制的对象:皇帝

C:\Users\huihui520\AppData\Roaming\Typora\typora-user-images\image-20210219215257637.png

package singleton1;
//static的作用域
/*
 *1、静态变量,在创建对象之前被初始化,或者说在类被载入之前进行初始化 。(所以静态不能包含动态)
 *2、静态变量被所有的对象共享,属于公共变量,对象和类都可以直接调用,但是推荐使用类来调用
 *3、成员变量放在堆中,而静态变量放在方法去中静态区
 */
public class Emperor {
   //确保emperor的作用域+对象间共享性
    private static Emperor emperor = null;
    private Emperor(){}
    //下面static确保可以类.引用调用方法(因为不能直接创建出皇帝对象)
    public static Emperor getEmperor(){
        if (emperor == null){
            return new Emperor();
        }
        return emperor;
    }
    public static void emperorInfo(){
        System.out.println("没错还是我皇帝1");
    }
}
package singleton1;

public class Minster {
    static int a = 1;
    public static void main(String[] args) {
        Emperor emperor = Emperor.getEmperor();
        //static有两种调用方法
        emperor.emperorInfo();
        Emperor emperor2 = Emperor.getEmperor();
        //static有两种调用方法
        emperor2.emperorInfo();
        Emperor emperor3 = Emperor.getEmperor();
        //static有两种调用方法
        emperor3.emperorInfo();
        Emperor emperor4 = Emperor.getEmperor();
        //static有两种调用方法
        emperor4.emperorInfojava();
       change();
        System.out.println(a);
    }
    public static void change(){
        a= 2;
    }
}

看到没,大臣天天见到的都是同一个皇帝,不会产生错乱情况,反正都是一个皇帝,是好是坏就这一
个,只要提到皇帝,大家都知道指的是谁,清晰,而又明确。问题是这是通常情况,还有个例的,如同一
个时期同一个朝代有两个皇帝,怎么办?
单例模式很简单,就是在构造函数中多了加一个构造函数,访问权限是 private 的就可以了,这个模
式是简单,但是简单中透着风险,风险?什么风险?在一个 B/S 项目中,每个 HTTP Request 请求到 J2EE
的容器上后都创建了一个**线程,**每个线程都要创建同一个单例对象,怎么办?,好,我们写一个通用的单例程
序,然后分析一下:

package singleton1;

public class SingletonPattern {
    private static SingletonPattern singletonPattern= null;
    //限制住不能直接产生一个实例
    private SingletonPattern(){

    }
    public SingletonPattern getInstance(){
        /************************************/
        if(this.singletonPattern == null){ //如果还没有实例,则创建一个
            this.singletonPattern = new SingletonPattern();
        }
        return this.singletonPattern;
    }
}

我们来看注释那一部分,假如现在有两个线程 A 和线程 B,线程 A 执行到 this.singletonPattern =

new SingletonPattern(),正在申请内存分配,可能需要 0.001 微秒,就在这 0.001 微秒之内,线程 B 执

行到 if(this.singletonPattern == null),你说这个时候这个判断条件是 true 还是 false?是 true,那

然后呢?线程 B 也往下走,于是乎就在内存中就有两个 SingletonPattern 的实例了,看看是不是出问题了?

如果你这个单例是去拿一个序列号或者创建一个信号资源的时候,会怎么样?业务逻辑混乱!数据一致性

校验失败!最重要的是你从代码上还看不出什么问题,这才是最要命的!因为这种情况基本上你是重现不

了的,不寒而栗吧,那怎么修改?有很多种方案,我就说一种,能简单的、彻底解决问题的方案:

DCL(美团面试原题,双重检查模型)

package singleton1;

public class SingletonPattern {
    private static volatile SingletonPattern singletonPattern= null;
    //限制住不能直接产生一个实例
    private SingletonPattern(){

    }
    public static SingletonPattern getInstance(){
        if(singletonPattern == null){ //如果还没有实例,则创建一个
           synchronized (SingletonPattern.class){ //注意对象是静态对象,需要用class去锁住,而不是this
               if(singletonPattern == null){
               singletonPattern = new SingletonPattern();
              }
           }
        }
        return singletonPattern;
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{ //对比hashcode来确定是否是新对象
                System.out.println(SingletonPattern.getInstance().hashCode());
            }).start();
        }
    }
}

04多例模式

可以实现有限个对象的创建

package MultitionPa;

import java.util.ArrayList;
import java.util.Random;

public class Emperor {
    private static int maxOfEmperor = 2;
    private static int countOfEmperor = 0;
    private static ArrayList<Emperor> emperorList = new ArrayList(maxOfEmperor);
    private static ArrayList emperorInfo = new ArrayList(maxOfEmperor);
    //先把2个皇帝产生出来 静态的直接创建出了对象 懒加载(最保险)
    static{
        //把所有的皇帝都产生出来
        for(int i=0;i<maxOfEmperor;i++){
            emperorList.add(new Emperor("皇"+(i+1)+"帝"));
        }
    }
    private Emperor(){}
    private Emperor(String info){
            emperorInfo.add(info);
    }
    public static Emperor getInstance(){
        Random random = new Random();
        countOfEmperor = random.nextInt(maxOfEmperor); //随机拉出一个皇帝,只要是个精神领袖就成
        return emperorList.get(countOfEmperor);
    }

    //皇帝叫什么名字呀
    public static void emperorInfo(){
        System.out.println(emperorInfo.get(countOfEmperor));
    }
}

05工厂模式

女娲补天的故事大家都听说过吧,今天不说这个,说女娲创造人的故事,可不是“造人”的工作,这个词被现代人滥用了。这个故事是说,女娲在补了天后,下到凡间一看,哇塞,风景太优美了,天空是湛蓝的,水是清澈的,空气是清新的,太美丽了,然后就待时间长了就有点寂寞了,没有动物,这些看的到都是静态的东西呀,怎么办?

别忘了是神仙呀,没有办不到的事情,于是女娲就架起了八卦炉(技术术语:建立工厂)开始创建人,具体过程是这样的:先是泥巴捏,然后放八卦炉里烤,再扔到地上成长,但是意外总是会产生的:

第一次烤泥人,兹兹兹兹~~,感觉应该熟了,往地上一扔,biu~,一个白人诞生了,没烤熟!

第二次烤泥人,兹兹兹兹兹兹兹兹~~,上次都没烤熟,这次多烤会儿,往地上一扔,嘿,熟过头了,黑人哪!

第三次烤泥人,兹兹~,一边烤一边看着,嘿,正正好,Perfect!优品,黄色人类!【备注:RB 人不属此列】

实际上在Spring全家桶中,大多也是使用工厂模式创建对象的,大量对象创建过程中交给了Factory,之后factory利用Class和Java.lang.reflect即反射技术实现对象创建过程,这里Class关键字和反射有基础介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rIk32RSq-1613813345207)(C:\Users\huihui520\AppData\Roaming\Typora\typora-user-images\image-20210220155449043.png)]

package Factory;

import java.util.List;
import java.util.Random;

public class HumanFactory {
    public static Human createHuman(Class c){
        Human human = null;
        try {
            //Class获取对象 
            // Class的返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。
            human = (Human)Class.forName(c.getName()).newInstance(); 
        } catch (IllegalAccessException e) {
            System.out.println("人类的定义有问题");
            e.printStackTrace();
        } catch (InstantiationException e) {
            System.out.println("指定人类的颜色");
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            System.out.println("你指定的人类找不到");
            e.printStackTrace();
        }
        return human ;
    }
}
package Factory;

public class NvWa {
    public static void main(String[] args) {
    //女娲第一次造人,试验性质,少造点,火候不足,缺陷产品
        System.out.println("------------造出的第一批人是这样的:白人 -----------------");
        Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class);
        whiteHuman.laugh();
    }
}
package Factory;

public interface Human {
    public void laugh();
}
package Factory;

public class WhiteHuman implements Human {
    @Override
    public void laugh() {
        System.out.println("白人");
    }
}

RRTI的概念以及Class对象作用

在java世界里,一切皆对象。从某种意义上来说,java有两种对象:实例对象Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的。

每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。

Class类没有公共的构造方法,Class对象是在类加载的时候由JVM以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:

  1. 加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。
  2. 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。
  3. 初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。

**所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。**当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。**因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。**这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。

在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。

在这里插入图片描述

如何获得Class对象

有三种获得Class对象的方式:

  1. Class.forName(“类的全限定名”)
  2. 实例对象.getClass()
  3. 类名.class (类字面常量)

理解反射技术

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。

Constructor类及其用法

Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:

方法返回值方法名称方法说明
static Class<?>forName(String className)返回与带有给定字符串名的类或接口相关联的 Class 对象。
Constructor<T>getConstructor(Class<?>... parameterTypes)返回指定参数类型、具有public访问权限的构造函数对象
Constructor<?>[]getConstructors()返回所有具有public访问权限的构造函数的Constructor对象数组
Constructor<T>getDeclaredConstructor(Class<?>... parameterTypes)返回指定参数类型、所有声明的(包括private)构造函数对象
Constructor<?>[]getDeclaredConstructor()返回所有声明的(包括private)构造函数对象
TnewInstance()创建此 Class 对象所表示的类的一个新实例。

下面看一个简单例子来了解Constructor对象的使用:

package reflect;

import java.io.Serializable;
import java.lang.reflect.Constructor;

/**
 * Created by zejian on 2017/5/1.
 * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
 */
public class ReflectDemo implements Serializable{
    public static void main(String[] args) throws Exception {

        Class<?> clazz = null;

        //获取Class对象的引用
        clazz = Class.forName("reflect.User");

        //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
        User user = (User) clazz.newInstance();
        user.setAge(20);
        user.setName("Rollen");
        System.out.println(user);

        System.out.println("--------------------------------------------");

        //获取带String参数的public构造函数
        Constructor cs1 =clazz.getConstructor(String.class);
        //创建User
        User user1= (User) cs1.newInstance("xiaolong");
        user1.setAge(22);
        System.out.println("user1:"+user1.toString());

        System.out.println("--------------------------------------------");

        //取得指定带int和String参数构造函数,该方法是私有构造private
        Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
        //由于是private必须设置可访问
        cs2.setAccessible(true);
        //创建user对象
        User user2= (User) cs2.newInstance(25,"lidakang");
        System.out.println("user2:"+user2.toString());

        System.out.println("--------------------------------------------");

        //获取所有构造包含private
        Constructor<?> cons[] = clazz.getDeclaredConstructors();
        // 查看每个构造方法需要的参数
        for (int i = 0; i < cons.length; i++) {
            //获取构造函数参数类型
            Class<?> clazzs[] = cons[i].getParameterTypes();
            System.out.println("构造函数["+i+"]:"+cons[i].toString() );
            System.out.print("参数类型["+i+"]:(");
            for (int j = 0; j < clazzs.length; j++) {
                if (j == clazzs.length - 1)
                    System.out.print(clazzs[j].getName());
                else
                    System.out.print(clazzs[j].getName() + ",");
            }
            System.out.println(")");
        }
    }
}


class User {
    private int age;
    private String name;
    public User() {
        super();
    }
    public User(String name) {
        super();
        this.name = name;
    }

    /**
     * 私有构造
     * @param age
     * @param name
     */
    private User(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

  //..........省略set 和 get方法
}

运行结果:

User [age=20, name=Rollen]
--------------------------------------------
user1:User [age=22, name=xiaolong]
--------------------------------------------
user2:User [age=25, name=lidakang]
--------------------------------------------
构造函数[0]:private reflect.User(int,java.lang.String)
参数类型[0]:(int,java.lang.String)
构造函数[1]:public reflect.User(java.lang.String)
参数类型[1]:(java.lang.String)
构造函数[2]:public reflect.User()
参数类型[2]:()

剩下2个类详情见参考博文
深入理解Java类

可见反射在实际引用中的强大

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值