单例模式、工厂模式、代理模式、命令模式、策略模式、适配器模式

java有很多设计模式,“模式”两个字体现出通用性,主要是以java的多态作为实现的技术手段。

1. 单例模式

1.1、懒汉模式(多线程不安全)

线程不安全是因为:当多个线程同时第一次调用getInstace方法的时候,singleton为null,因此这些线程都会执行new Singleton()创建对象初始化,但是静态变量只能初始化一次,故会报错。

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

1.2、懒汉模式(加同步锁,加锁过程复杂,会降低性能)

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

1.3、饿汉模式(多线程安全,但是类加载的时候就创建了实例,不管你用不用,它都会给你创建好,所以耗内存)

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

1.4、 静态内部类(这个方案推荐,既不会事先给你创建好实例,而且线程安全)。

因为内部类在被调用的时候才去加载该内部类,从而达到只有当用到的时候,才去创建Singleton对象,实现了懒汉特性,其次,是线程安全,因为JVM会保证静态对象创建的线程安全的。

public class Singleton {
    private Singleton(){
        
    }
    private static class SingletonHolder{
        private final static Singleton instance=new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

2. 工厂模式

2.1、静态工厂模式

Apple 和 Banana等类都是实现的Fruit类,利用一个静态的工厂方法根据条件参数去生产相应的实例化对象。

public class FruitFactory{
    public static Fruit createFruit(String fruitName){
        switch(fruitName){
            case "apple":
                return new Apple();
            case "banana":
                return new Apple();
            default:
                return null;
        }
    } 
}

public static void main(String[] args){
    Fruit apple = FruitFactory.createFruit("apple");
}

2.2、工厂模式

给每一个种实例化对象都设置一个生成工厂,然后调用相应的工厂,就能生产出对应的实例化对象。

interface FruitFactory{
    Fruit createFruit();
}
public class AppleFactory implements FruitFactory{
    @Override
    public Fruit createFruit(){
        return new Apple();
    }
}
public class BananaFactory implements FruitFactory{
    @Override
    public Fruit createFruit(){
        return new Banana();
    }
}

public static void main(String[] args){
    Fruit apple = new AppleFactory().createFruit();
}

2.3、 抽象工厂模式

public interface Fruit{
    void printName();
}

public interface Book{
    void printBookName();
}
public class Apple implements Fruit{
    @Override
    public void printName(){
        System.out.println("我是苹果");
    }
}
public class Banana implements Fruit{
    @Override
    public void printName(){
        System.out.println("我是香蕉");
    }
}
public class Mathematics implements Book{
    @Override
    public void printBookName(){
        System.out.println("我是数学");
    }
}
public class English implements Book{
    @Override
    public void printBookName(){
        System.out.println("我是英语");
    }
}
public interface FruitFactory{
    Fruit createApple();
    Fruit createBanana();
    Book createMathematics();
    Book createEnglish();
}

public class MyFactory implements FruitFactory{
    @Override
    public Fruit createApple(){
        return new Apple();
    }
    @Override
    public Fruit createBanana(){
        return new Banana();
    }
    @Override
    public Book createMathematics(){
        return new Mathematics();
    }
    @Override
    public Book Book createEnglish(){
        return new English();    
    }
}

总结三个工厂模式的区别:静态工厂模式比较简单,就是用于一类实例化对象的,根据参数条件的不同,生产出对应的对象(比如,生成水果,给定水果的类型,分别生成出对应的水果,比如苹果,香蕉等等)。工厂模式也是用于生产一类对象的,有一个工厂接口,针对每一类对象都有一个专门的生成工厂类(继承工厂接口),比如苹果工厂就生成苹果,香蕉工厂就生成香蕉。抽象工厂就是工厂模式的扩展版,不仅可以生成水果,还可以生成塑料袋等等,然后水果分为多种类型水果,塑料袋可以有多种塑料袋,但是抽象工厂只有一个(包含了全部的生成方法),实现工厂也有一个(实现 了全部的生成方法)。

3. 代理模式

代理可以理解为中介,客户只需要将信息交给中介,中介就会替你做完所有的事情,让客户和事情隔开,你客户有什么事情,让中介(代理)去做比如:理发这件事,你可以自己理发,但是如果你没有这个技能的话,就要找理发师,将自己交给理发师,然后理发这事情的执行就跟你没关系,理发师会执行的,这就是代理的主要作用。

3.1 静态代理

定义业务主题

public interface Hair{   //这是业务主题接口,即代理要处理的是什么类型的客户和业务
    void execute();
}
public class cutHair implements Hair{ //这是具体的客户类
    @Override
    public void execute(){
        System.out.println("我是做剪头发业务");
    } 
}
public class hotHair implements Hair{ //这是具体的客户类
    @Override
    public void execute(){
        System.out.println("我是做烫头发业务");
    }
}

定义代理类:

public class Proxy implements Hair{
    private Hair hair = null;
    public Proxy(Hair hair){
        this.hair = hair;
    }
    @Override
    public void execute(){
        if(this.hair != null){
            //前 这里可以执行一些代码
            this.hair.execute();
            //后 这里也可以执行一些代码
        }
    }
}

测试一下代理:

public class Test{
    public static void main(String[] args){
        Hair hair = new HotHair();
        Proxy p = new Proxy(hair);
        p.execute();
    }
}

静态代理相比其它代理模式没有什么优点,唯一的优点也是代理的共同优点:将客户与要做的业务隔离开;缺点是:接口中的方法被重复实现,浪费,而且只能处理一种客户类(其实可以做到处理多种客户类,但是非常不灵活,且实现起来比较冗余)。

3.2 动态代理

动态代理可以处理任何客户类型的不同业务,功能大而全面,但是缺点是开销增大了。

怎么做到的呢? 客户类有任意多种,我们无法指定代理类去继承主题接口,所以解决方法就是java反射机制(根据对象获取类信息,再根据类信息实例化成对象)。根据客户对象,得到客户类信息、客户类的接口信息、调用的方法(执行什么业务,假设名字叫aaa())、代理类信息,有了这四样信息,可以让生成这样的一个暂时的新代理类class(继承客户类的接口,然后实现了“调用的方法”aaa()),然后再根据class信息实例化成一个新代理类对象(只能用Object存着),然后调用这个新代理类对象的aaa()方法,即可以达到通过代理来实现客户的业务执行。

第一种写法:

定义业务主题:

public interface Hair{   //这是业务主题接口,即代理要处理的是什么类型的客户和业务
    void execute();
}
public class cutHair implements Hair{ //这是具体的客户类
    @Override
    public void execute(){
        System.out.println("我是做剪头发业务");
    } 
}
public class hotHair implements Hair{ //这是具体的客户类
    @Override
    public void execute(){
        System.out.println("我是做烫头发业务");
    }
}

动态代理类如下:

public class ProxyHandler implements InvocationHandler {
    //这用于存储客户对象的
    private Object target;
    //这个方法就是创建一个临时的新代理类对象(也叫动态代理类对象)
    public Object newProxyInstance(Object target) {
        this.target = target;
        //第一个参数指客户类的类加载器。
        //第二个参数客户类实现的接口。
        //第三个参数表示代理类(注意不是动态代理类哦~)
        Object result = Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
        return result;
    }

    //当动态代理类对象调用方法时(执行业务时),就会触发这个方法的执行,即相当于代理开始执行业务
    //第一个参数  代理
    //第二个参数 调用的方法(执行的业务)
    //第三个参数 方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //前 这里可以是其他的执行代码

        Object ret = null;
        try {
            //调用目标方法
            ret = method.invoke(target, args);
        } catch (Exception e) {
            log.error("调用{}.{}发生异常", target.getClass().getName(), method.getName(), e);
            throw e;
        }
        //后 这里可以是其他的执行代码
        return ret;
    }
}

测试动态代理:

//第一句创建了一个代理类对象
ProxyHandler handler = new ProxyHandler(); 
//第二句创建了一个动态代理类对象,然后用接口引用
Hair proxy = (Hair) handler.newProxyInstance(new HotHair());
//第三句调用接口引用的方法,实则调用的是动态代理类对象的方法,执行的内容就是invoke()方法的内容
proxy.execute();

第二种写法:

动态代理类写成下面这样(生成动态代理对象的任务没有放在这里):

public class ProxyHandler implements InvocationHandler {
    //这用于存储客户对象的
    private Object target;
    
    public void newProxyInstance(Object target) {
        this.target = target;
    }

    //当动态代理类对象调用方法时(执行业务时),就会触发这个方法的执行,即相当于代理开始执行业务
    //第一个参数  代理
    //第二个参数 调用的方法(执行的业务)
    //第三个参数 方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //前 这里可以是其他的执行代码
        Object ret = null;
        try {
            //调用目标方法
            ret = method.invoke(target, args);
        } catch (Exception e) {
            log.error("调用{}.{}发生异常", target.getClass().getName(), method.getName(), e);
            throw e;
        }
        //后 这里可以是其他的执行代码
        return ret;
    }
}

使用动态代理的代码却变了:

//第一句创建了一个代理类对象
ProxyHandler handler = new ProxyHandler(); 
//第二句创建了一个客户对象
HotHair hothair = new HotHair();
//第三句创建了一个动态代理类对象,然后用接口引用
Hair proxy = (Hair) Proxy.newProxyInstance(hothair.getClass().getClassLoader(),
                hothair.getClass().getInterfaces(), handler);
//第四句调用接口引用的方法,实则调用的是动态代理类对象的方法,执行的内容就是invoke()方法的内容
proxy.execute();

4. 命令模式 

命令模式主要有三个角色:命令、执行者、调用者。就是将命令封装为对象,由调用者触发命令的执行,由执行者运行具体的命令代码。同一个命令形式,不同的人有不同的执行方式,比如,有一桶水,命令是“将这通水送到某某小区”,将这条命令发给不同的人,就会有不同的执行方式,给小明的话,小明可能会用电动车运输,给小王的话,可能小汽车运输。

定义命令:

public interface Commond{  //定义命令接口
    void cutHair();
    void hotHair();
}

public class HairCommond implements Commond{ //命令
    public Acceiver acceiver = null;
    public HairCommond(Acceiver acceiver){
        this.acceiver = acceiver;
    }
    @Override
    public void cutHair(){  //命令执行的内容由执行者决定
        this.acceiver.shortHair();
    }
    @Override
    public void hotHair(){ //命令执行的内容由执行者决定
        this.acceiver.curllyHair(); 
    }
}

public class Acceiver{  //执行者
    public void shortHair(){
        //执行减短头发
    }        
    public void curllyHair(){
        //执行烫卷头发
    }
}

public class Invoker{  //命令调用者
    private Commond commond;
    public void setCommond(Commond commond){
        this.commond = commond;
    }
    public Commond getCommond(){
        return commond;
    }
    public executeCutHairCommond(){
        this.commond.cutHair();
    }
    public executeHotHairCommond(){
        this.commond.hotHair();
    }
}

测试一下:

public static void main(String[] args){
    Acceiver acceiver = new Acceiver();
    HairCommond hairCommond = new HairCommond(acceiver);
    Invoker invoker = new Invoker();
    invoker.setCommond(hairCommond);
    invoker.executeCutHairCommond();    
}
个人领悟:可以定一个执行者接口,实现多个执行者,这样,针对同一个命令,我们可以指定不同的执行者,即达到同一个命令可以有多种执行

5. 策略模式

同一事情可以有多种不同的执行策略,进而达到不同的执行结果。

public interface Strategy{ //定义策略接口
    executeStrategy();
}

public class StrategyOne implements Strategy{ //策略一
    @Override
    executeStrategy(){
        //执行策略一
    }
}

public class StrategyTwo implements Strategy{  //策略二
    @Override
    executeStrategy(){
        //执行策略二
    }
}

public class Context{  //策略模式的核心类
    public Strategy strategy;
    public void execute(){ //执行策略
        this.strategy.executeStrategy();
    }
    public changeStrategy(Strategy strategy){ //更换策略
        this.strategy = strategy;
    }
}

测试一下:

public static void main(String[] args){
    Context context = new Context();
    context.changeStrategy(new StrategyOne());
    context.execute(); //执行的是策略一
}

6. 适配器模式

适配器就是将两种不能代码协作的(类或者接口)进行适配,目的是让这两种八竿子打不着的类或者接口能够产生联系,比如一个手机的充电器是2头的,但是插座是3孔的,怎么适配呢?

public interface ThreeHoleSocket{ //定义一个三孔插座接口
    void threeHoleCharge();  //三孔充电
}

public class TwoHoleSocket{  //两孔插座
    void twoHoleCharge();  //两孔充电
}

public Adapter implements ThreeHoleSocket{
    TwoHoleSocket twoHoleSocket;
    public Adapter(TwoHoleSocket twoHoleSocket){
        this.twoHoleSocket = twoHoleSocket;
    }
    @Override
    void threeHoleCharge(){ //用三孔插座的电
        this.twoHoleSocket.twoHoleCharge();  //利用两孔冲给手机
    }
}

怎么让这上面两个能够协作起来呢,让工人能够打包水果。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值