说说设计模式

亚瑟 等级 311 0 0

设计模式

  • 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。
  • 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
  • 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
  • 设计模式不是一种方法和技术,而是一种思想。设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序可复用。
  • 学习设计模式能够促进对面向对象思想的理解,反之亦然。它们相辅相成。

设计模式分类

设计模式按照功能分为三类23种:

  1. 创建型(5种):工厂模式、抽象工厂模式、单例模式(重点)、原型模式、构建者模式
  2. 结构型(7种):适配器模式、装饰模式、代理模式(重点) 、外观模式、桥接模式、组合模式、享元模式
  3. 行为型(11种):模板方法模式、策略模式、观察者模式、中介者模式、状态模式、责任链模式、命令模式、迭代器模式、访问者模式、解释器模式、备忘录模式

设计模式注意事项:

每个设计模式都有自己的优缺点,要根据实际情况,去选择合适的设计模式

创建型

为什么要使用创建型设计模式?

  • 有些时候开发人员不想要知道对象的创建细节,只是想要创建一个可以使用的对象。所以可以将创建的工作给到专业的类。
  • 由于要使用的类是第三方包提供的,我们基本上不了解如何去构造该对象。

简单工厂

一个工厂类做所有的事情,可以生产所有需要的对象(万能工厂)

优缺点

优点:

  • 很明显,简单工厂的特点就是“简单粗暴”,通过一个含参的工厂方法,我们可以实例化任何产品类。
    缺点:
  • 任何”东西“的子类都可以被生产,负担太重。当所要生产产品种类非常多时,工厂方法的代码量可能会很庞大。
  • 违反了开闭原则。

改造方案:

  1. spring改造
  2. 工厂方法

工厂方法

工厂会是多个,生产一个产品(抽象概念),针对不同的需求,可以创建不同的工厂,问题是如何创建工厂?

先有造工厂的方法(标准也叫接口),在根据标准去造工厂,由这个工厂创建不同的对象

优缺点

优点:

  • 工厂方法模式就很好的减轻了工厂类的负担,把某一类/某一种东西交由一个工厂生产;(对应简单工厂的缺点1)
  • 同时增加某一类”东西“并不需要修改工厂类,只需要添加生产这类”东西“的工厂即可,使得工厂类符合开闭原则。
    缺点:
  • 对于某些可以形成产品族(一组产品)

抽象工厂

工厂会是多个,生产多个产品(抽象概念),这多个产品属于一个产品族

构建者

将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。

构建者模式和工厂模式很类似,区别在于构建者模式是一种个性化产品的创建。而工厂模式是一种标准化的产品创建。

构建者模式角色

  1. 产品:要生产的对象
  2. 导演类:如何去订制一个对象
  3. 构建者类:完成私人订制功能的具体类

构建者模式与常用的set模式区别:使用builder模式(一般是使用内部builder类实现)时,尚未产生对象,还不能被使用。所以,不用担心对象污染。

原型模式

原型模式虽然是创建型的模式,但是与工厂模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。

就是说不用new的方式创建对象,而是直接复制对象,分为浅复制和深复制(用的底层C语言的clone和serialize方式)。

浅拷贝

只有对象的基本类型和String类型进行复制,而引用类型只是复制了引用,没有将引用类型对应的对象进行复制。
需要实现clonable 
/* 浅复制 */
public Object clone() throws CloneNotSupportedException {
    Prototype proto = (Prototype) super.clone();
    return proto;
} 

深拷贝

引用类型对应的对象,也在内存中复制一份。
需要实现serializable 
/* 深复制 */
public Object deepClone() throws IOException, ClassNotFoundException {
    /* 写入当前对象的二进制流 */
    ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
    ObjectOutputStream oos = new ObjectOutputStream(bos); 
    oos.writeObject(this);
    /* 读出二进制流产生的新对象 */
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
} 

单例模式

单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

