文章目录
- 设计原则
- 常用设计模式
- 单例模式
- 1. 饿汉式
- 2. 懒汉式
- 3. 双重检测
- 工厂方法模式(简单工厂、工厂方法、抽象工厂)
- 简单工厂
- 静态工厂
- 工厂方法模式
- 抽象工厂模式
- 策略模式
- 责任链模式
设计原则
标记 | 设计模式原则名称 | 简单定义 |
---|---|---|
OCP | 开闭原则 | 对扩展开放,对修改关闭 |
SRP | 单一职责原则 | 一个类只负责一个功能领域中的相应职责 |
LSP | 里氏代换原则 | 所有引用基类的地方必须能透明地使用其子类的对象 |
DIP | 依赖倒转原则 | 依赖于抽象,不能依赖于具体实现 |
ISP | 接口隔离原则 | 类之间的依赖关系应该建立在最小的接口上 |
CARP | 合成/聚合复用原则 | 尽量使用合成/聚合,而不是通过继承达到复用的目的 |
LOD | 迪米特法则 | 一个软件实体应当尽可能少的与其他实体发生相互作用 |
其中,单一职责原则、开闭原则、迪米特法则、里氏代换原则和接口隔离原则就是我们平常熟知的SOLID。
常用设计模式
单例模式
保证一个类只能有一个实例,并提供一个全局访问点。
单例模式的实现需要三个必要的条件:
- 单例类的构造函数必须是私有的,这样才能将类的创建权控制在类的内部,从而使得类的外部不能创建类的实例。
- 单例类通过一个私有的静态变量来存储其唯一实例。
- 单例类通过提供一个公开的静态方法,使得外部使用者可以访问类的唯一实例。
另外,实现单例类时,还需要考虑三个问题:
- 创建单例对象时,是否线程安全。
- 单例对象的创建,是否延时加载。
- 获取单例对象时,是否需要加锁(锁会导致低性能)。
1. 饿汉式
饿汉式的单例实现比较简单,其在类加载的时候,静态实例instance
就已创建并初始化好了。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton () {}
public static Singleton getInstance() {
return instance;
}
}
- 优点:
- 单例对象的创建是线程安全的;
- 获取单例对象时不需要加锁。
- 缺点:单例对象的创建,不是延时加载。
2. 懒汉式
与饿汉式对应的是懒汉式,懒汉式为了支持延时加载,将对象的创建延迟到了获取对象的时候,但为了线程安全,不得不为获取对象的操作加锁,这就导致了低性能。
public class Singleton {
private static final Singleton instance;
private Singleton () {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 优点:
- 对象的创建是线程安全的。
- 支持延时加载。
- 缺点:获取对象的操作被加上了锁,影响了并发度。
- 如果单例对象需要频繁使用,那这个缺点就是无法接受的。
- 如果单例对象不需要频繁使用,那这个缺点也无伤大雅。
3. 双重检测
饿汉式和懒汉式的单例都有缺点,双重检测的实现方式解决了这两者的缺点。
双重检测将懒汉式中的 synchronized
方法改成了 synchronized
代码块。
public class Singleton {
private valatile static Singleton instance;
private Singleton () {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) { // 注意这里是类级别的锁
if (instance == null) { // 这里的检测避免多线程并发时多次创建对象
instance = new Singleton();
}
}
}
return instance;
}
}
双重检测单例优点:
- 对象的创建是线程安全的。
- 支持延时加载。
- 获取对象时不需要加锁。
使用场景:
单例模式可以用来管理一些共享资源,比如数据库连接池,线程池;解决资源冲突问题,比如日志打印。节省内存空间,比如配置信息类。
工厂方法模式(简单工厂、工厂方法、抽象工厂)
在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。
开闭原则:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
简单工厂
简单工厂不是一种设计模式,反而比较像是一种编程习惯。
注意
1.类图中的符号
+:表示public
-:表示private
#:表示protected
2.泛化关系(继承)用带空心三角箭头的实线来表示
3.依赖关系使用带箭头的虚线来表示
工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和
商品对象的耦合。后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。
- 优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。 - 缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
静态工厂
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静
态工厂模式
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffe;
}
}
工厂方法模式
针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。
工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
-
优点:
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则; -
缺点:
每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
抽象工厂模式
工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。
这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
抽象工厂模式的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
- 优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。 - 缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
策略模式
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要角色如下:
- 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
案例:一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)
推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
应用场景:
下图是gitee的登录的入口,其中有多种方式可以进行登录
- 用户名密码登录
- 短信验证码登录
- 微信登录
- QQ登录
像这样的需求,在日常开发中非常常见,场景有很多,以下的情景都可以使
用工厂模式+策略模式解决比如:
- 订单的支付策略
支付宝支付
微信支付
银行卡支付
现金支付 - 解析不同类型excel
xls格式
xlsx格式 - 打折促销
满300元9折
满500元8折
满1000元7折 - 物流运费阶梯计算
5kg以下
5kg-10kg
10kg-20kg
20kg以上
一句话总结:只要代码中有冗长的 if-else 或 switch 分支判断都可以采用策略模式优化
责任链模式
为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
比较常见的springmvc中的拦截器,web开发中的filter过滤器
职责链模式主要包含以下角色:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
案例:处理订单请求
- 优点
- 降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度。 - 增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则。 - 增强了给对象指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。 - 责任链简化了对象之间的连接
一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。 - 责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
- 缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
使用场景: