程序员技术干货Java 杂谈

面试常见各种设计模式

2017-10-03  本文已影响962人  柠檬乌冬面

今天分享几个常见的设计模式,这些设计模式是我在面经里面看到的。也就意味着公司在面试过程中常考的可能性相对较大,但是也不绝对。比起23种设计模式要全部记住似乎还是需要费一点功夫的。所以这里就本人认为的几个常见的设计模式进行相关资料的整理,方便复习和记忆。

单例模式

概念

java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍二种:懒汉式单例、饿汉式单例。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

代码实例
//懒汉式
public class Singleton {

    // 需要加上volatile关键字来保证线程安全 对于双重检测的时候
    private static volatile Singleton singleton = null;

    private Singleton() {
        System.out.println("init class");
    }

    // 第一种方法: 利用synchronize关键字来实现线程安全
    public synchronized static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }

        return singleton;
    }

    // 第二种方法: 双重检测 来确保线程安全 同时将同步块放在方法内减少每次都需要同步加锁带来的消耗
    public static Singleton getInstance1() {
        if (singleton == null) {

            synchronized (singleton) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }

        return singleton;
    }

    // 第三种方法: 静态内部类实现懒汉式 实现了线程安全又避免同步带来的影响
    private static class LazyLoader {
        private static final Singleton SINGLETON = new Singleton();
    }

    public static final Singleton getInstance3() {
        return LazyLoader.SINGLETON;
    }
}

上面展现了三种方法来实现单例模式

1.第一种直接在方法上使用synchronize关键字 保证了线程安全,只是每次调用都需要同步 比较影响性能。而且单例模式下,创建的概率远低于使用返回实例的概率

  1. 第二种方法是双重检测 在返回实例方法的内部使用了synchronize代码块来实现线程安全,确保了实例为null时才会进行创建实例和同步的过程 ,避免了每次都需要同步带来的消耗

第二种方法存在一个问题

我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行singleton = new Singleton();
c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

简单来说 就是你new的对象并没有初始化完成,突然感觉有点神奇。new了不就初始化了吗?
这里可能涉及的就是JAVA比较底层的一些东西,可以把创建对象概况成三个步骤:
1。 分配对象内存空间
2。 初始化对象
3。 把对象指向它的内存地址
步骤2和3可能会发生指令重排序,不一定就是可能先执行3然后再2 所以就发生了上面的情况。这个时候可以考虑使用volatile关键字或者第三种方法去实现

  1. 第三种方法 是使用静态内部类,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题
使用场景

在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例

工厂方法模式和抽象工厂模式

概念

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

按细的分一共有三种,一种是简单工厂,一种是工厂方法还有就是抽象工厂。GOF在《设计模式》一书中将工厂模式分为两类:
工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。
将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。

代码实例
package com.Model.Factory;

//产品接口
public interface Sender {

    public void send();
}

package com.Model.Factory;

//工厂模式
public class Factory {

    // 创建相应的产品 根据产品类型
    public Sender produceSender(String senderType) {
        if (senderType == null) {
            return null;
        } else if (senderType.equals("sms")) {
            return new SMSSender();
        } else if (senderType.equals("email")) {
            return new EmailSender();
        } else {
            System.out.println("invaild senderType....");
            return null;
        }
    }
}

// 具体的EMail产品类 实现统一的产品接口
class EmailSender implements Sender {

    @Override
    public void send() {
        System.out.println("sending a email...");
    }
}

// 具体的SMS产品类 实现统一的产品接口
class SMSSender implements Sender {

    @Override
    public void send() {
        System.out.println("sending a sms....");

    }

}

下面是工厂方法模式的代码实例
只有工厂的部分有一些区别

// 工厂方法模式 避免字符串有误创建不了实例 以及不需要实例化工厂 可以直接调用创建实例
class SenderFactory {
    public static Sender produceEmail() {
        return new EmailSender();
    }

    public static Sender produceSMS() {
        return new SMSSender();
    }
}

工厂方法和简单工厂的一些区别就是 使用static以及不需要使用字符串来判断创建类型,避免创建null实例和可以不需要生成工厂对象直接调用生成具体产品实例的好处。

下面是抽象工厂模式

生成抽象工厂

package com.Model.Factory;

//抽象工厂 具有生产具体工厂的功能
public interface Producer {

    public Sender provide();
}

具体的工厂

// 专门生成email的工厂
class EmailFactory implements Producer {

    @Override
    public Sender provide() {
        return new EmailSender();
    }

}

// 专门生产SMS的工厂
class SMSFactory implements Producer {

    @Override
    public Sender provide() {
        return new SMSSender();
    }
}

抽象工厂的好处就是可以不用修改原工厂的代码,直接进行拓展即可。比如我想再拓展一个电子发送器,则我只需要继承抽象工厂的接口,继承产品的接口。然后分别实现即可。

使用场景

方便地构造对象实例,而不必关心构造对象实例的细节和复杂过程。并且构造的实例过程比较复杂。

代理模式

概念

为其他对象提供一种代理以控制对这个对象的访问。在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

代码实例
package com.Model.Proxy;

//买车行为
public interface BuyCar {
    public void buycar();
}
package com.Model.Proxy;

public class People implements BuyCar {

    private int cash;
    private String vip;
    private String username;

    @Override
    public void buycar() {
        System.out.println(username + " is vip so he/she can buy any car...");
    }

    public int getCash() {
        return cash;
    }

    public void setCash(int cash) {
        this.cash = cash;
    }

    public String getVip() {
        return vip;
    }

    public void setVip(String vip) {
        this.vip = vip;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

}

// 代理类 去检测买车行为是否符合规则
class ProxyBuyCar implements BuyCar {

    private People People;

    public People getPeople() {
        return People;
    }

    public void setPeople(People people) {
        People = people;
    }

    @Override
    public void buycar() {
        if (People.getVip().equals("vip")) {
            People.buycar();
        } else if (People.getCash() >= 50000) {
            System.out.println(People.getUsername() + "buy a new car trade over...");
        } else {
            System.out.println(People.getUsername() + "people can't buy a car ");
        }
    }

}

关键在于代理类,利用一些规则去限制行为的发生。

使用场景

1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。

说到代理模式 最经典的就是Spring AOP 就是对目标类和方法进行切面增强的功能,通过在目标类的基础上增加切面逻辑,完成一些和程序业务无关的内容。主要有两种实现模式: 一种是JDK的动态代理,一种是Cglib代理

这两种的区别是:
JDK动态代理只能代理实现了接口的目标类
Cglib则不需要 Cglib是基于类的代理 原理是对目标类生成一个子类然后覆盖和实现它的所有方法并增强 所有Cglib不能对final修饰的类进行代理。

这里提供一个JDK代理的实例

package com.Model.Proxy;

//操作定义
public interface SubjectOperations {
    // 打印操作
    public void print();

    // 打印输入字符串操作
    public void printfStr(String string);
}

package com.Model.Proxy;

public class RealSubject implements SubjectOperations {

    @Override
    public void print() {
        System.out.println("我实现了接口 完成这个打印操作");

    }