单例模式分成两种实现方式:懒汉式和饿汉式

饿汉式

在类初始化时就将对象创建出来

public class Student1 {

    // 2:成员变量初始化本身对象
    // 静态类在进行类加载(静态变量初始化)的时候,在JVM内部是保证线程安全的
    private static Student1 student = new Student1();

    // 1.构造私有
    private Student1() {}

    // 3:对外提供公共方法获取对象
    public static Student1 getSingletonInstance() {
        return student;
    }

    public void sayHello(String name) {
        System.out.println("hello," + name);
    }
} 

饿汉式不存在线程安全问题,但是会浪费内存资源

懒汉式

在第一次使用该类的时候

public class Student2 {

    //1:构造私有
    private Student2(){}

    //2:定义私有静态成员变量,先不初始化
    private static Student2 student = null;

    //3:定义公开静态方法,获取本身对象
    public static Student2 getSingletonInstance(){
        //没有对象,再去创建
        if (student == null) {
            student = new Student2();
        }
        //有对象就返回已有对象
        return student;
    }
} 

节省内存资源,但是存在线程安全问题

懒汉式三种线程安全写法

  1. 双重检查锁
  2. 静态内部类
  3. 枚举

并发编程三大特性

  1. 原子性: CPU操作指令必须是原子的;广义上是字节码指令是原子的
  2. 有序性: 狭义上指的是CPU操作指令是有序执行的;广义上指的是字节码指令是有序执行的
  3. 可见性: 在多核CPU下,不同的CPU缓存之间是相互不可见的

双重检查锁

首先来看几个问题:

1. 如何保证原子性?
加锁(synchronize、Lock)

2. 指令重排序?
JIT即时编译器的优化策略,happend-before六大原则。
两行代码之后的操作,执行结果不存在影响,就可以发生指令重排序。

3. 对象在JVM中的创建步骤
Student student = new Student(); 过程中
1.new:开辟JVM堆中的内存空间
2.将内存空间初始化(指的就是对象的成员变量初始化为0值)
3.将内存空间地址(引用地址)赋值给引用类型的变量
结论:在new对象的时候,JIT即时编译器会根据运行情况,对对象创建的过程进行指令重排序(132)

4. 线程执行需要竞争CPU时间片来执行 

由以上几点,当线程A双重检查锁单例中进行对象创建的时候释放时间片,线程B进入单例并识别对象不为空,并调用单例中的属性时,会有NullPointer风险。

所以在双重检查锁中,使用volatile关键字。该关键字有几个作用:

  1. 禁止被它修饰的变量发生指令重排序。是通过内存屏障实现的。
  2. 禁止使用CPU缓存(保证可见性),内部实现是被volatile关键词修饰的变量,在修改之前都需要将CPU缓存中的数据刷新到主内存中。
public class Student5 {

    private volatile static Student5 student;

    private Student5() {
    }

    public static Student5 getSingletonInstance() {
        if (student == null) {
            // B线程检测到student不为空
            synchronized (Student5.class) {
                if (student == null) {
                    student = new Student5();
                    // A线程被指令重排了,刚好先赋值了;但还没执行完构造函数。
                }
            }
        }
        return student;// 后面B线程执行时将引发:对象尚未初始化错误。
    }

} 

静态内部类

类似于饿汉式。

public class Student {

    private Student() {}

    /*
     * 此处使用一个内部类来维护单例 JVM在类加载的时候,是互斥的,所以可以由此保证线程安全问题
     */
    private static class SingletonFactory {
        private static Student student = new Student();
    }

    /* 获取实例 */
    public static Student getSingletonInstance() {
        return SingletonFactory.student;
    }

} 

原理:当使用Student.getSingletonInstance()时,相当于SingletonFactory的静态初始化,而SingletonFactory使用饿汉式,所以线程安全。

破坏单例的方式

  1. 反射攻击

    public class SingletonAttack {
     public static void main(String[] args) throws Exception {
         reflectionAttack();
     }
     public static void reflectionAttack() throws Exception { 
         //通过反射,获取单例类的私有构造器
         Constructor constructor = DoubleCheckLockSingleton.class.getDeclaredConstructor(); //设置私有成员的暴力破解
         constructor.setAccessible(true); // 通过反射去创建单例类的多个不同的实例 
         DoubleCheckLockSingleton s1 = (DoubleCheckLockSingleton)constructor.newInstance(); // 通过反射去创建单例类的多个不同的实例 DoubleCheckLockSingleton s2 = (DoubleCheckLockSingleton)constructor.newInstance();
         s1.tellEveryone();
         s2.tellEveryone();
         System.out.println(s1 == s2);
     }
    } 
  2. 序列化攻击 就是深复制

    public class SingletonAttack {
     public static void main(String[] args) throws Exception {
         serializationAttack();
     }
     public static void serializationAttack() throws Exception { // 对象序列化流去对对象进行操作
         ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serFile"));
         //通过单例代码获取一个对象
         DoubleCheckLockSingleton s1 = DoubleCheckLockSingleton.getInstance(); //将单例对象,通过序列化流,序列化到文件中 outputStream.writeObject(s1); // 通过序列化流,将文件中序列化的对象信息读取到内存中
         ObjectInputStream inputStream = new ObjectInputStream(new
         FileInputStream(new File("serFile"))); //通过序列化流,去创建对象
         DoubleCheckLockSingleton s2 = (DoubleCheckLockSingleton)inputStream.readObject();
         s1.tellEveryone();
         s2.tellEveryone();
         System.out.println(s1 == s2);
     }
    } 

枚举单例

public enum EnumSingleton {
    INSTANCE;
    public void tellEveryone() {
        System.out.println("This is an EnumSingleton " + this.hashCode());
    } 
} 
  1. 枚举单例预防反射攻击

java枚举类都隐式继承Enum抽象类,而Enum抽象类没有无参构造方法,只有一个

protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
} 

而在暴力破解时使用反射Constructor时

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enumobjects"); 

又不允许Enum类进行实例化。

  1. 枚举单例防御序列化攻击

在使用ObjectInputStream类时,对枚举类型有一个专门的readEnum()方法来处理,其简要流程如下:

  • 通过类描述符取得枚举单例的类型EnumSingleton;
  • 取得枚举单例中的枚举值的名字(这里是INSTANCE);
  • 调用Enum.valueOf()方法,根据枚举类型和枚举值的名字,获得最终的单例。
    这种处理方法与readResolve()方法大同小异,都是以绕过反射直接获取单例为目标。不同的是,枚举对序列化的防御仍然是JDK内部实现的。

综上所述,枚举单例确实是目前最好的单例实现了,不仅写法非常简单,并且JDK能够保证其安全性, 不需要我们做额外的工作。

正文到此结束

本文转自 http://blog.pkspace.cn/article/4,如有侵权,请联系删除。

收藏
评论区

相关推荐

