1. 软件架构设计原则
目标: 高内聚、低耦合、代码的可重用性、可读性、可靠性、可维护性
指导思想: 软件架构设计原则
- 单一职责原则
- 开闭原则
- 里氏替换原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特原则
- 合成复用原则
解决方法/方案: 软件开发的设计模式
实战:实战时要权衡,找到适合自己业务的点(从成本、维护等多角度考虑)
2. 软件开发的涉及的设计模式
2.1 分类:
- 创建型模式(创建对象的)
- 结构型模式(为了达到某种目的,增加一个结构/模块,强调结构变化)
- 行为型模式(为做某事,而提出了如何做,强调做)
2.2 创建型模式-单例模式
2.2.1 什么是单例模式
一个应用中,一个类只能有一个对象。单例模式强调是在一个应用中一个类只能创建一个对象,强调的是创建、只有1个。
2.2.2 什么样的类适合做单例
- 线程安全
- 无状态或状态不可变的类适合使用单例模式,状态说的就是对象的成员变量,无状态就该对象无成员变量或有成员变量但其成员变量不可变。
2.2.3 如何实现单例模式
分类: 饿汉、懒汉
饿汉:提前创建,使用时直接用
懒汉:使用时在创建
要求:在一个应用中只能创建一个对象并提供获取该对象的方法
- 饿汉式
方法1:
构造器私有+ 类加载机制(声明时赋值静态成员变量)+提供获取该对象的静态方法
public class SingletonHungry {
// 1. 构造函数私有化
private SingletonHungry(){}
// 2. 类加载机制
public static SingletonHungry singletonHungry = new SingletonHungry();
// 3. 获取单例对象的方法
public static SingletonHungry getInstance(){
return singletonHungry;
}
}
方法2:利用枚举(只声明一个对象)
public enum SingletonHungryEnum {
// 1. 只声明一个对象
SINGLETON_HUNGRY_ENUM_OBJECT;
public static void main(String[] args) {
for(int i =0; i< 50; i++){
new Thread(new Runnable() {
@Override
public void run() {
// 2. 获取单例对象的方法: 类.单例对象名
System.out.println(SingletonHungryEnum.SINGLETON_HUNGRY_ENUM_OBJECT
==SINGLETON_HUNGRY_ENUM_OBJECT);
}
}).start();
}
}
}
- 懒汉式
因为没有利用线程安全的类加载机制,因此创建对象时需要保证线程安全。
方法1: 锁整个创建对象方法
方法2: volatile 声明 + 双重检查 + 锁第2次检查
public class SingletonLazy {
// private static SingletonLazy singletonLazy;
/**
* 构造函数私有化
**/
private SingletonLazy(){}
/**
* 对外暴露一个方法获取类的对象
* 第1种方式: 这个是bug
**/
public static SingletonLazy getInstance() throws InterruptedException {
// 此句与singletonLazy = new SingletonLazy()不是原子的
if(null == singletonLazy){
TimeUnit.MICROSECONDS.sleep(500);
singletonLazy = new SingletonLazy(); //
}
return singletonLazy;
}
public static void main(String[] args) {
for(int i =0; i< 50 ; i++){
new Thread(() -> {
try {
System.out.println(SingletonLazy.getInstance());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
/**
* 对外暴露一个方法获取类的对象
* 第1种方式(ok): 通过synchronized加锁保证单例
* 问题:synchronized是重锁,再加上锁在方法上,锁粒度大,性能开销大
* 解决方法:锁粒度变小
**/
public static synchronized SingletonLazy getInstance2(){
if(null == singletonLazy){
singletonLazy = new SingletonLazy();
}
return singletonLazy;
}
/**
* 对外暴露一个方法获取类的对象
* 第1种方式(bug): 通过synchronized加锁保证单例
* 问题:synchronized是重锁,再加上锁在方法上,锁粒度大,性能开销大
* 解决方法:锁粒度变小
* singletonLazy = new SingletonLazy();是否是线程安全的呢?
* 不是,分3步骤(但顺序不固定)
* 1. 分配空间给对象
* 2. 在空间内创建对象
* 3. 将对象赋值给引用instance
* 假如某个线程1->3->2这样的顺序执行,会把值写到主内存,其他线程就会读取到instance最新值,但这个对象是不完全的对象
* 解决方法就是使用volatile关键字禁止指令重排:
private static volatile SingletonLazy singletonLazy;
**/
private static SingletonLazy getInstance22(){
if(null == singletonLazy){ // 此句与下面锁语句不是原子(不是原子说的可能存在多个线程执行这些代码)的
synchronized(SingletonLazy.class){
singletonLazy = new SingletonLazy(); //另外此句也不是原子性操作
}
}
return singletonLazy;
}
private static volatile SingletonLazy singletonLazy;
/**
* 对外暴露一个方法获取类的对象
* 第2种方式:
DCL=Double Checked Locking 双重检查锁定 + 内存模型(指令重排)
* 通过synchronized加锁保证单例(锁粒度变小)
*
* 解决方法:锁粒度变小
* singletonLazy = new SingletonLazy();是否是线程安全的呢?
* 不是,分3步骤(但顺序不固定)
* 1. 分配空间给对象
* 2. 在空间内创建对象
* 3. 将对象赋值给引用instance
* 假如某个线程1->3->2这样的顺序执行,会把值写到主内存,其他线程就会读取到instance最新值,但这个对象是不完全的对象
* 解决方法就是使用volatile关键字禁止指令重排: private static volatile SingletonLazy singletonLazy;
**/
public static SingletonLazy getInstance3(){
// 1. 第1重检查
if(null == singletonLazy){
synchronized(SingletonLazy.class){
// 2. 第2重检查
if(null == singletonLazy){
singletonLazy = new SingletonLazy();
}
}
}
return singletonLazy;
}
}
2.3 使用场景
饿汉:适用于创建对象繁琐、耗时,该对象占用内存较少(空间换时间)
懒汉:适用于创建对象简单,占用内存较多(时间换空间)
单例一般都要求为线程安全的即对象无状态(使用时,创建时)
第3课 设计模式之创建型模式-原型模式
3.1 什么是原型模式?
根据一个对象创建一个新的对象(这2个对象都属于一个类)。
3.2 分类
- 浅拷贝/浅克隆
原型对象与clone对象之间有一定的关系即一个对象改变可能使另一个对象状态的改变。
2. 深拷贝/深拷贝
原型对象与clone对象无论他们怎么变,都互不影响。
3.3 浅拷贝
Jdk提供一种方式:实现java.lang.Cloneable并且重写Object的clone方法(2者缺1不可)
3.4 深拷贝
常用2种方法:
- 序列化json字符串(必须实现Serializable接口)
默认方式,Java对象中的非静态和非transient的字段都会被定义为需要序列的字段
2. IO流
protected Person deepClone() throws IOException, ClassNotFoundException {
//1. 序列化/输出
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//2. 反序列化/输入
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (Person) ois.readObject();
}
以上代码注意并发问题
3.5 使用场景
- 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
- 如果系统要保存对象的状态,做备份使用
4. 设计模式之创建型模式-构建者模式
4.1 什么是构建者模式?
明确创建什么样的产品?即产品有什么组成?
明确创建产品的步骤?有没有变化即是否固定(步骤+固定)
明确变化的内容?(变与不变的分离)
创建一个对象的方法固定(流程固定),唯一的差异就是材料不同(变与不变分离)。
官话:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象/方法,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
目的:创建对象
共同特点: 流程固定,差异是材料不同
有4个角色:
- 产品: 定义了该产品有哪些材料组成(具体电脑)
- 抽象构建者:定义了创建对象的流程+创建对象(制作工艺+流水线)
- 具体构建者:实现抽象构建者(代工厂)
- 指挥长:根据传入不同的构建者,创建不同的产品(就是销售)
使用场景
- 创建的产品的步骤固定(不变),产品存在差异(变)
- 不变与变分离
- 创建产品的步骤繁琐(+链式设计模式更好)
5. 创建型设计模式-工厂模式之简单工厂
5.1 简单工厂又称静态工厂(提供一个静态方法,根据参数不同返回不同的对象)
5.2 三个角色:
抽象产品(所有产品的基类,定义产品规范)
具体产品(抽象产品的具体实现类)
工厂类(提供静态方法,根据静态方法的入参不同,创建不同的对象并返回)
5.3 使用场景
适用于产品种类少并由共同特点
5.4 优缺点
优点:对象创建与业务分离,降低系统的耦合度,使得两者修改起来都相对独立,相对容易。
缺点:工厂类的职责相对过重,增加新产品需要修改工厂类的判断逻辑(if-else增多),这一点与开闭原则是相违背
6. 创建型设计模式-工厂模式之工厂方法
6.1 工厂方法模式又叫工厂模式
6.2 对简单工厂进一步的抽象,工厂根据产品类型做进一步细分
6.3 优点:
满足开闭原则
满足最少知原则
满足接口隔离原则
满足里氏替换原则
满足依赖倒置原则
6.4 缺点:
更抽象:抽取产品、工厂的特性
可能使类爆炸,增加一个产品,需要增加2个类,一个产品类,一个生产该产品的工厂类
6.5 使用:客户端只需要知道需要的工厂,即可获得相应的产品
6.6 应用场景
- 客户只知道创建产品的工厂名(工厂类名),而不知道具体的产品名(返回的抽象产品的变量)。
- 客户不关心创建产品的细节,只关心产品的品牌/厂商