1. 设计模式概述
是前辈对代码开发经验的总结,是解决特定问题的一系列套路。不是语法规定,而是一套用来提高代码可复用性,可维护性,可读性,稳健性以及安全性的解决方案
创建型模式:使对象的创建与使用分离
单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式:如何将类或者对象按照某种布局组成一种更大的结构
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式:描述类或者对象之间如何相互协作完成单个对象无法完成的任务
模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
2. OOP七大原则
开闭原则:对扩展开放,对修改关闭
里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立
依赖倒置原则:要面向接口变成,不要面向实现编程
单一职责原则:控制类的粒度大小,将对象解耦,提高其内聚性
接口隔离原则:要为各个类建立它们需要的专用接口
迪米特原则:只与你的直接朋友交谈,不跟”陌生人“说话
合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
3. 单例模式
在内存中只会创建且仅创建一次对象的设计模式。
在程序中多次使用同一个对象且作用相同时,为了防止频繁创建对象使内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
1.饿汉式单例
饿汉式单例在类加载时已经创建好对象(private static final Hungry HUNGRY = new Hungry()
),程序调用时直接返回该单例对象即可
package com.itwrj.single;
// 饿汉式单例
public class Hungry {
//可能会浪费空间
private byte[] date1 = new byte[1024*1024];
//单例模式最重要的特征:构造器私有
private Hungry() {
}
//一上来就把对象加载
private static final Hungry HUNGRY = new Hungry();
//对外的方法
public static Hungry getInstance(){
return HUNGRY;
}
}
public class Hungry{
private Hungry(){}
private static final Hungry HUNGRY = new Hangry();
public static Hungry getInstance(){
return HUNGRY;
}
}
2.懒汉式单例
在真正需要使用时才去创建单例对象
创建方法:在程序使用对象前,先判断该对象是否已经实例化(判空),如果已经实例化则返回该对象,否则先执行实例化操作
public class LazyMan{
private LazyMan(){
}
private volatile static LazyMan lazyman;
//双重检测锁模式的懒汉式单例 即DCL懒汉式
public static LazyMan getInstance(){
if(lazyman==null){
synchronized(LazyMan.class){
if(lazyman==null){
lazyman = new LazyMan();
}
}
}
return lazyman;
}
}
public static LazyMan{
private LazyMan(){}
private volatile static LazyMan lazyman;
if(lazyman==null){
synchroniezd(LazyMan.class){
if(lazyman==null){
lazyman=new LazyMan();
}
}
}
return lazyman;
}
}
Q1:但是在多线程下是不安全的
比如有两个线程同时判断lazyman==null,那么他们都会去实例化一个对象,这个时候就变成了双例
Q2 :可以想到在方法上或者代码块上加锁,但是每次获取对象时都需要先获取锁,并发性能很差。
因此,可以在获取锁前判断是否已经实例化,如果没有实例化则加锁,否则直接获取对象
Q3:但是创建对象并非原子性操作,而是有3个步骤。在创建对象时可能发生指令重排,即可能以132的步骤创建对象,当A执行1、3步骤时,如果另个一个线程B判断lazyman不为空,就会返回对象,但是此时的对象是未初始化的对象,就会发生错误。
lazyman = new LazyMan();
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向内存空间
因此,可以使用volatile关键字防止指令重排
volatile关键字的作用
1.防止指令重排
2.使用volatile修饰的变量,可以保证内存的可见性,即每一时刻线程读取到该变量的 值都是内存中最新的那个值,线程每次操作该变量都需要先读取该变量
3.破坏饿汉式单例懒汉式单例
反射和序列化 会破坏单例对象,产生多个对象
4.使用枚举实现单例模式
jdk1.5之后有了枚举
public enum EnumSingle{
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
public enum EnumSingle{
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
优势:
1.代码更加简洁
2.不需要任何额外的操作去保证对象单一性和线程安全性
3.可以防止反射、序列化与反序列化抢占生成多个对象
5.总结
1.单例模式常见写法有:饿汉式和懒汉式
2.饿汉式:在类加载时已经创建好了对象,在获取对象时直接返回该对象即可,不会存在安全性和并发问题
3.懒汉式:在需要时才实例化单例对象,需要双重验证和锁,解决并发问题
4.在开发中如果对内存要求很高,使用懒汉式写法,在需要时才创建对象
5.如果对内存要求不高,使用饿汉式写法,简单不易出错且没有安全性问题
6.懒汉式单例下,为了防止创建对象发生指令重排报NPE,在变量上加volatile关键字,防止指令重排序
7.最好的方式是枚举,代码精简没有安全性问题且可以防止反射、序列化和反序列化破坏单例模式
4.工厂模式
作用:创建者和调用者分离
核心本质:
1.实例化对象不使用new,用工厂方法代替
2.将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦
一般模式
我们需要自己去new对象,然后使用
需要Car接口,Wuling实现类,Tesla实现类
//Car接口
package com.itwrj.factory.simple;
public interface Car {
void name();
}
//Wuling实现类
package com.itwrj.factory.simple;
public class Wuling implements Car{
@Override
public void name() {
System.out.println("五菱宏光");
}
}
//Tesla实现类
package com.itwrj.factory.simple;
public class Tesla implements Car{
@Override
public void name() {
System.out.println("特斯拉");
}
}
package com.itwrj.factory.simple;
public class Consumer {
public static void main(String[] args) {
//接口、实现类都需要知道 相当于自己造车出来
Car car = new Wuling();
Car car1 = new Tesla();
car.name();
car1.name();
}
}
1.简单工厂模式/静态工厂模式
用来生产同一等级结构中的任意产品(对于增加新的产品,需要球盖已有代码)
简单工厂模式下,可以自己创建一个车工厂,只需要传给他一个String类型的名字
package com.itwrj.factory.simple;
//车工厂,只需要传给他一个String类型的名字
public class CarFactory {
public static Car getCar(String car){
if (car.equals("五菱宏光")){
return new Wuling();
}else if (car.equals("特斯拉")){
return new Tesla();
}else {
return null;
}
}
}
package com.itwrj.factory.simple;
public class Consumer {
public static void main(String[] args) {
//接口、实现类都需要知道 相当于自己造车出来
//Car car = new Wuling();
//Car car1 = new Tesla();
//使用工厂创建
Car car = CarFactory.getCar("五菱宏光");
Car car1 = CarFactory.getCar("特斯拉");
car.name();
car1.name();
}
}
优点:
用专门的工厂类创建对象,不用关心对象的具体细节,提高了灵活性
缺点:
当需要添加新产品时,需要修改工厂类的代码,不满足开闭原则(对扩展开放,对修改关闭)
2.工厂方法模式
用来生产同一等级结构中的固定产品(支持增加任意产品)
//类接口
public interface Car {
void name();
}
//Tesla类
public class Tesla implements Car{
@Override
public void name() {
System.out.println("特斯拉");
}
}
//Wuling类
public class Wuling implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
//Dazhong类
public class Dazhong implements Car{
@Oberride
public void name() {
sout("大众");
}
}
//创建对象的接口
public interface CarFactory {
Car getCar();
}
//Tesla工厂类,决定实例化的对象
public class TeslaFactory implements CarFactory {
@Override
public Car getCar() {
return new Tesla();
}
}
//Wuling工厂类,决定实例化的对象
public class WulingFactory implements CarFactory{
@Override
public Car getCar() {
return new Wuling();
}
}
//Dazhong工厂类,决定实例化的对象
public class DazhongFactory implements CarFactory{
@Override
public Car getCar(){
return new Dazhong();
}
}
优点:
将工厂抽象化,定义一个创建对象的接口。每次新增产品,只需要增加该产品以及对应的实现工厂类。由具体的工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,就符合开闭原则(对扩展开放,对修改关闭),扩展时不必修改原来的代码
缺点:
新增产品需要增加对于的产品类和实现工厂类,导致类比较多,结果庞大
简单工厂VS工厂方法
结构复杂度:simple<method
代码复杂度:simple<method
编程复杂度:simple<method
管理复杂度:simple<method
根据设计原则:method 因为满足开闭原则
根据实际业务:simple 因为更简单
3.抽象工厂模式
围绕一个超级工厂创建其他工厂。该超级工程又称为其他工厂的工厂
生产工厂的工厂
定义:提供了一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类
适用场景:
1.客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
2.强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的代码复用
3.提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体的实现
产品等级:比如华为手机和小米手机
产品族:比如小米手机和小米路由器;华为手机和华为路由器
手机产品接口 路由器产品接口 小米手机类 小米路由器类 华为手机类 华为路由器类
产品工厂接口(包含手机工厂和路由器工厂)(超级工厂) 华为工厂 小米工厂 客户端
超级工厂是产品工厂(IProductFactory 接口) 用来创建小米工厂(XiaomiFactory 实现类)和华为工厂(HuaweiFactory实现类) 小米工厂需要创建小米手机(XiaomiIphone实现类)和小米路由器(XiaomiIRouter实现类) 小米手机的创建需要手机接口(IPhoneProduct 接口) 小米路由器的创建需要路由器接口(RouterProduct 接口)
优点:
1.具体的产品在应用层的代码隔离,无需关心创建细节
2.将一个系列的产品统一到一起创建
缺点:
1.规定了所有可能会被创建的产品集合,产品族中扩展新的产品困难
2.增加了系统的抽象性和理解难度
4.小结
简单工厂模式:
虽然某种程度上不符合设计原则,但实际用的多
工厂方法模式:
不修改已有类的前提下,通过增加新的工厂类实现扩展
抽象工厂模式:
不可以增加产品,可以增加产品族
应用场景:
1.JDK中Calender的getInstance方法
2.JDBC中的Connection对象的获取
3.Spring中IOC容器创建管理bean对象
4.反射中Class对象的newInstance方法
5. 建造者模式
提供一种创建对象的最佳方式。
定义:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
作用:
在用户不知道对象的建造过程和细节的情况下就可以直接创建出复杂的对象
//产品(具体的房子)要创建的产品对象
public class Product {
private String buildA;
private String buildB;
private String buildC;
private String buildD;
public String getBuildA() {
return buildA;
}
public void setBuildA(String buildA) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
}
//抽象的建造者,不负责造房子,而是定义一些方法和接口
public abstract class Builder {
abstract void buildA(); //地基
abstract void buildB(); //钢筋水泥
abstract void buildC(); //铺线
abstract void buildD(); //粉刷
abstract Product getProduct();
}
//具体的建造者 根据不同的业务逻辑,具体化对象各个部分的组建
public class Worker extends Builder {
private Product product;
public Worker() {
product = new Product();
}
@Override
void buildA() {
product.setBuildA("地基");
System.out.println("地基");
}
@Override
void buildB() {
product.setBuildB("钢筋水泥");
System.out.println("钢筋水泥");
}
@Override
void buildC() {
product.setBuildC("铺线");
System.out.println("铺线");
}
@Override
void buildD() {
product.setBuildD("粉刷");
System.out.println("粉刷");
}
@Override
Product getProduct() {
return product;
}
}
//指挥:是核心 负责指挥构建一个工程,工程如何构建由他决定
public class Director {
//指挥工人按照顺序建房子
public Product build(Builder builder){
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();
}
}
public class Test {
public static void main(String[] args) {
//指挥
Director director = new Director();
//指挥 实体的工人完成产品
Product build = director.build(new Worker());
System.out.println(build.toString());
}
}
public static void main(String[] args) {
//指挥
Director director = new Director();
//指挥 实体的工人完成产品
Product build = director.build(new Worker());
System.out.println(build.toString());
}
}