JavaScript中的类型
JavaScript中的类型 一、关于类型 什么叫做类型?简单地说,类型就是把内存中的一个二进制序列赋予某种意义。比如,二进制序列0100 0000 0111 0000 0001 0101 0100 1011 1100 0110 1010 0111 1110 1111 1001 1110如果看作是64位无符号整数类型就是4
Python基本数据类型
在了解基本数据类型的时候,我们需要了解基本数据类型有哪些?数字int、布尔值bool、字符串str、列表list、元组tuple、字典dict等,其中包括他们的基本用法和其常用的方法,这里会一一列举出来,以便参考。然后我们还需要了解一些运算符,因为这些基本数据类型常常会用于一些运算等等。 一、运算符   运算通常可以根据最终获得的值不同,可以
Groovy中的类
Groovy 中的类 迄今为止,您已经用 Groovy 输出了许多次 “Hello World”,已经操作了集合,用闭包在集合上迭代,也定义了您自己的闭包。做所有这些工作时,甚至还没有讨论那个对 Java 开发人员来说至关重要的概念 — 类。 当然,您已经在这个教程中使用过类了:您编写的最后几个示例就是在不同类的 main() 方法中。而且,您已经知道,在
Python入门之类(class)
Python3 面向对象 Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。 如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对
Python入门之类(class)
Python3 面向对象 Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。 如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对
python类的继承
一、概述    面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。   通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”,继承的过程,就是从一般到特殊的过程。在某些 OOP 语言中,一个子类可以继
Python的新式类和旧式类
概述: Python中支持多继承,也就是一个子类可以继承多个父类/基类。当一个调用一个自身没有定义的属性时,它是按照何种顺序去父类中寻找的呢?尤其是当众多父类中都包含有同名的属性,这就涉及到新式类 和 经典类的区别。 多继承: class Food(object): 2 3 def __init__(self, name, col
Python新式类与经典类(旧式类)的区别
Python新式类与经典类(旧式类)的区别 Python中类分两种:旧式类和新式类: ➤新式类都从object继承,经典类不需要。 ➤新式类的MRO(method resolution order 基类搜索顺序)算法采用C3算法广度优先搜索,而旧式类的MRO算法是采用
我的golang笔记
面向对象思想 面向对象简介 编程思想 与编程语言无关。 C语言、Go中的 结构体 就是后来面向对象编程语言中的类。 面向对象编程:高内聚,低耦合。 特性 继承 —— 匿名字段(实名字段) 封装 —— 方法 多态 —— 接口(interface) Go 语言是典型的面向对象编程语言。 通过程序描述对象 创建类(指定类属性) 类属性:静
说说设计模式
设计模式 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总
Java枚举类妙用
上代码:java@Getterpublic enum NodeIdStatusRef { / reference / NI001_A001_AGREE(NodeIdEnum.NI001.getNodeId(), OrderStatus.A101.getCode(), OrderStatus.A101.getCo
如何避免JavaScript类型转换
你是否经历过JavaScript中的某些值比较没有得到预期结果的情况?看下面的情况:即使[]0结果为真,if[]条件也没有根据结果执行。有没有想过为什么会这样?本文主要说明这些值比较的工作原理以及影响它们的因素。在深入解释之前,大家要熟悉一个概念:类型转换。 什么是 JavaScript 类型转换?这也称为类型强制。对于不熟悉此概念的人来说,它只是将值从一种
Java 实用类
实用类 枚举 Math Random String StringBuffer 日期类 枚举枚举(Enum)是一种有确定取值区间的数据类型,它本质上是一种类,具有简洁、安全、方便等特点。可以这样理解,枚举的值被约束到一个特定的范围,只能取这个范围以内的值。我们为什么要用枚举呢?我们在描述对象的一些属性特征时,可选择的值是一个特定范围的,不能随便定义。比如性别只
基于Maven工程下的MyBatis基本使用之SQL传单/多参、多表关联查询
MyBatis基本使用声明:基于《基于Maven工程下的MyBatis框架+MySQL+连接池的数据查询操作》进一步拓展,相关配置文件、数据文件可阅上篇。 SQL传单/多参 在goods.xml新增两个<select: <!单参数传参,使用paramterType指定的数据类型即可,SQL中value提取参数 <select id"sel
学妹问我Java枚举类与注解,我直接用这个搞定她!
很多人问我学妹长什么样,不多说 上图吧![](https://shimo.im/docs/9GTP6XrJg9J88cJD/)@ 一、枚举类类的对象只有有限个, 确定的. 我们称此类为枚举类.说明:1.类的对象只有有限个,确定的。如: 星期:Monday(星期一)、......、Sunday(星期天) 性别:Man(男)、Woman(女)  季节:Sp