美文网首页
6. Interview-GoF

6. Interview-GoF

作者: allen锅 | 来源:发表于2020-07-21 16:38 被阅读0次
23种GoF(5+7+11) 设计模式之间的关系

1 设计模式的设计原则(6大原则)

  • 单一职责原则
    一个方法、一个类、一个微服务子系统都只有一个职责
  • 高内聚低耦合
    单个类或子系统内聚程度要高、专注解决一个问题,彼此之间的耦合性要低,便于微服务拆分、扩展、重构等
  • 开闭原则
    一个类应该对扩展开放,对修改关闭
  • 依赖倒置原则
    要依赖抽象,不要依赖具体类,面向接口编程而不是面向实现编程
  • 里氏替换原则
    子类型能够正确替换父类型,对程序功能没影响,反之不行,继承和多态的体现。
  • 迪米特原则
    最小知识原则,一个类的调用者或依赖者只需要知道它所需要的方法即可,其他的知道的越少越好,类与类之间耦合性越小越好。

2 设计模式分类

设计模式起源于建筑行业,1995年四人帮GoF提出了23种设计模式。大致可以分为三类:

  • 创建型设计模式(5个):描述对象实例化过程
    • 抽象工厂模式(Abstract Factory)
    • 工厂方法模式(Factory Method)
    • 建造者模式(Builder)
    • 原型模式(Prototype)
    • 单例模式(Singleton)
  • 结构型设计模式(7个):描述如何组装类和对象以获得更大的结构
    • 适配器模式(Adapter)
    • 桥接模式(Bridge)
    • 组合模式(Composite)
    • 装饰模式(Decorator)
    • 外观模式(Facade)
    • 享元模式(Flyweight)
    • 代理模式(Proxy)
  • 行为型设计模式(11个):描述算法和对象间职责的分配
    • 职责链模式(Chain of Responsibility)
    • 命令模式(Command)
    • 解释器模式(Interpreter)
    • 迭代器模式(Iterator)
    • 中介者模式(Mediator)
    • 备忘录模式(Memento)
    • 观察者模式(Observer)
    • 状态模式(State)
    • 策略模式(Strategy)
    • 模板方法模式(Template Method)
    • 访问者模式(Visitor)

3 工厂模式

3.1 简单工厂/静态工厂

创建型模式,其实不算做23种GoF中的设计模式。根据不同的参数返回不同类的实例。一般被创建的实例都有共同的父类。

3.2 工厂方法模式

  • 定义接口,父类延迟到子类实现,子类选择实例化哪个类
  • 工厂方法模式是针对单一产品,抽象工厂模式是针对多个产品

3.3 抽象工厂模式

  • 定义接口,父类延迟到子类实现,子类选择实例化哪个类
  • 工厂方法模式是针对单一产品,抽象工厂模式是针对多个产品

4 单例模式

4.1 两种方式

懒汉式:空间换时间,延迟加载,线程非安全

public class Singleton {

    private static Singleton instance;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

恶汉式:时间换空间,线程安全,利用了static的特性,只会在JVM加载类的时候初始化一次,不会出现并发

public class Singleton {

    // 4.定义一个静态变量存储创建的实例,直接new实例,主动利用static特性(static变量在JVM加载类的时候初始化一次,不会出现并发)
    private static Singleton instance = new Singleton();

    // 1.私有化构造方法
    private Singleton() {

    }

    // 2.提供一个方法返回实例
    // 3.这个方法要定义成类方法,要加上static
    public static Singleton getInstance() {
        // 5.直接返回这个实例
        return instance;
    }
}

4.2 懒汉式线程安全问题

  • 恶汉式是线程安全的,由JVM保证的,JVM加载类的时候不会出现并发
  • 懒汉式是线程非安全的,可以进行如下优化:

synchronized同步方法

public class Singleton {

    // 4.定义一个变量存储创建的实例
    // 5.因为要在静态方法中使用,所以被迫加上static,并非主动使用static的特性
    private static Singleton instance = null;

    // 1.私有化构造方法
    private Singleton() {
    }

    // 2.提供一个方法返回实例
    // 3.这个方法要定义成类方法,要加上static
    public static synchronized Singleton getInstance() {
        // 6.判断是否有实例
        if (null == instance) {
            // 6.1 没有就创建一个实例
            instance = new Singleton();
        }

        // 6.2 如果有直接使用
        return instance;
    }
}

synchronized同步代码块,双重检查加锁

public class Singleton {

    // 4.定义一个变量存储创建的实例
    // 5.因为要在静态方法中使用,所以被迫加上static,并非主动使用static的特性
    private volatile static Singleton instance = null;

    // 1.私有化构造方法
    private Singleton() {
    }

    // 2.提供一个方法返回实例
    // 3.这个方法要定义成类方法,要加上static
    public static  Singleton getInstance() {
        // 6.判断是否有实例
        if (null == instance) {
            // 6.1 没有就创建一个实例
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        // 6.2 如果有直接使用
        return instance;
    }
}

静态内部类+多线程缺省同步锁

  • 没有用synchronized,效率高,JVM保证线程安全
public class Singleton {

    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
  • 存在反射攻击
public static void main(String[] args) throws Exception {
    Singleton singleton = Singleton.getInstance();
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton newSingleton = constructor.newInstance();
    System.out.println(singleton == newSingleton);
}
  • 存在反序列化攻击
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

public class Singleton implements Serializable {

    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }

    private Singleton() {

    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        byte[] serialize = SerializationUtils.serialize(instance);
        Singleton newInstance = SerializationUtils.deserialize(serialize);
        System.out.println(instance == newInstance);
    }
}

枚举

  • JVM保证线程安全
  • 自动支持序列化机制,防止多次实例化
  • 防止反射机制破坏单例
  • 防止反序列化时破坏单例
public enum Singleton {

    // 定义一个枚举元素,代表Singleton的唯一实例
    INSTANCE;

  public EnumSingleton getInstance(){
        return INSTANCE;
    }
}
public class User {
    //私有化构造函数
    private User(){ }
 
    //定义一个静态枚举类
    static enum SingletonEnum{
        //创建一个枚举对象,该对象天生为单例
        INSTANCE;
        private User user;
        //私有化枚举的构造函数
        private SingletonEnum(){
            user=new User();
        }
        public User getInstnce(){
            return user;
        }
    }
 
    //对外暴露一个获取User对象的静态方法
    public static User getInstance(){
        return SingletonEnum.INSTANCE.getInstnce();
    }
}

public class Test {
    public static void main(String [] args){
        System.out.println(User.getInstance());
        System.out.println(User.getInstance());
        System.out.println(User.getInstance()==User.getInstance());
    }
}
结果为true
  • 调用枚举单例模式
public class Main {

    public static void main(String[] args) {
        Singleton.INSTANCE.doSomething();
    }
}

5 枚举为什么能实现单例模式?

  • 枚举是一组有限常量集,java枚举本质上是int值,构造器私有,只能自己实例化,所有的实例就是枚举定义的几个,其他地方不允许创建实例。

6 枚举为什么不能继承?

  • 可以实现接口,但不能继承类,因为所有枚举类都继承自java.lang.Enum(由编译器添加),同时java不支持多继承。

7 枚举为什么是线程安全的?

  • 枚举类在第一次被使用时会被JVM加载并初始化,由JVM层面保证线程安全。

8 为什么反序列化枚举类型也不会创建新的实例?

  • 普通类的反序列化是通过反射实现的,枚举类的反序列化不是通过反射实现的。源码基于jdk1.8
  • 枚举类型在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

9 原型模式

  • 原型模式是创建重复的对象,对对象的属性的完全复制,包括基本类型属性和引用类型的对象的属性的安全复制。当直接创建对象代价比较大时,可以采用这种模式。
    • 浅拷贝
      一般通过clone实现,引用类型的属性复制的是引用,而不是新的对象。原型对象需要实现java.lang.Cloneable。
    • 深拷贝
      • 可以通过手动进行set赋值,引用对象类型的属性,先创建该对象再赋值。
      • 通过序列化的方式进行赋值。对象需要实现java.io.Serializable。

10 建造者模式 / 生成器模式

  • 建造者模式将一个复杂对象的构建和表示分离,分离整体构建算法Director和部件构造Builder,也叫创建者模式或生成器模式。

  • Client---------》Director--------》Builder

    • Director负责整体构建算法,相对不变的部分,变化的部分要分离出去;
    • 生成器Builder负责变化的部分,负责构建的具体实现
  • 优点:

    • 使用建造者模式可以使客户端不必知道产品内部组成的细节。
    • 具体的建造者类之间是相互独立的,这有利于系统的扩展。
    • 具体的建造者相互独立,因此可以对建造的过程逐步细化,而不会对其他模块产生任何影响。
  • 缺点:

    • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
    • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

11 代理模式(Proxy)

  • Client----------代理对象Proxy-----------目标对象Target,为其他对象提供一种代理以控制对这个对象的访问。开闭原则,中介隔离。

静态代理:编译时就创建代理对象

  • 优点
    • 可以做到在不修改目标对象的功能前提下,对目标功能扩展。
  • 缺点:
    • 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。
    • 一旦接口增加方法,目标对象与代理对象都要维护。

动态代理 / JDK动态代理

