面试常见各种设计模式

作者: 柠檬乌冬面 | 来源:发表于2017-10-03 23:19 被阅读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种设计模式

    相关文章

      网友评论

        本文标题:面试常见各种设计模式

        本文链接:https://www.haomeiwen.com/subject/tcdfyxtx.html