策略模式
业务场景
现在有这样的一个场景,关联系统送过来一批文件,根据不同类型采取不同解析方式,很多小伙伴肯定会写出一下代码
if(type=="A"){
// 按照A格式解析
}else if("B".equals(type)){
// 按照B格式解析
}else if("C".equals(type)){
// 按照C格式解析
}
。。。
后面还有多种格式
这个代码会出现什么问题?
如果分支变多,这里代码变得臃肿,难以维护,可读性低
如果需要接入新的解析类型,只能在原来代码上修改
专业一点就是以上代码违背面向对象编程开闭原则和单一原则
- 开闭原则:对扩展开放,对修改关闭,增加或者删除某个逻辑,需要修改原来代码
- 单一原则:规定一个类只有一个发生变化原因,修改任何类型的分支逻辑代码,都需要改动当前类代码
这时候我们的利器来了 “策略模式”
定义和理解
定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
是不是有点抽象,举个通俗易懂例子
孙权看刘备有雄起之意,杀是不能杀了,那会惹天下人唾弃,就想个招儿收拾他一下,那有什么办法呢?孙权有个妹妹——孙尚香,准备招刘备做女婿,然后孙权想办法把刘备软禁起来,孙权的想法还是很单纯的嘛,就是不让你刘备回西川,然后我东吴想干啥就干啥,夺荆州,吞西川也不是不可能的。东吴的想法是好的,无奈中间多了智谋无敌的诸葛亮,他早就预测了东吴有此招数,于是在刘备去东吴招亲之前,特授以伴郎赵云三个锦囊,说是按天机拆开解决棘手问题。
这三个妙计分别是:找乔国老帮忙(也就是走后门了),求吴国太放行(诉苦)以及孙夫人断后,对这三个妙计不熟悉的读者可以去温习一下《三国演义》,这里就不多说了。想想看,这三个计谋有什么相似之处,他们都是告诉赵云要怎么执行,也就是说这三个计谋都有一个方法是执行,具体执行什么内容,每个计谋当然不同了,分析到这里,我们是不是就有这样一个设计思路:三个妙计应该实现的是同一个接口?
策略模式针对一组算法,将每一个算法封装到共同的接口独立的类中,从而使得他们可以相互转换。
应用场景
● 多个类只有在算法或行为上稍有不同的场景。
● 算法需要自由切换的场景。
● 需要屏蔽算法规则的场景。
UML
策略模式使用
- 一个接口或者抽象类,里面包含两个方法,一个方法匹配类型,一个是可以替换的逻辑实现方法
- 不同策略差异化实现(不同实现类)
- 使用策略模式
妙计接口
public interface IStrategy {
//每个锦囊妙计都是一个可执行的算法
public void operate();
}
第一个妙计:乔国老开后门
@Component
public class BackDoor implements IStrategy {
@Override
public void operate() {
System.out.println("找乔国老帮忙,让吴国太给孙权施加压力");
}
}
第二个妙计:是找吴国太哭诉,企图给自己开绿灯
@Component
public class GivenGreenLight implements IStrategy {
@Override
public void operate() {
System.out.println("求吴国太开绿灯,放行!");
}
}
第三个妙计是在逃跑的时候,让新娘子孙夫人断后,谁来砍谁,这是非常好的主意
@Component
public class BlockEnemy implements IStrategy {
@Override
public void operate() {
System.out.println("孙夫人断后,挡住追兵");
}
}
在这个场景中,三个妙计都有了,那还缺少两个配角:第一,妙计肯定要放到一个地方吧,这么重要的东西要保管呀,也就是承装妙计的锦囊,所以俗称锦囊妙计嘛;第二,这些妙计都要有一个执行人吧,是谁?当然是赵云了,妙计是小亮给的,执行者是赵云。赵云就是一个干活的人,从锦囊中取出妙计,执行,然后获胜。过程非常清晰,我们把完整的过程设计出来
锦囊
public class Context {
//构造函数,你要使用哪个妙计
private IStrategy straegy;
public Context(IStrategy strategy){
this.straegy = strategy;
}
//使用计谋了,看我出招了
public void operate(){
this.straegy.operate();
}
}
通过构造函数把策略传递进来,然后用operate()方法来执行相关的策略方法。三个妙计有了,锦囊也有了,然后就是赵云雄赳赳地揣着三个锦囊,拉着已步入老年行列的、还想着娶纯情少女的刘老爷子去入赘了。嗨,还别说,小亮同志的三个妙计还真是不错
public class ZhaoYun {
//赵云出场了,他根据诸葛亮给他的交代,依次拆开妙计
public static void main(String[] args) {
Context context;
//刚刚到吴国的时候拆第一个
System.out.println("---刚刚到吴国的时候拆第一个---");
context = new Context(new BackDoor()); //拿到妙计
context.operate(); //拆开执行
System.out.println("\n\n\n\n\n\n\n\n");
//刘备乐不思蜀了,拆第二个了
System.out.println("---刘备乐不思蜀了,拆第二个了---");
context = new Context(new GivenGreenLight());
context.operate(); //执行了第二个锦囊
System.out.println("\n\n\n\n\n\n\n\n");
//孙权的小兵追来了,咋办?拆第三个
System.out.println("---孙权的小兵追来了,咋办?拆第三个---");
context = new Context(new BlockEnemy());
context.operate(); //孙夫人退兵
System.out.println("\n\n\n\n\n\n\n\n");
}
}
最终效果
統一规范接口
package com.geekmice.onetomany.codeoptimize;
import java.math.BigDecimal;
/**
* @description 统一规范
*/
public interface Strategy {
/**
* @param money
* @return
* @description 计算方法
*/
BigDecimal compute(BigDecimal money);
/**
* @return
* @description 返回type
*/
String getType();
}
工厂门面
package com.geekmice.onetomany.codeoptimize;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StrategyFactory {
private Map<String, Strategy> map = null;
public StrategyFactory() {
List<Strategy> strategies = new ArrayList<>();
strategies.add(new OrdinaryStrategy());
strategies.add(new SilverStrategy());
strategies.add(new GoldStrategy());
strategies.add(new PlatinumStrategy());
// list转换map,key:gettype value:对应实现类
map = strategies.stream().collect(
Collectors.toMap(Strategy::getType, strategy -> strategy)
);
}
public static class Holder {
public static StrategyFactory instance = new StrategyFactory();
}
public static StrategyFactory getInstance() {
return Holder.instance;
}
public Strategy get(String type) {
return map.get(type);
}
}
普通会员实现类
package com.geekmice.onetomany.codeoptimize;
import java.math.BigDecimal;
public class OrdinaryStrategy implements Strategy {
@Override
public BigDecimal compute(BigDecimal money) {
System.out.println("普通会员不优惠");
return money;
}
@Override
public String getType() {
return "1";
}
}
白银会员实现类
package com.geekmice.onetomany.codeoptimize;
import java.math.BigDecimal;
public class SilverStrategy implements Strategy {
@Override
public BigDecimal compute(BigDecimal money) {
System.out.println("白银会员优惠50元");
return money.subtract(new BigDecimal("50"));
}
@Override
public String getType() {
return "2";
}
}
黄金会员实现类
package com.geekmice.onetomany.codeoptimize;
import java.math.BigDecimal;
public class GoldStrategy implements Strategy {
@Override
public BigDecimal compute(BigDecimal money) {
System.out.println("黄金会员打8折");
return money.multiply(new BigDecimal("0.8"));
}
@Override
public String getType() {
return "3";
}
}
白金会员实现类
package com.geekmice.onetomany.codeoptimize;
import java.math.BigDecimal;
public class PlatinumStrategy implements Strategy {
@Override
public BigDecimal compute(BigDecimal money) {
System.out.println("白金会员优惠50元,再打7折");
return money.subtract(new BigDecimal("50")).multiply(new BigDecimal("0.7"));
}
@Override
public String getType() {
return "4";
}
}
调用入口
private static BigDecimal getResult(BigDecimal money, String type) {
if (money.compareTo(new BigDecimal("1000")) == -1) {
return money;
}
Strategy strategy = StrategyFactory.getInstance()
.get(type);
Assert.notNull(strategy, "please input right type");
return strategy.compute(money);
}
单例模式
业务场景
Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理这些Bean的生命期,决定什么时候创建出来,什么时候销毁,销毁的时候要如何处理,等等。如果采用非单例模式(Prototype类型),则Bean初始化后的管理交由J2EE容器,Spring容器不再跟踪管理Bean的生命周期。
定义和理解
如何理解
7.1 我是皇帝我独苗
自从秦始皇确立了皇帝这个位置以后,同一时期基本上就只有一个人孤零零地坐在这个位置。这种情况下臣民们也好处理,大家叩拜、谈论的时候只要提及皇帝,每个人都知道指的是谁,而不用在皇帝前面加上特定的称呼,如张皇帝、李皇帝。这一个过程反应到设计领域就是,要求一个类只能生成一个对象(皇帝),所有对象对它的依赖都是相同的,因为只有一个对象,大家对它的脾气和习性都非常了解,建立健壮稳固的关系,我们把皇帝这种特殊职业通过程序来实现。
皇帝每天要上朝接待臣子、处理政务,臣子每天要叩拜皇帝,皇帝只能有一个,也就是一个类只能产生一个对象,该怎么实现呢?对象产生是通过new关键字完成的(当然也有其他方式,比如对象复制、反射等),这个怎么控制呀,但是大家别忘记了构造函数,使用new关键字创建对象时,都会根据输入的参数调用相应的构造函数,如果我们把构造函数设置为private私有访问权限不就可以禁止外部创建对象了吗?臣子叩拜唯一皇帝的过程类图如图7-1所示。
图7-1 臣子叩拜皇帝类图
只有两个类,Emperor代表皇帝类,Minister代表臣子类,关联到皇帝类非常简单。Emperor如代码清单7-1所示。
代码清单7-1 皇帝类
public class Emperor {
private static final Emperor emperor =new Emperor(); //初始化一个皇帝
private Emperor(){
//世俗和道德约束你,目的就是不希望产生第二个皇帝
}
public static Emperor getInstance(){
return emperor;
}
//皇帝发话了
public static void say(){
System.out.println("我就是皇帝某某某....");
}
}
通过定义一个私有访问权限的构造函数,避免被其他类new出来一个对象,而Emperor自己则可以new一个对象出来,其他类对该类的访问都可以通过getInstance获得同一个对象。
皇帝有了,臣子要出场,其类如代码清单7-2所示。
代码清单7-2 臣子类
public class Minister {
public static void main(String[] args) {
for(int day=0;day<3;day++){
Emperor emperor=Emperor.getInstance();
emperor.say();
}
//三天见的皇帝都是同一个人,荣幸吧!
}
}
臣子参拜皇帝的运行结果如下所示。
我就是皇帝某某某…
我就是皇帝某某某…
我就是皇帝某某某…
臣子天天要上朝参见皇帝,今天参拜的皇帝应该和昨天、前天的一样(过渡期的不考虑,别找茬哦),大臣磕完头,抬头一看,嗨,还是昨天那个皇帝,老熟人了,容易讲话,这就是单例模式。
定义情况
单例模式(Singleton Pattern)是一个比较简单的模式,其定义如下:
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)
单例模式的通用类图如图7-2所示。
图7-2 单例模式通用类图
Singleton类称为单例类,通过使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化的(在Singleton中自己使用new Singleton())。单例模式的通用源代码如代码清单7-3所示。
代码清单7-3 单例模式通用代码
public class Singleton {
private static final Singleton singleton = new Singleton();
//限制产生多个对象
private Singleton(){
}
//通过该方法获得实例对象
public static Singleton getSingleton(){
return singleton;
}
//类中其他方法,尽量是static
public static void doSomething(){
}
}
实践应用
7.3.3 单例模式的使用场景
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体的场景如下:
● 要求生成唯一序列号的环境;
● 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
● 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
● 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
UML
如何使用
饿汉式
public class Singleton {
// 创建一个实例对象
private static Singleton instance = new Singleton();
/**
* 私有构造方法,防止被实例化
*/
private Singleton(){}
/**
* 静态get方法
*/
public static Singleton getInstance(){
return instance;
}
}
之所以叫饿汉式大家可以理解为他饿,他想提前把对象new出来,这样别人哪怕是第一次获取这个类对象的时候直接就存在这个类了,省去了创建类这一步的开销。
等我介绍完懒汉之后,对比一下大家就知道两者的区别,以及各自适用在什么场景了。
懒汉式
线程不安全的模式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式大家可以理解为他懒,别人第一次调用的时候他发现自己的实例是空的,然后去初始化了,再赋值,后面的调用就和饿汉没区别了。
懒汉和饿汉的对比:大家可以发现两者的区别基本上就是第一次创作时候的开销问题,以及线程安全问题(线程不安全模式的懒汉)。
那有了这个对比,那他们的场景好理解了,在很多电商场景,如果这个数据是经常访问的热点数据,那我就可以在系统启动的时候使用饿汉模式提前加载(类似缓存的预热)这样哪怕是第一个用户调用都不会存在创建开销,而且调用频繁也不存在内存浪费了。
而懒汉式呢我们可以用在不怎么热的地方,比如那个数据你不确定很长一段时间是不是有人会调用,那就用懒汉,如果你使用了饿汉,但是过了几个月还没人调用,提前加载的类在内存中是有资源浪费的。
线程安全问题
上面的懒汉我是故意没加锁的,大家肯定都知道懒汉的线程安全问题的吧?
???忘了?那好吧,暖男带你回忆一波吧。
在运行过程中可能存在这么一种情况:多个线程去调用getInstance方法来获取Singleton的实例,那么就有可能发生这样一种情况,当第一个线程在执行if(instance==null)时,此时instance是为null的进入语句。
在还没有执行instance=new Singleton()时(此时instance是为null的)第二个线程也进入了if(instance==null)这个语句,因为之前进入这个语句的线程中还没有执行instance=new Singleton(),所以它会执行instance = new Singleton()来实例化Singleton对象,因为第二个线程也进入了if语句所以它会实例化Singleton对象。
这样就导致了实例化了两个Singleton对象,那怎么解决?
简单粗暴,加锁就好了,这是加锁之后的代码。
public class Singleton {
private static Singleton instance = null;
/**
* 私有构造方法,防止被实例化
*/
private Singleton(){}
/**
* 静态get方法
*/
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
这是一种典型的时间换空间的写法,不管三七二十一,每次创建实例时先锁起来,再进行判断,严重降低了系统的处理速度。
有没有更好的处理方式呢?
有,通过双检锁做两次判断,代码如下:
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized (Singleton.class) {
//再次检查实例是否存在,如果不存在才真正的创建实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。
但是,这样就没有问题了吗?
看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。
但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。
这样就可能出错了,我们以A、B两个线程为例:
-
A、B线程同时进入了第一个if判断
-
A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
-
由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
image-20201212010622553
-
B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
-
此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
加上volatile修饰Singleton,再做一次优化:
public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized (Singleton.class) {
//再次检查实例是否存在,如果不存在才真正的创建实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
**通过volatile修饰的变量,不会被线程本地缓存,所有线程对该对象的读写都会第一时间同步到主内存,从而保证多个线程间该对象的准确性 **
volatile的作用
- 防止指令重排序,因为instance = new Singleton()不是原子操作
- 保证内存可见
这个是比较完美的写法了,这种方式能够安全的创建唯一的一个实例,又不会对性能有太大的影响。
但是由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高,还有更优的写法吗?
通过静态内部类
public class Singleton {
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
/* 获取实例 */
public static Singleton getInstance() {
return SingletonFactory.instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return getInstance();
}
}
使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。
这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕, 这样我们就不用担心上面的问题。
同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式。
还有更完美的写法吗,通过枚举:
public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例。
*/
Instance;
}
使用枚举来实现单实例控制会更加简洁,而且JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
最后这种也是我最青睐的一种(代码少)。
工厂模式
业务场景
当创建对象逻辑比较复杂时,可以考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。下面两种情况可视为创建逻辑复杂
类似规则配置解析的例子,代码中存在 if-else 分支判断,动态地根据不同的类型创建不同的对象。针对这种情况。考虑使用工厂模式,将这一大坨 if-else创建对象的代码抽离出来,放到工厂类中。
尽管不需要根据不同的类型创建不同的对象,但是,单个对象本身的创建过程比较复杂,比如要组合其他类对象,做各种初始化操作。在这种情况下,也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中。
对于第一种情况,当每个对象的创建逻辑都比较简单的时候,推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的简单工厂类,推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。同理,对于第二种情况,因为单个对象本身的创建逻辑就比较复杂,所以,建议使用工厂方法模式。置于抽象工厂模式,使用场景不多。如果碰到一个工厂需要承担多种不同类型的对象创建时,可以考虑使用。
定义和理解
今天我就聊一下工厂模式
,工厂模式在我们的电商领域的应用是非常广泛的。写之前我看了自己电脑项目的代码,确实随处可见。
工厂模式主要可以分为三大类:
- 简单工厂模式
- 工厂方法模式
- 抽象工程模式
今天我也主要围绕这三种模式来举例了解一下
简单工厂模式
工厂模式主要是用于对实现逻辑的封装,并且通过对公共的接口提供对象的实列画的服务,在我添加新的类时不需大动干戈,只要修改一点点就好。
举个例子我之前所在的电商业务怎么创建创建商品的:
在这个简单工厂里面,如果要创建活动商品1 以及活动商品2,我们要创建商品的时候只要调用简单工厂里面的创建商品方法,根据类型创建出不同的商品然后实列化返回就可以了。
简单工厂几种实现方式:
静态工厂模式
我们还是以创建商品为列子
这种方式创建看起来其实也没什么问题,根据类型创建不同的商品,但是有一个问题不知道大家发现没有?
是不是我每增加一种类型
是不是我还要去修改createProduct方法的 if else?这不是违背我们的开闭原则吗?
所以这种方式不好,我们还有接来的两种:
-
使用反射机制
-
直接注册商品对象,添加一个Type类型方法,根据type类型返回自身相同类型的方法
同样的我们还是以创建商品为列:反射实现
看上面的代码,我发现反射其实也很容易就实现了,但是在一些特定的情况下,并不适用,而且在某些特定的情况下是无法实现的,而且反射机制也会降低程序的运行效果,在对性能要求很高的场景下应该避免这种实现。
这里还有一个问题适用反射不当是容易导致线上机器出问题的,因为我们反射创建的对象属性是被SoftReference软引用的,所以当**-XX:SoftRefLRUPolicyMSPerMB** 没有设置好的话会一直让机器CPU很高。
当然他的默认值是1000,也就根据大家的情况而定吧,反正就是注意一下这点。
剩下的一种其实很反射实现很像,就是为了避免使用反射,在Map的对象中不是存的要添加的类,而是将要添加的每种类对象实列。这个我就不写demo了😂
工厂方法模式
工厂方法模式是对静态工厂模式的上的一种改进,我们的工厂类直接被抽象化,需要具体特定化的逻辑代码转移到实现抽象方法的子类中,这样我们就不要再去修改工厂类(即:不用再去做什么if else 修改)这也是我们当前比较常用的一种方式。
还是我们以创建商品为例:
看这张图 其实就是说白了再创建一个工厂,用来创建工厂类对象
接下来我们看下代码怎么来实现这个功能吧:
这里我们先创建一个抽象工厂方法
再创建一个商品工厂去继承抽象工厂方法。
当还有其他类型的时候我们只要去继承我们创建的工厂方法可以了
这个代码大家肯定都能实现出来,但是我们真正的需要学习的是前辈们的这种工厂模式的思想。把这种思想运用到我们真实的业务场景中,学以致用才能对我们有真正的提升。
我再给大家举一个列子:学以致用
假设现在leader要你做一个分享商品图片,我们知道商品的类型 有很多,比如 无SKU 商品,有SKU 商品,下单分享,邀请分享…等一系列的场景。那我们怎么去设计这个代码做到更加的易懂,易读,今后扩展性好呢?
ps:sku和spu是我们电商里面的名词,spu差不多跟item也就是商品是一个维度,一个商品item有很多sku,比如:iphone是一个商品是item 也是spu,白色的iphone 12 64G 就是一个具体的sku。
第一步我们应该都是定义一个创建分享模版
第二步我们再创建一个分享工厂根据我们的类型获取我们预先加载在Spring容器中的bean实列
最后就是定义我们不同的类型来实现分享图片。
其实我们学习设计模式就是这种思想,有了这种思想 上面提到的三点优势才能体现出来对我们今后的成长,面对复杂业务设计以及思考能力才能提升。
那么问题来了,什么时候该用工厂方法模式,而非简单工厂模式呢?
这里引用设计模式之美里面的一句话:当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。
而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得很复杂
抽象工厂模式
看完工厂方法模式,再理解抽象工厂模式就更加简单,因为它其实就是工厂方法的一个延伸。
工厂方法类中只有一个抽象方法,要想实现多种不同的类对象,只能去创建不同的具体工厂方法的子类来实列化,而抽象工厂 则是让一个工厂负责创建多个不同类型的对象
感觉理解起来有点绕,我们还是来画个图吧
样子可能有点丑,但是能说明问题,其实看上去可以分为以上几个部分组成:
- 抽象工厂类
- 具体工厂类
- 抽象类
抽象工厂类我个人可以理解为一个刚出厂的手机,具体抽象工厂这是认为我们每个人对这个手机壁纸自定义设置,最后抽象类我理解就是手机壁纸。
实践应用
首先,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
其次,需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(也就是连接方式)再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了。
再次,工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
最后,可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或EasyMock。
UML
抽象工厂模式
观察者模式
业务场景
登录注册是最常见的业务场景了,就拿注册来说是,我们经常遇到类似情况,用户注册成功之后,给用户发送短信或者发送邮件,有如下代码
void register(User user){
insertRegister(user);
sendMsm();
sendMobileMsg();
}
这部分代码有什么问题呢,如果产品添加新的需求,现在注册成功的用户,再给发送一条短信通知,于是乎我们又要修改register方法代码,是不是违反开闭原则
并且调用发短信的接口失败了,是不是影响用户注册,这时候是不是得加个异步方法给通知才可以
实际上我们可以使用观察者模式优化
定义和理解
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新
举例说明:韩非子大家都应该记得吧,法家的代表人物,主张建立法制社会,实施重罚制度,真是非常有远见呀!看看现在社会在呼吁什么,建立法制化的社会,这在2000多年前就已经提出了。大家可能还不知道,法家还有一个非常重要的代表人物——李斯。李斯是秦国的丞相,最终被残忍车裂的那位,李斯和韩非子都是荀子的学生,李斯是师兄,韩非子是师弟,若干年后,李斯成为最强诸侯国秦国的上尉,致力于统一全国,于是安插了间谍到各个国家的重要人物的身边,以获取必要的信息,韩非子作为韩国的重量级人物,身边自然有不少间谍,韩非子做的事,李斯都了如指掌,那可是相隔千里!怎么做到的呢?间谍呀!我们先通过程序把这个过程展现一下,看看李斯是怎么监控韩非子的
实践应用
完成某件事之后,异步通知场景比如办理业务成功发送短信,邮件
订阅发布
UML
我们先来解释一下观察者模式的几个角色名称:
● Subject被观察者
定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
● Observer观察者
观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。
● ConcreteSubject具体的被观察者
定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
● ConcreteObserver具体的观察者
每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。
观察者模式使用
观察者
/**
* @description 观察者
*/
public interface Observer {
void doEvent();
}
被观察者
package com.geekmice.onetomany.eventbus;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
public class Subject {
private Vector<Observer> observers = new Vector<>();
private Integer state;
public Integer getState() {
return state;
}
public void setState(Integer state) {
notifyAllObservers(state);
}
/**
* 添加观察者
*/
public void addServer(Observer observer) {
observers.add(observer);
}
/**
* @description 删除观察者
*/
public void removeServer(Observer observer) {
observers.remove(observer);
}
/**
* @param state
* @description 通知所有观察者
* */
private void notifyAllObservers(Integer state) {
if (state != 1) {
System.out.println("不是通知的状态");
return;
}
for (Observer obj : observers) {
obj.doEvent();
}
}
}
发送邮件被观察者
package com.geekmice.onetomany.eventbus;
public class EmailObserver implements Observer{
@Override
public void doEvent() {
System.out.println("发送email");
}
}
发送短信被观察者
package com.geekmice.onetomany.eventbus;
public class MSMessageObserver implements Observer {
@Override
public void doEvent() {
System.out.println("发送MSM短信");
}
}
场景类
package com.geekmice.onetomany.eventbus;
public class Context {
public static void main(String[] args) {
Subject observerAble = new Subject();
observerAble.setState(1);
Observer observer = new EmailObserver();
observerAble.addServer(observer);
observer.doEvent();
Observer msMessageObserver = new MSMessageObserver();
observerAble.addServer(observer);
msMessageObserver.doEvent();
}
}
eventbus优化
Guava EventBus封装好观察者模式,提供了一套基于注解的事件总线,api可以灵活的使用,美滋滋
被观察者
package com.geekmice.onetomany.eventbus;
import com.google.common.eventbus.EventBus;
public class EventBusCenter {
private static EventBus eventBus = new EventBus();
private EventBusCenter() {
}
public static EventBus getInstance() {
return eventBus;
}
/**
* @description 添加观察者
* @param obj
*/
public static void register(Object obj) {
eventBus.register(obj);
}
/**
* @description 删除观察者
* @param object
*/
public static void unregister(Object object) {
eventBus.unregister(object);
}
/**
* @description 把消息推送给观察者
* @param object
*/
public static void post(Object object) {
eventBus.post(object);
}
}
观察者
package com.geekmice.onetomany.eventbus;
import com.google.common.eventbus.Subscribe;
// 声明观察者
public class EventListener {
// 加了订阅,这里标记这个方法是事件处理方法
@Subscribe
public void handle(NotifyEvent notifyEvent) {
System.out.println("发送IM消息" + notifyEvent.getImNo());
System.out.println("发送短信消息" + notifyEvent.getMobileNo());
System.out.println("发送mail消息" + notifyEvent.getEmailNo());
}
}
事件通知类
package com.geekmice.onetomany.eventbus;
// 通知事件类
public class NotifyEvent {
private String mobileNo;
private String emailNo;
private String imNo;
public NotifyEvent(String mobileNo, String emailNo, String imNo) {
this.mobileNo = mobileNo;
this.emailNo = emailNo;
this.imNo = imNo;
}
public String getMobileNo() {
return mobileNo;
}
public void setMobileNo(String mobileNo) {
this.mobileNo = mobileNo;
}
public String getEmailNo() {
return emailNo;
}
public void setEmailNo(String emailNo) {
this.emailNo = emailNo;
}
public String getImNo() {
return imNo;
}
public void setImNo(String imNo) {
this.imNo = imNo;
}
}
demo测试
package com.geekmice.onetomany.eventbus;
public class Client {
public static void main(String[] args) {
EventListener eventListener = new EventListener();
EventBusCenter.register(eventListener);
EventBusCenter.post(new NotifyEvent("1660702****", "1233445@163.com", "666"));
}
}
发送IM消息666
发送短信消息1660702****
发送mail消息1233445@163.com