    @Override
    public void printfStr(String string) {
        // TODO Auto-generated method stub
        System.out.println("打印输入的内容:  " + string);
    }

}


package com.Model.Proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class LogHandler implements InvocationHandler {

    private Object ImpClass;

    public LogHandler(Object realObject) {
        this.ImpClass = realObject;
    }

    public Object bind(Object impclass) {
        this.ImpClass = impclass;

        return Proxy.newProxyInstance(impclass.getClass().getClassLoader(), impclass.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("在调用代理的对象的真实方法前 我先调用一些自己的方法和规则..... 下面调用真实方法");

        System.out.println("Method:   " + method);

        method.invoke(ImpClass, args);

        System.out.println("调用代理的对象的真实方法后, 我进行一些逻辑处理 上面是调用的真实方法");

        return null;
    }

    public static void main(String[] args) {

    }
}

class Client {
    public static void main(String[] args) {
        RealSubject subject = new RealSubject();
        LogHandler handler = new LogHandler(subject);

        // 转化成接口 只能代理实现了接口的类
        SubjectOperations pSubject1 = (SubjectOperations) handler.bind(subject);
        System.out.println(pSubject1.getClass().getName());

        pSubject1.print();
        pSubject1.printfStr("YYYYY");
    }
}


控制台输出:

com.sun.proxy.$Proxy0
在调用代理的对象的真实方法前 我先调用一些自己的方法和规则..... 下面调用真实方法
Method:   public abstract void com.Model.Proxy.SubjectOperations.print()
我实现了接口 完成这个打印操作
调用代理的对象的真实方法后, 我进行一些逻辑处理 上面是调用的真实方法
在调用代理的对象的真实方法前 我先调用一些自己的方法和规则..... 下面调用真实方法
Method:   public abstract void com.Model.Proxy.SubjectOperations.printfStr(java.lang.String)
打印输入的内容:  YYYYY
调用代理的对象的真实方法后, 我进行一些逻辑处理 上面是调用的真实方法

职责链模式

概念

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。

代码实例

首先创建一个报销类,里面包含这个业务的一些具体信息 包括姓名 费用等

package com.Model.Chain_of_Responsibility;

public class MoneyRequest {

    private String name;
    private double money;

    public MoneyRequest(String name, double money) {
        super();
        this.name = name;
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

}

下面是一个抽象类 抽象的审批对象,处理报销请求

package com.Model.Chain_of_Responsibility;

public abstract class Leader {

    protected String name;
    protected Leader successor;

    public Leader(String name) {
        this.name = name;
    }

    public void Setsuccessor(Leader successor) {
        this.successor = successor;
    }

    public abstract void handleRequest(MoneyRequest moneyRequest);

}

下面是具体的实现类,用于实现审批的条件和规则

package com.Model.Chain_of_Responsibility;

public class Director extends Leader {

    public Director(String name) {
        super(name);
        // TODO Auto-generated constructor stub
    }

    public void handleRequest(MoneyRequest moneyRequest) {
        if (moneyRequest.getMoney() < 300) {
            System.out.println("主管   " + name + "   审批员工" + moneyRequest.getName() + "   的报销请求,请求金额为     "
                    + moneyRequest.getMoney());
        } else {
            this.successor.handleRequest(moneyRequest);
        }
    }

}

package com.Model.Chain_of_Responsibility;

public class GeneralManager extends Leader {
    public GeneralManager(String name) {
        super(name);
        // TODO Auto-generated constructor stub
    }

    public void handleRequest(MoneyRequest moneyRequest) {
        if (moneyRequest.getMoney() < 2000) {
            System.out.println("主管     " + name + "    审批员工     " + moneyRequest.getName() + "    的报销请求,请求金额为     "
                    + moneyRequest.getMoney());
        } else {
            System.out
                    .println(moneyRequest.getName() + "    尽然敢报销这个金额     " + moneyRequest.getMoney() + "    不想混了,看来!");
        }
    }
}

package com.Model.Chain_of_Responsibility;

public class Manager extends Leader {

    public Manager(String name) {
        super(name);
        // TODO Auto-generated constructor stub
    }

    public void handleRequest(MoneyRequest moneyRequest) {
        if (moneyRequest.getMoney() < 800) {
            System.out.println("主管   " + name + "   审批员工" + moneyRequest.getName() + "   的报销请求,请求金额为     "
                    + moneyRequest.getMoney());
        } else {
            this.successor.handleRequest(moneyRequest);
        }
    }
}

使用场景

JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

参考来源:
23种设计模式汇总整理
设计模式
java设计模式全面详解_Java开发中的23种设计模式

上一篇下一篇

猜你喜欢

热点阅读