简介
设计模式是众多开发人员对于一类或多类问题的思考和总结,针对一系列问题所形成的解决方案,运用好这些设计模式可以使得我们的代码提高代码的可重用性、可维护性、可拓展性和可读性(对于设计模式有一定理解的开发者来说)。
设计模式是由4位作者,合称为GOF所提出,主要基于以下面向对象的设计原则:
1)对接口编程而不是对实现编程;
2)优先使用对象组合而不是继承;
出自该书:Design Patterns - Elements of Reusable Object-Oriented Software.
三大分类:
1)创建型模式:单例模式、工厂模式、抽象工厂模式、原型模式;
2)结构型模式:常用模式:适配器模式、桥接模式、装饰器模式、代理模式、组合模式、外观模式、享元模式;
3)行为型模式:责任链模式、迭代器模式、观察者模式、状态模式、策略模式、 模板模式、备忘录模式、命令模式、访问者模式、中介者模式、解释器模式;
创建型模式
提供了⼀种在创建对象的同时隐藏创建逻辑的⽅式,使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
常用模式:工厂模式、抽象工厂模式、单例模式、建造者模式;
不常用模式:原型模式
单例模式(Singleton)
确保一个类只有一个实例,并提供一个全局访问点。
懒汉式(懒加载,延迟创建对象)
/**
* 该方法是线程不安全的,但高效
*/
public class SingletonLazy01 {
// 声明私有静态变量以存储类的实例
private static SingletonLazy01 instance;
// 构造函数是私有的,以防止来自类外部的实例化
private SingletonLazy01() {
}
// 获取类实例的公共方法
public static SingletonLazy01 getInstance() {
// 如果实例为空,则创建一个新实例
// 在高并发(纳秒微妙级别)场景下,线程A和B同时进来,都通过了if语句,都创建了实例,这将导致多个实例
if (instance == null) {
instance = new SingletonLazy01();
}
return instance;
}
/**
* 单例对象方法
*/
public void print() {
System.out.println("SingletonLazy01");
}
}
/**
* 线程安全但并不高效
*/
public class SingletonLazy02 {
// 声明私有静态变量以存储类的实例
private static SingletonLazy02 instance;
// 构造函数是私有的,以防止来自类外部的实例化
private SingletonLazy02() {
}
// 这个方法通过加锁保证了线程安全,但是性能开销较大,因为在多线程环境下,每次获取实例都要进行同步等待。
public synchronized static SingletonLazy02 getInstance() {
// 如果实例为空,则创建一个新实例
if (instance == null) {
instance = new SingletonLazy02();
}
return instance;
}
}
/**
* 线程安全,双重检查锁定
*/
public class SingletonLazy03 {
// 声明私有静态变量以存储类的实例
private static SingletonLazy03 instance;
// 构造函数是私有的,以防止来自类外部的实例化
private SingletonLazy03() {
}
// 缩小颗粒度,提高效率
// DCL 双重检查锁定,在多线程情况下保持高性能和线程安全
public static SingletonLazy03 getInstance() {
// 如果实例为空,则创建一个新实例
if (instance == null) {
synchronized (SingletonLazy03.class) {
// 再次检查,以防止多个线程同时通过第一个检查
// 如果实例仍然为空,则创建一个新实例
if (instance == null) {
instance = new SingletonLazy03();
}
}
}
return instance;
}
}
/**
* 线程安全。解决指令重排问题
*/
public class SingletonLazy04 {
// 声明私有静态变量以存储类的实例
private volatile static SingletonLazy04 instance;
// 构造函数是私有的,以防止来自类外部的实例化
private SingletonLazy04() {
}
// 深究到内存模型,仍存在问题
// instance = new SingletonLazy04();并不是原子性操作
// 创建对象时,涉及三个步骤:1.分配内存空间 2.初始化对象 3.将instance指向刚分配的内存空间
// 假如线程先按1-》3-》2的顺序执行,
// 此时instance不为null,但对象可能未完成初始化,
// 假如线程二按1-》2-》3的顺序执行,
// 此时instance为null,但对象已初始化完成,
// 这就是线程不安全的体现
// 以上就成为指令重排问题
// 解决方案:使用volatile关键字修饰instance变量,禁止指令重排
public static SingletonLazy04 getInstance() {
// 如果实例为空,则创建一个新实例
if (instance == null) {
synchronized (SingletonLazy04.class) {
// 再次检查,以防止多个线程同时通过第一个检查
// 如果实例仍然为空,则创建一个新实例
if (instance == null) {
instance = new SingletonLazy04();
}
}
}
return instance;
}
}
饿汉式(与懒汉相反,提前创建对象)
/**
* 饿汉式
* 在类加载时就将实例化,不管你用不用
*/
public class SingletonHungry
{
private static final SingletonHungry INSTANCE = new SingletonHungry();
private SingletonHungry()
{
}
public static SingletonHungry getInstance()
{
return INSTANCE;
}
}
实现步骤
使用场景
工厂模式(Factory Method)
定义一个用于创建对象的接口,由子类决定创建哪个类的对象。例如⾥⾯有统⼀下单和⽀付接⼝,具体的⽀付实现可以微信、⽀付宝、银⾏卡等。
简单⼯⼚模式(扩展性差)
通过传⼊相关的类型来返回相应的类,这种⽅式⽐较单 ⼀,可扩展性相对较差;
//优点:将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
//缺点:
//1)⼯⼚类的职责相对过重,增加新的产品需要修改⼯⼚类的判断逻辑,这⼀点与开闭原则是相违背;
//2)即开闭原则(Open Close Principle)对扩展开放,对修改关闭,程序需要进⾏拓展的时候,不能去修改原有的代码,实现⼀个热插拔的效果;
//3)将会增加系统中类的个数,在⼀定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护,创建简单对象就不⽤模式;
public class SimplePayFactory {
public static Pay createPay(String payType) {
if (payType.equalsIgnoreCase("ALI_PAY")) {
return new AliPay();
} else if (payType.equalsIgnoreCase("WECHAT_PAY")) {
return new WechatPay();
} else {
// 默认支付宝支付
return null;
}
}
}
public interface Pay {
// 统一下单
void unifiedPay();
}
public class AliPay implements Pay{
@Override
public void unifiedPay() {
System.out.println("支付宝支付");
}
}
public class WechatPay implements Pay{
@Override
public void unifiedPay() {
System.out.println("微信支付");
}
}
public class Main {
public static void main(String[] args) {
Pay wechatPay = SimplePayFactory.createPay("WECHAT_PAY");
wechatPay.unifiedPay();
}
}
⼯⼚模式(扩展性强)
通过实现类实现相应的⽅法来决定相应的返回结果,这种⽅式的可扩展性⽐较强;
//优点:符合开闭原则,增加⼀个产品类,只需要实现其他具体的产品类和具体的⼯⼚类;
// 符合单⼀职责原则,每个⼯⼚只负责⽣产对应的产品
// 使⽤者只需要知道产品的抽象类,⽆须关⼼其他实现类,满⾜迪⽶特法则、依赖倒置原则和⾥⽒替换原则 迪⽶特法则:
// 最少知道原则,实体应当尽量少地与 其他实体之间发⽣相互作⽤
// 依赖倒置原则:针对接⼝编程,依赖于抽象⽽不依赖于具体
// ⾥⽒替换原则:俗称LSP, 任何基类可以出现的地⽅,⼦类⼀定可以出现, 对实现抽象化的具体步骤的规范
//缺点:增加⼀个产品,需要实现对应的具体⼯⼚类和具体产品类;
// 每个产品需要有对应的具体⼯⼚和具体产品类
public interface PayFactory {
Pay getPay();
}
public class AliPayFactory implements PayFactory {
@Override
public Pay getPay() {
return new AliPay();
}
}
public class WechatPayFactory implements PayFactory {
@Override
public Pay getPay() {
return new WechatPay();
}
}
public interface Pay {
// 统一下单
void unifiedPay();
}
public class AliPay implements Pay{
@Override
public void unifiedPay() {
System.out.println("支付宝支付");
}
}
public class WechatPay implements Pay {
@Override
public void unifiedPay() {
System.out.println("微信支付");
}
}
public class Main {
public static void main(String[] args) {
AliPayFactory aliPayFactory = new AliPayFactory();
Pay pay = aliPayFactory.getPay();
pay.unifiedPay();
}
}
抽象⼯⼚模式
基于上述两种模式的拓展,且⽀持细化产品
/**
* 下单
* 优点:
* 1.当⼀个产品族中的多个对象被设计成⼀起⼯作时,它能保证使⽤⽅始终只使⽤同⼀个产品族中的对象
* 2.产品等级结构扩展容易,如果需要增加多⼀个产品等级,只需要增加新的⼯⼚类和产品类即可, ⽐如增加银⾏⽀付、退款
* 缺点:
* 1.产品族扩展困难,要增加⼀个系列的某⼀产品,既要在抽象的⼯⼚和抽象产品⾥修改代码,不是很符合开闭原则
* 2.增加了系统的抽象性和理解难度
*
*/
package priv.muller.factory;
import priv.muller.factory.ali.AliOrderFactory;
import priv.muller.factory.wechat.WechatOrderFactory;
public class FactoryProducer {
// 返回一个工厂
public static IOrderFactory getFactory(String choice){
if(choice.equalsIgnoreCase("WECHAT")){
return new WechatOrderFactory();
} else if(choice.equalsIgnoreCase("ALI")){
return new AliOrderFactory();
}
return null;
}
}
package priv.muller.factory;
/**
* 下单
*/
public interface IOrderFactory {
IPayFactory createPayFactory();
IRefoundFactory createRefoundFactory();
}
package priv.muller.factory;
/**
* 支付
*/
public interface IPayFactory {
// 统一下单
void unifiedPay();
}
package priv.muller.factory;
/**
* 退款接口
*/
public interface IRefoundFactory {
void refund();
}
package priv.muller.factory.ali;
import priv.muller.factory.IOrderFactory;
import priv.muller.factory.IPayFactory;
import priv.muller.factory.IRefoundFactory;
public class AliOrderFactory implements IOrderFactory {
@Override
public IPayFactory createPayFactory() {
return new AliPay();
}
@Override
public IRefoundFactory createRefoundFactory() {
return new AliRefund();
}
}
package priv.muller.factory.ali;
import priv.muller.factory.IPayFactory;
public class AliPay implements IPayFactory {
@Override
public void unifiedPay() {
System.out.println("支付宝支付");
}
}
package priv.muller.factory.ali;
import priv.muller.factory.IRefoundFactory;
public class AliRefund implements IRefoundFactory {
@Override
public void refund() {
}
// 实现支付宝的退款逻辑
}
package priv.muller.factory.wechat;
import priv.muller.factory.IOrderFactory;
import priv.muller.factory.IPayFactory;
import priv.muller.factory.IRefoundFactory;
public class WechatOrderFactory implements IOrderFactory {
@Override
public IPayFactory createPayFactory() {
return new WechatPay();
}
@Override
public IRefoundFactory createRefoundFactory() {
return new WechatRefund();
}
}
package priv.muller.factory.wechat;
import priv.muller.factory.IPayFactory;
public class WechatPay implements IPayFactory {
@Override
public void unifiedPay() {
System.out.println("微信支付");
}
}
package priv.muller.factory.wechat;
import priv.muller.factory.IRefoundFactory;
public class WechatRefund implements IRefoundFactory {
@Override
public void refund() {
// 实现微信退款逻辑
System.out.println("微信退款");
}
}
工厂模式和抽象工厂模式的区别
1)工厂方法模式通常只针对一个抽象产品类进行创建,而抽象工厂模式则需要针对多种抽象产品进行创建;
2)使用工厂方法模式时,往往仅需选用所需的工厂方法即可,而使用抽象工厂模式时,则需要创建必须的所有抽象产品对象;
3)工厂方法模式(Factory Method)通过让子类实现工厂接口,来决定具体应该创建哪一个产品类的实例对象。它允许我们在不改变现有代码基础上添加新的产品类型,并且可以将具体产品的实现与调用方分离开来;
4)抽象工厂模式(Abstract Factory)与工厂方法模式类似,也是用于创建一系列相关的对象。不同之处在于,抽象工厂是针对多个产品族而言的,即每个工厂可以创建多种不同类型的产品。这样的话,抽象工厂为创建一组相关或独立的对象提供了一种方式。
原型模式(Prototype)
通过复制现有的对象来创建新的对象,避免了直接创建对象的开销
浅拷贝(通过实现 Cloneable)
public class Person implements Cloneable {
private String name;
private int age;
public Person() {
System.out.println("调用了无参构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) throws CloneNotSupportedException {
// 创建一个具体的原型对象
Person prototype = new Person();
prototype.setAge(25);
prototype.setName("原型对象");
// 克隆一个具体的原型对象
Person clone = prototype.clone();
clone.setName("克隆的原型对象");
// 比较原对象和克隆对象是否相等
System.out.println("原对象:" + prototype);
System.out.println("新对象:" + clone);
}
深拷贝(通过实现 Serializable 读取⼆进制流)
//优点:
// 当创建新的对象实例较为复杂时,使⽤原型模式可以简化对象的创建过程,可以提⾼新实例的创建效率可辅助实现撤销操作
// 使⽤深克隆的⽅式保存对象的状态,使⽤原型模式将对象复制⼀份并将其状态保存起来,以便在需要的时候使⽤恢复到历史状态
//缺点:
// 需要为每⼀个类配备⼀个克隆⽅法,对已有的类进⾏改造时,需要修改源代码,违背了“开闭原则”
// 在实现深克隆时需要编写较为复杂的代码,且当对象之间存在多重的嵌套引⽤时,需要对每⼀层对象对应的类都必须⽀持深克隆
public class Person implements Cloneable, Serializable {
private String name;
private int age;
private List<String> hobbies = new ArrayList<>();
public Person() {
System.out.println("调用了无参构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", hobbies=" + hobbies.toString() +
'}';
}
public Person deepClone() {
try {
//输入序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//输入反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Person person = (Person) ois.readObject();
return person;
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
public class DeepCloneClient {
public static void main(String[] args) {
// 创建一个具体的原型对象
Person prototype = new Person();
prototype.setAge(25);
prototype.setName("原型对象");
prototype.getHobbies().add("篮球");
// 克隆一个具体的原型对象
Person clone = prototype.deepClone();
clone.setName("克隆的原型对象");
clone.getHobbies().add("吉他");
// 比较原对象和克隆对象是否相等
System.out.println("原对象:" + prototype);
System.out.println("新对象:" + clone);
}
}
建造者模式(Builder)
将一个复杂对象的构建过程分离出来,使同样的构建过程可以创建不同的表示。
//优点:
// 1)客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦
// 2)每⼀个具体建造者都相对独⽴,⽽与其他的具体建造者⽆关,更加精细地控制产品的创建过程
// 3)增加新的具体建造者⽆须修改原有类库的代码,符合开闭原则
// 4)建造者模式结合链式编程来使⽤,代码上更加美观缺点
// 5)建造者模式所创建的产品⼀般具有较多的共同点,如果产品差异⼤则不建议使⽤
//缺点
// 1)建造者模式所创建的产品⼀般具有较多的共同点,如果产品差异⼤则不建议使⽤
public class Director {
public Computer create(Builder builder) {
builder.buildCpu();
builder.buildMemory();
builder.buildPower();
builder.buildDisk();
return builder.createComputer();
}
}
public interface Builder {
void buildCpu();
void buildDisk();
void buildPower();
void buildMemory();
Computer createComputer();
}
public class HighComputerBuilder implements Builder{
private Computer computer = new Computer();
@Override
public void buildCpu() {
computer.setCpu("高配CPU");
}
@Override
public void buildDisk() {
computer.setHardDisk("高配硬盘");
}
@Override
public void buildPower() {
computer.setPower("高配电源");
}
@Override
public void buildMemory() {
computer.setMemory("高配内存");
}
@Override
public Computer createComputer() {
return computer;
}
}
public class LowComputerBuilder implements Builder{
private Computer computer = new Computer();
@Override
public void buildCpu() {
computer.setCpu("低配CPU");
}
@Override
public void buildDisk() {
computer.setHardDisk("低配硬盘");
}
@Override
public void buildPower() {
computer.setPower("低配电源");
}
@Override
public void buildMemory() {
computer.setMemory("低配内存");
}
@Override
public Computer createComputer() {
return computer;
}
}
public class Computer {
// 需要构建的属性
private String cpu;
private String memory;
private String hardDisk;
private String power;
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getHardDisk() {
return hardDisk;
}
public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}
public String getPower() {
return power;
}
public void setPower(String power) {
this.power = power;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", memory='" + memory + '\'' +
", hardDisk='" + hardDisk + '\'' +
", power='" + power + '\'' +
'}';
}
}