有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著)。该系列文章可随意转载。
Singleton 模式 :只有一个实例
何为Singleton 模式 :
像这种确保只生成一个实例的模式被称为Singleton 模式。
Singleton 模式登场的角色:
类图如下:
Demo 如下:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(???):
单例模式有多种实现 饱汉式、饿汉式、DCL等等,这里不做过多赘述,如有需要,可以参考:设计模式-单例模式介绍和使用场景及Spring单例模式的使用
相关设计模式:以下设计模式中,多数情况下都只会生成一个实例。
Prototype 模式 :通过复制生成实例
在开发过程中会存在 在不指定类名的前提下生成实例 的需求,如:
Prototype 模式中登场的角色
类图如下:
Demo 如下:
public class Manager {
private Map<String, Product> registeredProducts = Maps.newHashMap();
/**
* 注册 Product
* @param name
* @param product
*/
public void register(String name, Product product) {
registeredProducts.put(name, product);
}
/**
* 创建 Product
* @param productName
* @return
*/
public Product create(String productName) {
return registeredProducts.get(productName).createClone();
}
}
// Product 接口
public interface Product extends Cloneable {
/**
* 使用该 Product
* @param msg
*/
void user(String msg);
/**
* 通过 clone 创建出一个 Product实例
* @return
*/
Product createClone();
}
public class MessageBox implements Product {
private char msgChar;
public MessageBox(char msgChar) {
this.msgChar = msgChar;
}
@Override
public void user(String msg) {
for (int i = 0; i < msg.length() + 4; i++) {
System.out.print(msgChar);
}
System.out.println();
System.out.println(msgChar + " " + msg + " " + msgChar);
for (int i = 0; i < msg.length() + 4; i++) {
System.out.print(msgChar);
}
System.out.println();
}
@Override
public Product createClone() {
try {
return (Product) clone();
} catch (Exception e) {
return null;
}
}
}
public class UnderlinePen implements Product {
private char msgChar;
public UnderlinePen(char msgChar) {
this.msgChar = msgChar;
}
@Override
public void user(String msg) {
System.out.println(msg);
for (int i = 0; i < msg.length(); i++) {
System.out.print(msgChar);
}
System.out.println();
}
@Override
public Product createClone() {
try {
return (Product) clone();
} catch (Exception e) {
return null;
}
}
}
// 测试用main 方法
public class DemoMain {
public static void main(String[] args) {
Manager manager = new Manager();
UnderlinePen underlinePen = new UnderlinePen('~');
MessageBox messageBox = new MessageBox('*');
manager.register("underlinePen", underlinePen);
manager.register("messageBox", messageBox);
manager.create("underlinePen").user("Hello World");
System.out.println();
manager.create("messageBox").user("Hello World");
}
}
Main 方法执行后输出如下
Hello World
~~~~~~~~~~~
***************
* Hello World *
***************
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(???):
在某项目中,当遇到异常情况时需要发送告警信息,告警信息多种多样,但是标题信息不同,这时使用 Prototype 模式来 拷贝告警信息的基础信息。(不过实际上即是不依靠 Prototype 模式也可以实现该需求,不过这里通过 WarnMsgManager 的存在使其与 WarnMsg 解耦)如下:
// 告警消息基础类
@Slf4j
@Data
public class WarnMsg implements Cloneable {
/**
* 消息标题
*/
private String title;
/**
* 消息内容
*/
private String content;
/**
* 环境
*/
private String env;
/**
* 需要的话可以重写该方法
*
* @return
*/
public WarnMsg clone(String content) {
try {
final WarnMsg clone = (WarnMsg) clone();
clone.setContent(content);
return clone;
} catch (Exception e) {
log.warn("[告警信息][克隆失败]", e);
}
return null;
}
}
// 上线告警
public class OnlineWarnMsg extends WarnMsg {
public OnlineWarnMsg() {
setTitle("上线");
setEnv("AAA");
}
}
// 离线告警
public class OfflineWarnMsg extends WarnMsg {
/**
* 设置消息的初始属性,假设这里有很多属性
*/
public OfflineWarnMsg() {
setTitle("下线");
setEnv("AAA");
}
}
// 告警消息管理类
public class WarnMsgManager {
/**
* 通过 warnMsgMap 可以将 WarnMsg 与 WarnMsgManager 解耦
*/
private Map<String, WarnMsg> warnMsgMap = Maps.newHashMap();
/**
* 注册消息类型
* @param name
* @param warnMsg
*/
public void register(String name, WarnMsg warnMsg) {
warnMsgMap.put(name, warnMsg);
}
/**
* 创建对应消息类型
* @param name
* @return
*/
public WarnMsg create(String name, String content) {
return warnMsgMap.get(name).clone(content);
}
}
public class Main {
public static void main(String[] args) {
// 初始化数据
WarnMsgManager warnMsgManager = new WarnMsgManager();
warnMsgManager.register("offline", new OfflineWarnMsg());
warnMsgManager.register("online", new OnlineWarnMsg());
// 创建告警消息实例
final WarnMsg online = warnMsgManager.create("online", "上线信息");
final WarnMsg offline = warnMsgManager.create("online", "下线信息");
System.out.println("offline = " + offline);
System.out.println("online = " + online);
}
}
Prototype 模式可以用于 对象的构造成本比较高或是较为麻烦时,其是对象构建时需要频繁获取链接或锁的情况时,通过 clone 的效率会更高,否则可以使用new的形式。
相关设计模式
扩展思路:
Builder 模式 :组装复杂的实例
Builder 模式中登场的角色
类图如下:
Demo 如下:
// 抽象构建器类
public abstract class Builder {
/**
* 构建 title
* @param title
*/
public abstract void builderTitle(String title);
/**
* 构建 content
* @param content
*/
public abstract void builderContent(String content);
/**
* 构建结束 :可以做最后的整合或者一些必填参数的校验
*/
public abstract void close();
}
// 使用 Builder 生成实例
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
/**
* 定义生成过程,并且可以做一些额外的操作
*/
public void construct() {
builder.builderTitle("title");
builder.builderContent("content");
builder.close();
}
}
public class ConcreteBuilder extends Builder {
private String fileContent = "";
@Override
public void builderTitle(String title) {
fileContent += title + "\n";
}
@Override
public void builderContent(String content) {
fileContent += content;
}
@Override
public void close() {
// TODO : 可以用于校验一些必须填充的条件,或其他处理
}
public String getResult() {
return fileContent;
}
}
public class Main {
public static void main(String[] args) {
// 实际使用中会可能会由多种 Builder
ConcreteBuilder concreteBuilder = new ConcreteBuilder();
final Director director = new Director(concreteBuilder);
director.construct();
final String result = concreteBuilder.getResult();
System.out.println(result);
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义登陆拦截器
JwtLoginFilter jwtLoginFilter = new JwtLoginFilter();
jwtLoginFilter.setAuthenticationManager(authenticationManagerBean());
jwtLoginFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
jwtLoginFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
JwtTokenFilter jwtTokenFilter = new JwtTokenFilter();
// 使用自定义验证实现器
JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(userDetailsService, passwordEncoder);
// 登陆验证信息
http.authenticationProvider(jwtAuthenticationProvider)
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
object.setAccessDecisionManager(accessDecisionManager);
return object;
}
})
.anyRequest().authenticated()
.and()
.formLogin();
// jwt 拦截器配置
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) //禁用session
.and()
.csrf().disable()
.addFilterAt(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class) // 添加拦截器
.addFilterAfter(jwtTokenFilter, JwtLoginFilter.class);
// 权限处理信息
http.exceptionHandling()
// 用来解决认证过的用户访问无权限资源时的异常
.accessDeniedHandler(accessDeniedHandler)
// 用来解决匿名用户访问无权限资源时的异常
.authenticationEntryPoint(authenticationEntryPoint);
}
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(???):
在某项目中,某一个接口返回的数据结果集相同,但是其内部会根据 Region 的不同通过不同的途径去获取数据,最终构建出来一个完成的数据集,如果对象的构建过程非常耗时可以将对象的部分内容异步构建。这里可以使用Builder。如下:
// 顶层抽象类,protected 防止子类滥用方法
public abstract class Builder {
protected abstract String buildTitle(String msg);
protected abstract String buildAuthor(String msg);
protected abstract String buildContent(String msg);
protected abstract boolean close();
}
// 增加获取数据集的方法
public abstract class DataBuilder extends Builder {
/**
* 构建并校验
* @return
*/
public String getResult() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(buildTitle("")).append("\n");
stringBuilder.append(buildAuthor("")).append("\n");
stringBuilder.append(buildContent("")).append("\n");
close();
return stringBuilder.toString();
}
}
// 一个数据来源的实现,简单实现
public class OneDataBuilder extends DataBuilder {
@Override
public String buildTitle(String msg) {
return "title";
}
@Override
public String buildAuthor(String msg) {
return "author";
}
@Override
public String buildContent(String msg) {
return "content";
}
@Override
public boolean close() {
// TODO : 检验数据或对数据做最后的处理
return true;
}
}
// main 方法调用
public class Main {
public static void main(String[] args) {
// 实际应用会有多个 DataBuilder。
DataBuilder dataBuilder = new OneDataBuilder();
// 获取结果。不同的Region数据来源不同,最后拼接的数据集 result。
// 在某些 Region 中,DataBuilder 不同部分的数据甚至可以可以开启异步来获取。最终在 getResult 方法中拼接
final String result = dataBuilder.getResult();
System.out.println("result = " + result);
}
}
相关设计模式 :
Abstract Factory 模式 :将关联零件组装成产品
本部分内容推荐阅读 : 抽象工厂模式在spring源码中的应用,以下部分内容参考该文。
抽象工厂会将抽象零件组装成抽象产品。也就是说,我们并不关心零件的具体实现,而是只关心接口。我们仅使用该接口欧将零件组装成为产品。
在 Template Method 模式和 Builder 模式中,子类这一层负责方法的具体实现。在 Abstract Factory 模式中也是一样的。在子类这一层中有具体的工厂,它负责将具体的零件组装成为具体的产品。
Abstract Factory 模式 登场的角色如下:
类图如下:
Demo 如下:
// 抽象罐头工厂类
public abstract class CanFactory {
/**
* 获取工厂实例
* @param className
* @return
* @throws Exception
*/
public CanFactory getCanFactory(String className) throws Exception {
final Class<?> factoryClass = Class.forName(className);
return (CanFactory) factoryClass.newInstance();
}
/**
* 包装苹果罐头
*/
public abstract AppleCan packAppleCan();
/**
* 包装牛肉罐头
*/
public abstract BeefCan packBeefCan();
}
// 互联网罐头工厂
public class InternetCanFactory extends CanFactory {
// 包装苹果罐头
@Override
public AppleCan packAppleCan() {
return new BaiduAppleCan();
}
// 包装牛肉贯通
@Override
public BeefCan packBeefCan() {
return new TencentBeefCan();
}
}
// 牛肉罐头抽象类
public abstract class BeefCan {
/**
* 闻起来如何
* @return
*/
public abstract String smell();
}
// 苹果罐头抽象类
public abstract class AppleCan {
/**
* 吃起来如何
* @return
*/
public abstract String taste();
}
// 罐头具体实现类
public class BaiduAppleCan extends AppleCan {
@Override
public String taste() {
return "牛肉真香";
}
}
public class TencentBeefCan extends BeefCan {
@Override
public String smell() {
return "苹果真甜";
}
}
在上面的 Demo 中:CanFactory、AppleCan 和 BeefCan 的关系已经确定, CanFactory 用来生产 AppleCan 和 BeefCan ,而 InternetCanFactory 作为 CanFactory 的实现类,可以生产 百度苹果罐头 和 腾讯牛肉罐头 。即抽象层定义工厂框架,实现层进行具体的实现。
在 Spring 中,BeanFactory 是用于管理 Bean 的一个工厂,所有工厂都是 BeanFactory 的子类。这样我们可以通过 IOC 容器来管理访问 Bean,根据不同的策略调用 getBean() 方法,从而获得具体对象。
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(???):
在项目A中,需要对多种文件类型资料进行解析,而解析后的结果是相同的,则可以通过抽象工厂模式为每种文件资料实现一个工厂类。如下:
public class FileHandlerFactory {
/**
* pdf
*/
public static final String FILE_PDF = "pdf";
/**
* excel
*/
public static final String FILE_EXCEL = "excel";
/**
* 获取处理器
* @param fileType
* @return
*/
public static FileHandler getFileHandler(String fileType) {
if (FILE_PDF.equalsIgnoreCase(fileType)) {
return new PdfFileHandler();
} else if (FILE_EXCEL.equalsIgnoreCase(fileType)) {
return new ExcelFileHandler();
}
return null;
}
}
public abstract class FileHandler {
/**
* 处理
*/
abstract void handle();
}
public class ExcelFileHandler extends FileHandler {
@Override
void handle() {
System.out.println("ExcelFileHandler.handle");
}
}
public class PdfFileHandler extends FileHandler{
@Override
void handle() {
System.out.println("PdfFileHandler.handle");
}
}
public class DemoMain {
public static void main(String[] args) {
FileHandler pdfHandler = FileHandlerFactory.getFileHandler(FileHandlerFactory.FILE_PDF);
FileHandler excelHandler = FileHandlerFactory.getFileHandler(FileHandlerFactory.FILE_EXCEL);
pdfHandler.handle();
excelHandler.handle();
}
}
优点 : 抽象工厂模式隔离了具体类的生成, 使得客户并不需要知道什么被创建。 由于这种隔离,更换一个具体工厂就变得相对容易, 所有的具体工厂都实现了抽象工厂中定义的那些公共接口, 因此只需改变具体工厂的实例, 就可以在某种程度上改变整个软件系统的行为。当一个族中的多个对象被设计成一起工作时, 它能够保证客户端始终只使用同一个族中的对象。增加新的族很方便, 无须修改已有系统, 符合“开闭原则”。
缺点 : 增加新的等级结构麻烦, 需要对原有系统进行较大的修改, 甚至需要修改抽象层代码,这显然会带来较大的不便, 违背了“开闭原则”。
使用场景 : 一个系统不应当依赖于具体类实例如何被创建、 组合和表达的细节, 这对于所有类型的工厂模式都是很重要的, 用户无须关心对象的创建过程, 将对象的创建和使用解耦;系统中有多于一个的族, 而每次只使用其中某一族。 可以通过配置文件等方式来使得用户可以动态改变族, 也可以很方便地增加新的族。属于同一个族的对象将在一起使用, 这一约束必须在系统的设计中体现出来。 同一个族中的对象可以是没有任何关系的对象, 但是它们都具有一些共同的约束, 如同一操作系统下的按钮和文本框, 按钮与文本框之间没有直接关系, 但它们都是属于某一操作系统的, 此时具有一个共同的约束条件: 操作系统的类型。等级结构稳定, 设计完成之后, 不会向系统中增加新的等级结构或者删除已有的等级结构。
相关设计模式:
扩展思路: