java常用七种设计模式总结
- 单例模式
- 单例模式的实现
- 第 1 种:懒汉式单例
- 第 2 种:饿汉式单例
- 工厂方法模式
- 一,简单工厂模式
- 二、工厂方法模式
- 三、抽象工厂模式
- 建造者模式
- 策略模式
- 模板方法
- 责任链模式
- 代理模式
- 适配器模式
- 观察者模式
单例模式
单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
单例模式的实现
第 1 种:懒汉式单例
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例
public class SingleLazyPattern {
private SingleLazyPattern() {
System.out.println(Thread.currentThread().getName().toString());
}
private volatile static SingleLazyPattern singleLazyPattern = null;
//适用于多线程环境,但效率不高
public synchronized static SingleLazyPattern getSingleLazyPatternInstance() {
if (singleLazyPattern == null) {
singleLazyPattern = new SingleLazyPattern();
}
return singleLazyPattern;
}
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
SingleLazyPattern.getSingleLazyPatternInstance();
}
}).start();
}
}
}
注意:如果编写的是多线程程序,则不要删除上例代码中的关键字 volatile 和 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。适用于多线程环境,但效率不高
双重检测锁模式
为了在多线程环境下,不影响程序的性能,不让线程每次调用getSingleLazyPatternInstance()方法时都加锁,而只是在实例未被创建时再加锁,在加锁处理里面还需要判断一次实例是否已存在。
//双重检测锁
public static SingleLazyPattern getSingleLazyPatternInstance() {
if (singleLazyPattern == null) {
synchronized (SingleLazyPattern.class) {
if (singleLazyPattern == null) {
singleLazyPattern = new SingleLazyPattern();
}
}
}
return singleLazyPattern;
}
这里解释一下双重检查机制的三个疑问:
-
外层判断null的作用
-
内层判断null的作用变量
-
使用volatile关键字修饰的
作用外层判断null的作用:其实就是为了减少进入同步代码块的次数,提高效率。你想一下,其实去了外层的判断其实是可以的,但是每次获取对象都需要进入同步代码块,实在是没有必要。
内层判断null的作用:防止多次创建对象。假设AB同时走到同步代码块,A先抢到锁,进入代码,创建了对象,释放锁,此时B进入代码块,如果没有判断null,那么就会直接再次创建对象,那么就不是单例的了,所以需要进行判断null,防止重复创建单例对象。
volatile关键字的作用:防止重排序。因为创建对象的过程不是原子,大概会分为三个步骤
- 第一步:分配内存空间给Singleton这个对象
- 第二步:初始化对象
- 第三步:将INSTANCE变量指向Singleton这个对象内存地址
假设没有使用volatile关键字发生了重排序,第二步和第三步执行过程被调换了,也就是先将INSTANCE变量指向Singleton这个对象内存地址,再初始化对象。这样在发生并发的情况下,另一个线程经过第一个if非空判断时,发现已经为不为空,就直接返回了这个对象,但是此时这个对象还未初始化,内部的属性可能都是空值,一旦被使用的话,就很有可能出现空指针这些问题。
通过反射来破坏掉单例模式
public static void main(String[] args) throws Exception {
SingleLazyPattern singleLazyPattern = SingleLazyPattern.getSingleLazyPatternInstance();
Constructor<SingleLazyPattern> declaredConstructor1 = SingleLazyPattern.class.getDeclaredConstructor(null);
//在方法名中加Declared的是返回所有的构造方法,不加Declared的只返回public访问权限的构造器
//破坏私有化的构造器
declaredConstructor1.setAccessible(true);
SingleLazyPattern singleLazyPattern1 = declaredConstructor1.newInstance();
System.out.println(singleLazyPattern);
System.out.println(singleLazyPattern1);
}
结果还是可以别破坏掉
通过加上标识位
private SingleLazyPattern() {
if (!isCreate) {
isCreate = true;
}else{
throw new RuntimeException("反射获取失败");
}
System.out.println(Thread.currentThread().getName().toString());
}
public static void main(String[] args) throws Exception {
//通过反射创建对象
Constructor<SingleLazyPattern> declaredConstructor = SingleLazyPattern.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
SingleLazyPattern singleLazyPattern = declaredConstructor.newInstance();
System.out.println(singleLazyPattern);
//通过反射破坏掉表示位创建对象
Field isCreate = SingleLazyPattern.class.getDeclaredField("isCreate");
isCreate.setAccessible(true);
isCreate.set(singleLazyPattern,false);
SingleLazyPattern singleLazyPattern1 = declaredConstructor.newInstance();
System.out.println(singleLazyPattern1);
}
结果还是能够被破坏掉
枚举来检测反射是否能破坏单例
public enum Instance {
INSTANCE;
public static Instance getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception{
Instance instance1 = Instance.getInstance();
Constructor<Instance> declaredConstructor = Instance.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Instance instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
通过枚举的方式就不会被破解掉
第 2 种:饿汉式单例
public class SingleStarvaePattern {
private SingleStarvaePattern(){
}
private static SingleStarvaePattern singleStarvaePattern = new SingleStarvaePattern();
public static SingleStarvaePattern getSingleStarvaePatternInstance(){
return singleStarvaePattern;
}
}
这种方法一进行类加载,这个对象就被实例化了,这种方法有个很大的缺点,那就是浪费内存空间
工厂方法模式
一,简单工厂模式
1.创建一个接口或者抽象方法
public interface Sender {
//公有方法
public void Send();
}
2.创建两个实现类
public class MailSend implements Sender{
@Override
public void Send() {
// TODO Auto-generated method stub
//实现接口方法
System.out.println("MailSender");
}
}
public class SmsSend implements Sender{
@Override
public void Send() {
// TODO Auto-generated method stub
//实现接口方法
System.out.println("SmsSend");
}
}
3.创建一个工厂类
public class SenderFactory {
public Sender produce(String Type){
if("mail".equals(Type)){
return new MailSend();
}else if("sms".equals(Type)){
return new SmsSend();
}else{
System.out.println("请输入正确的类型!");
return null;
}
}
}
public class SimpleAnimalFactory {
public Animal createAnimal(String animalType) {
if ("cat".equals(animalType)) {
Cat cat = new Cat();
//一系列复杂操作
return cat;
} else if ("dog".equals(animalType)) {
Dog dog = new Dog();
//一系列复杂操作
return dog;
} else {
throw new RuntimeException("animalType=" + animalType + "无法创建对应对象");
}
}
}
上面说的简单工厂模式看起来没啥问题,但是还是违反了七大设计原则的OCP原则,也就是开闭原则。所谓的开闭原则就是对修改关闭,对扩展开放。
什么叫对修改关闭?就是尽可能不修改的意思。就拿上面的例子来说,如果现在新增了一种动物兔子,那么createAnimal方法就得修改,增加一种类型的判断,那么就此时就出现了修改代码的行为,也就违反了对修改关闭的原则。
二、工厂方法模式
/**
* 工厂接口
*/
public interface AnimalFactory {
Animal createAnimal();
}
/**
* 小猫实现
*/
public class CatFactory implements AnimalFactory {
@Override
public Animal createAnimal() {
Cat cat = new Cat();
//一系列复杂操作
return cat;
}
}
/**
* 小狗实现
*/
public class DogFactory implements AnimalFactory {
@Override
public Animal createAnimal() {
Dog dog = new Dog();
//一系列复杂操作
return dog;
}
}
将动物工厂抽象提取成AnimalFactory接口,每个动物都实现这个接口,调用者需要创建动物时,就可以通过各自的工厂来实现。
AnimalFactory animalFactory = new CatFactory();
Animal cat = animalFactory.createAnimal();
新增一个动物兔子,那么只需要实现AnimalFactory接口就行,对于原来的猫和狗的实现,其实代码是不需要修改的,遵守了对修改关闭的原则,同时由于是对扩展开放,实现接口就是扩展的意思,那么也就符合扩展开放的原则。
三、抽象工厂模式
工厂方法模式其实是创建一个产品的工厂,比如上面的例子中,AnimalFactory其实只创建动物这一个产品。而抽象工厂模式特点就是创建一系列产品,比如说,不同的动物吃的东西是不一样的,那么就可以加入食物这个产品,通过抽象工厂模式来实现。
public interface AnimalFactory {
Animal createAnimal();
Food createFood();
}
接口ITvInterface
public interface ITvInterface {
void start();
void shutdown();
}
子类
public class ATV implements ITvInterface{
@Override
public void start() {
System.out.println("ATV start");
}
@Override
public void shutdown() {
System.out.println("ATV shutdown");
}
}
接口IComputerInterface
public interface IComputerInterface {
void start();
void shutdown();
}
子类
public class AComputer implements IComputerInterface{
@Override
public void start() {
System.out.println("Acomputer start");
}
@Override
public void shutdown() {
System.out.println("Acomputer shutdown");
}
}
接口IProductFactory
public interface IProductFactory {
IComputerInterface iComputerInterface();
ITvInterface iTvInterface();
}
子类AProduct
public class AProduct implements IProductFactory{
@Override
public IComputerInterface iComputerInterface() {
return new AComputer();
}
@Override
public ITvInterface iTvInterface() {
return new ATV();
}
}
建造者模式
将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。
class Computer {
private final String cpu;//必须
private final String ram;//必须
private final String computerName;//
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.computerName = builder.computerName;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", computerName='" + computerName + '\'' +
'}';
}
public static class Builder {
private String cpu;//必须
private String ram;//必须
private String computerName;//可选
public Builder(String computerName) {
this.computerName = computerName;
}
public Builder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder setRam(String ram) {
this.ram = ram;
return this;
}
public Computer build() {
return new Computer(this);
}
}
public static void main(String[] args) {
Computer computer = new Computer.Builder("mycomputer")
.setCpu("inter")
.setRam("512G")
.build();
System.out.println(computer.toString());
}
}
策略模式
假设现在有一个需求,需要将消息推送到不同的平台。最简单的做法其实就是使用if else来做判断就行了。
public void notifyMessage(User user, String content, int notifyType) {
if (notifyType == 0) {
//调用短信通知的api发送短信
} else if (notifyType == 1) {
//调用app通知的api发送消息
}
}
虽然能实现功能,但是如果想要新增一个邮件通知,那么就得修改notifyMessage方法,违反了开闭原则,这个时候就要考虑使用策略模式来优化。
首先设计一个策略接口:
public interface MessageNotifier {
/**
* 是否支持改类型的通知的方式
*
* @param notifyType 0:短信 1:app
* @return
*/
boolean support(int notifyType);
/**
* 通知
*
* @param user
* @param content
*/
void notify(User user, String content);
}
短信接口实现
public class SMSMessageNotifier implements MessageNotifier {
@Override
public boolean support(int notifyType) {
return notifyType == 0;
}
@Override
public void notify(User user, String content) {
//调用短信通知的api发送短信
}
}
app接口实现
public class AppMessageNotifier implements MessageNotifier {
@Override
public boolean support(int notifyType) {
return notifyType == 1;
}
@Override
public void notify(User user, String content) {
//调用通知app通知的api
}
}
遍历消息通知的类型,一旦support返回true就发送对应的通知即可,如果要有其他的通知方式,只需要实现MessageNotifier接口即可。
private List<MessageNotifier> messageNotifiers;
public void notifyMessage(User user, String content, int notifyType) {
for (MessageNotifier messageNotifier : messageNotifiers) {
if (messageNotifier.support(notifyType)) {
messageNotifier.notify(user, content);
}
}
}
模板方法
模板方法模式是指,在父类中定义一个操作中的框架,而操作步骤的具体实现交由子类做。其核心思想就是,对于功能实现的顺序步骤是一定的,但是具体每一步如何实现交由子类决定。
可以把做饭翻译成如下模板代码,让子类实现其功能,就可以按照一定的顺序步骤执行。
public abstract class PatternMethod {
//做饭
public void run() {
//洗菜
wash();
//切菜
cut();
//做菜
play();
//吃饭
eat();
}
protected abstract void wash();
protected abstract void cut();
protected abstract void play();
protected abstract void eat();
public static void main(String[] args) {
PatternMethod patternMethod = new I();
patternMethod.run();
}
}
class I extends PatternMethod{
@Override
protected void wash() {
System.out.println("I wash");
}
@Override
protected void cut() {
System.out.println("I cut");
}
@Override
protected void play() {
System.out.println("I play");
}
@Override
protected void eat() {
System.out.println("I eat");
}
}
class He extends PatternMethod{
@Override
protected void wash() {
System.out.println("He wash");
}
@Override
protected void cut() {
System.out.println("He cut");
}
@Override
protected void play() {
System.out.println("He play");
}
@Override
protected void eat() {
System.out.println("He eat");
}
}
责任链模式
责任链模式:Chain of Responsibility Patten 。就是将链中的每一个结点看做是一个对象,每个结点处理请求均不同,且内部自动维护一个下一个结点对象。当请求从链条的首端出发时,会沿着链的路径依次传递给每一个结点的对象,直到有对象处理这个请求为止。
案例:
1.定义一个请求的实体类
public class ApproverRequest {
private int type = 0; //请求类型
private float price = 0.0f; //请求金额
private int id = 0;
//构造器
public ApproverRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}
public int getType() { return type; }
public float getPrice() { return price; }
public int getId() { return id; }
}
2.定义一个抽象类,抽象类中要有下一个调用者的记录,抽象类中有个抽象方法用于处理逻辑,所有链条中的子类都需要继承这个抽象类
public abstract class Approver {
//下一个调用者
Approver next ;
//需要传入的名字
String name ;
public Approver(String name){
this.name = name;
}
//设置下一个调用者
public void setNext(Approver next) {
this.next = next;
}
public abstract void processApprover(ApproverRequest approveRequest);
}
3.教学主任处理的逻辑,如果金额在5000到10000之间会处理,否则交给下一个调用者处理
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {
super(name);
}
@Override
public void processApprover(ApproverRequest approveRequest) {
if (approveRequest.getPrice() > 5000 && approveRequest.getPrice() <= 10000) {
System.out.println(" 请求编号 id= " + approveRequest.getId() + " 被 " + this.name + " 处理");
} else {
System.out.println(this.name + "处理不了");
this.next.processApprover(approveRequest);
}
}
}
4.测试
public static void main(String[] args) {
//创建一个请求
ApproverRequest approveRequest = new ApproverRequest(1, 31000, 1);
//创建相关的审批人
TeacherApprover teacherApprover = new TeacherApprover("张老师");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");
//需要将各个审批级别的下一个设置好
teacherApprover.setNext(collegeApprover);
collegeApprover.setNext(schoolMasterApprover);
//单向责任链这里可以不加
teacherApprover.processApprover(approveRequest);
}
代理模式
代理模式也是开源项目中很常见的使用的一种设计模式,这种模式可以在不改变原有代码的情况下增加功能。可以扩展业务的功能,专注主要业务即可,其他的交给代理来做,比如日志功能。
案例:
1.被代理的对象
public interface IShoppingService {
public void shopping(String shoppingName);
}
class ShoppingService implements IShoppingService {
@Override
public void shopping(String shoppingName) {
//业务代码
System.out.println("买" + shoppingName);
}
}
2.实现代理
public class JDKProxy {
/**
* 维护一个目标对象
*/
private Object target;
public JDKProxy(Object target) {
this.target = target;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理实现额外的功能
System.out.println("打印日志开始");
//调用业务代码
Object invoke = method.invoke(target, args);
//代理实现的额外的功能
System.out.println("打印日志结束");
return invoke;
}
});
}
}
3.测试
public static void main(String[] args) {
IShoppingService proxyInstance = (IShoppingService) new JDKProxy(new ShoppingService()).getProxyInstance();
proxyInstance.shopping("车");
/** 结果
* 打印日志开始
* 买车
* 打印日志结束
*/
}
适配器模式
适配器模式使得原本由于接口不兼容而不能一起工作的哪些类可以一起工作,将一个类的接口转换成客户希望的另一个接口。
/**
* 适配器模式:想要给USB接口的手机充电,但是只有typeC的充电器,那么可以使用typeC转usb的转接口就可以给usb的手机充电
*/
//适配器typec
public class AdaptorTypec {
public void chargeTypeC() {
System.out.println("开启充电了");
}
}
//没有usb的充电线,那么就将typec的转换成usb的进行充电
interface USB {
void charge();
}
class AdaptorUsb implements USB {
AdaptorTypec adaptorTypec = new AdaptorTypec();
@Override
public void charge() {
adaptorTypec.chargeTypeC();
}
//适配器(转接头)就可以实现TypeC给MicroUSB接口充电。
public static void main(String[] args) {
AdaptorUsb microUsb = new AdaptorUsb();
microUsb.charge();
}
}
适配器的模式的应用
Mybatis提供了一个Log的适配器,但是Mybatis框架在设计的时候,无法知道项目中具体使用的是什么日志框架,所以只能适配各种日志框架,项目中使用什么框架,Mybatis就使用什么框架
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w7Ely5P8-1675418381615)(en-resource://database/1112:1)]
观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。又被称作发布-订阅(Publish/Subscribe)模式,发布者负责发布消息,订阅负责消费消息。
案例:微信公众号的关注订阅号接收消息推送与我们的观察者模式相似,公众号作为发布者负责发布内容,关注者作为订阅者负责消费内容。
/**
* @Description 主题 公众号
* @Version 1.0.0
* @Date 2022/10/13 22:21
* @Author NiKaBoy
* @Email 123456789@qq.com
*/
public interface Subject {
/**
* @Description: 添加观察者(订阅者)到队列的方法
* @Data:[observer]
* @return: void
* @Author: NiKaBoy
* @Email: 123456789@qq.com
* @Date: 22-10-286 22:24:02
*/
void addToQueue(Observer observer);
/**
* @Description: 从队列移除
* @Data:[observer]
*/
void removeBeQueue(Observer observer);
/**
* @Description: 通知 观察者 发布消息
* @Data:[]
* @return: void
* @Author: NiKaBoy
* @Email: 123456789@qq.com
* @Date: 22-10-286 22:26:58
*/
void notifyObserver();
}