  • 动态代理的主要特点就是能够在程序运行时JVM才为被代理对象生成代理对象。
  • 常说的动态代理也叫做JDK代理也是一种接口代理,JDK中生成代理对象的代理类就是Proxy,所在包是java.lang.reflect,JDK动态代理本质是基于java的反射机制,适用于有接口的类
  • 实现JDK动态代理要implements InnovationHandler并重写invoke方法
  • 代理对象不需要实现接口,但是被代理的目标对象一定要实现接口,否则不能使用动态代理,这是JDK动态代理的缺陷。

CGlib动态代理

  • CGlib动态代理不需要被代理的目标对象必须实现接口,CGlib动态代理本质是基于ASM字节码操纵技术,适用于没有接口的类。
  • CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
  • CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。

12 适配器模式(Adapter)

适配器模式
  • 转换适配,复用功能。尽量重构,少用适配。
  • 新老接口兼容问题,中国和美国的插座转换问题都可以用适配器模式。

13 外观模式 / 门面模式(Facade)

门面模式/外观模式

14 装饰模式(Decorator)

  • 动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

15 桥接模式(Bridge)

  • 将抽象部分和实现部分分离,使他们都能独立的变化。桥接模式是为了解决继承的缺陷而提出的设计模式。

  • Java中的实现:JDBC,各种驱动

  • 优点

    • 分离抽象接口及其实现部分。
    • 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
    • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
    • 实现细节对客户透明,可以对用户隐藏实现细节。
  • 缺点

    • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
    • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

16 组合模式(Composite)

组合模式
  • 不用区分组合对象和叶子对象,设计一个抽象类,来统一这两个对象
  • 表示对象的部分-整体层次结构用组合模式

17 享元模式(Flyweight)

享元模式
  • 池化技术是享元模式的典型应用,运用共享技术有效地支持大量细粒度的对象,共享、池化(String常量池、数据库连接池、缓冲池)、缓存
  • 享元模式的本质是分离与共享,重点在于分离变与不变。把一个对象的状态分为内部状态和外部状态,内部状态是不变的,外部状态是可变,通过共享不变的部分,达到减少对象数量并节约内存的目的。

18 观察者模式(Observer)

  • 观察者模式又叫做发布/订阅模式Publish/Subscribe
  • 被观察者(notify)---------》观察者1/2/.../N(update)
  • 推模式和拉模式
  • java中实现的观察者java.util.Observable和Observer
  • MQ、Redis、ZooKeeper的watch机制都用到了观察者模式

19 职责链模式 / 责任链模式(Chain of Responsibility)

职责链模式
  • 一个请求多个对象都有机会处理,将这些对象连成一条链,并沿着这条链路传递该请求,直到有一个对象处理请求。
  • 常用责任链模式的场景:
    • 工作流、审批流
    • springmvc的HandlerExecutionChain

20 策略模式(Strategy)

策略模式

解决什么问题?

  • 商场针对不同的客户有不同的折扣,有不同的算法,传统的方式需要写很多的if判断,这样不利于程序扩展和维护,可以引入策略模式Strategy
  • 程序中要写很多if-else的时候,可以考虑使用策略模式

实现逻辑

  • 将不同的折扣计算定义为不同的算法,他们实现同一个接口Strategy,不同的算法实现自这个Strategy接口,引入一个上下文对象Context,负责持有算法,但是具体的选择哪个实现算法由Client决定

  • 策略算法是相同行为的不同实现

  • 可以在Client端和Context选择具体的策略算法

扩展性

新增加一种策略的两种实现方式:

  • 新增一个Context继承之前的Context

    • 风格统一,所有的策略需要的数据都来自于Context,扩展的策略也可以作为公共功能给其他人使用
  • 新增一个策略算法implements原来的策略接口

    • 实现简单,面向接口编程,但是风格不统一,别的策略数据都来自Context,扩展的策略一部分来自Context、一部分来自自己。

策略模式的本质

分离算法,选择实现

21 模板方法模式(Template Method)

  • 模板方法模式是定义一个算法的骨架,将其中一些具体步骤的实现延迟到子类,使得子类不改变算法的骨架就可以重新定义该算法的具体的步骤。
  • 类似重构,将类似的方法抽出一个公共的方法来,相当于算法的骨架
  • AbstractClass算法骨架(抽象类,不变的部分,移到父类)------------ConcreteClass具体的实现子类(变的部分,放到子类实现)
  • 模板方法定义为抽象类,一般既要约束子类的行为,又要为子类提供公共功能的时候就用抽象类。
  • java使用回调方式实现模板方法,比继承耦合程度更低,基于接口。

22 状态模式(State)

状态模式
  • 状态模式的本质是根据状态来分离和选择行为

解决什么问题?

  • if-else问题
  • 工作流问题

23 备忘录模式(Memento)

备忘录模式
  • 在不破坏对象封装性的前提下,保存和恢复对象的状态
  • 一个备忘录是一个对象,它存储另一个对象某一个瞬间的内部状态,后者被称为备忘录的原发器。
  • 备忘录模式的本质是保存和恢复对象状态,保存是手段,恢复才是目的

24 中介者模式(Mediator)

中介者模式
  • 优点
    • 高内聚,低耦合,使用中介者明显降低了对象之间的耦合
    • 通过让对象彼此解耦,增加对象的复用性
    • 通过将控制逻辑集中,可以简化系统维护
    • 通过中介者使一对所变成了一堆一,便于理解
  • 缺点
    • 如果涉及不好,引入中介者会使程序变的复杂
    • 中介者承担过多责任,维护不好会出大事

25 命令模式(Command)

命令模式
  • Client------------》接收者对象----------》命令对象----------》Invoker
  • 命令队列,可以将命令记入日志,允许接受请求的一方否决请求,容易实现请求的撤销和重做,新增加的命令也很容易扩展

26 解释器模式(Interpreter)

解释器模式
  • 定义一种语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子

27 迭代器模式(Iterator)

迭代器模式
  • 聚合对象Aggregate------迭代器Iterator
  • 把聚合对象的遍历和访问从聚合对象中分离出来,放入单独的迭代器中

28 访问者模式(Visitor)

访问者模式
  • 访问者模式是一种将数据操作和数据结构分离的设计模式。最复杂的设计模式,使用率不高。

  • 访问者模式的优点:

    • 各角色职责分离,符合单一职责原则
    • 具有优秀的扩展性
    • 如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。
    • 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
    • 灵活性
  • 访问者模式的缺点:

    • 具体元素对访问者公布细节,违反了迪米特原则
    • 违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有依赖抽象

29 spring用到了哪些设计模式?用在什么场景?

  • 工厂模式,IoC,BeanFactory,ApplicationContext
  • 单例模式,spring的bean的作用域默认是singleton,还有prototype等
  • 代理模式,AOP,JDK动态代理,cglib动态代理
  • 模板方法模式,jdbcTemplate,hibernateTemplate
  • 观察者模式,spring事件驱动模型,事件角色ApplicationEvent、事件监听ApplicationListener、事件发布者ApplicationEventPublisher
  • 适配器模式
    • AOP的增强或通知advice
    • springmvc的controller
  • 装饰模式
    • spring配置DataSource动态切换数据源,装饰模式都在类名上加 Wrapper或者 Decorator
  • 策略模式
    • 加载资源文件Resource,ClassPathResource、FileSystemResource

30 Tomcat用到了哪些设计模式?

  • 门面模式
    • ApplicationContext、ServletContext
  • 观察者模式
    • 控制组件生命周期的Lifecycle
    • servlet实例的创建
    • Session的管理
  • 责任链模式
    • Container
  • 命令模式
    • connector通过命令模式调用container
  • 装饰器模式
    • javax.servlet.http.HttpServletRequestWrapper

相关文章

  • 6. Interview-GoF

    1 设计模式的设计原则(6大原则) 单一职责原则一个方法、一个类、一个微服务子系统都只有一个职责 高内聚低耦合单个...

  • 6.

    我和比我年纪小的女孩儿们一起跳舞,这个舞蹈班里,我年龄最大。 一把老骨头了才开始学跳舞,一些考验柔韧度的动作我...

  • 6.

    江边柳 唐 雍裕之 袅袅古堤边,青青一树烟。...

  • 6.

    宁柒回到盛世刚出电梯遇到了匆忙往外走的乔飞。 大飞哥,你这是要去哪啊? 啊 宁柒啊 薇姐和其他部门的人在楼上开会商...

  • 6.

    9-1 等待登机ing....美国,我来了,带着无尽的好感与钦佩,扑面而来...有人说,美国真的是个神奇的国家,能...

  • 6.

    今日打卡金句:【Day4】 What's this? It's an air-conditioner. Is th...

  • 6.

    护照上莫名其妙多了很多中东国家的签证,在土耳其遇到一个强奸犯但是逃走了,后来又遇到一个会说日语的藏族老婆婆其实是上...

  • 6.

    居然,,,见面了。 说不完的话,聊不完的八卦。 你不自觉走着走着便牵着手。搂着肩。有个人照顾着,很温暖。 见到我,...

  • 6.

    请你看我的平静 它就像今天的云 厚重沉实 我就让你这样看到 这样感受 因为你在看我的时候 我也在看着你

  • 6.

    当思绪停留处,执笔挥发。 让万千的欲望,止于笔上。 我本乡村少年,未曾见过,紫玉上纸。 如今见了, ...

网友评论

      本文标题:6. Interview-GoF

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