本篇只是总结下各个设计模式要表达的核心思想。算是我的笔记。
创建型
单例模式
模式侧重点:单实例
对于某个类,在进程级别只允许一个实例存在。当然还有多进程间共享单例(一般也不常用)或者线程级别的单例。
实现方式:
① 恶汉式(最常见,最简单)
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
② 懒汉式(双重检测,为防止指令冲排序,加上volatile)
public class DoubleCheck {
private static volatile DoubleCheck INSTANCE;
private DoubleCheck() {
}
public static DoubleCheck getInstance() {
if (INSTANCE != null) {
return INSTANCE;
}
synchronized (DoubleCheck.class) {
if (INSTANCE != null) {
return INSTANCE;
}
INSTANCE = new DoubleCheck();
return INSTANCE;
}
}
}
③ 静态内部类(内部类是要在用到时候才加载)
public class StaticClass {
private StaticClass() {
}
private static class Holder {
private static final StaticClass INSTANCE = new StaticClass();
}
public static StaticClass getInstance() {
return Holder.INSTANCE;
}
}
④ 使用Enum
来实现(反正我是没用过)
public enum SingletonEnum {
INSTANCE,
;
public void soSomething() {
System.out.println("do something");
}
}
这个设计模式应该谁都懂,没必要多说。细节的资料网上太多。在大部分情况,我会选择恶汉式的实现方式,因为简单嘛,不用动脑子。不过一般项目都会加入Spring
,直接使用Spring
的Singleton
即可。除非要是技术框架开发,那就要看实际情况了。
工场模式
模式侧重点:创建对象的类型
简单工场(使用pulic static T create/getInstance/valueOf(){},把创建对象的细节封装起来)
工场方法(针对单一维度)
public interface IType {
}
public class YamlType implements IType {
}
public class XmlType implements IType {
}
public class PropertiesType implements IType {
}
public class JsonType implements IType {
}
public interface ITypeFactory {
IType create();
}
public class YamlTypeFactory implements ITypeFactory {
@Override
public IType create() {
return new YamlType();
}
}
public class XmlTypeFactory implements ITypeFactory {
@Override
public IType create() {
return new XmlType();
}
}
public class PropertiesTypeFactory implements ITypeFactory {
@Override
public IType create() {
return new PropertiesType();
}
}
public class JsonTypeFactory implements ITypeFactory {
@Override
public IType create() {
return new JsonType();
}
}
工场方法,创建单一维度的东西,用得比较多。要跟下面的抽象工场对比起来看。
抽象工场(针对多个维度,不常用)
public interface IType1 {
}
public class YamlType1 implements IType1 {
}
public class XmlType1 implements IType1 {
}
public class PropertiesType1 implements IType1 {
}
public class JsonType1 implements IType1 {
}
public interface IType2 {
}
public class YamlType2 implements IType2 {
}
public class XmlType2 implements IType2 {
}
public class PropertiesType2 implements IType2 {
}
public class JsonType2 implements IType2 {
}
public interface ITypeFactory {
IType1 createType1();
IType2 createType2();
}
public class YarmTypeFactory implements ITypeFactory {
@Override
public IType1 createType1() {
return new YamlType1();
}
@Override
public IType2 createType2() {
return new YamlType2();
}
}
public class XmlTypeFactory implements ITypeFactory {
@Override
public IType1 createType1() {
return new XmlType1();
}
@Override
public IType2 createType2() {
return new XmlType2();
}
}
public class PropertiesTypeFactory implements ITypeFactory {
@Override
public IType1 createType1() {
return new PropertiesType1();
}
@Override
public IType2 createType2() {
return new PropertiesType2();
}
}
public class JsonTypeFactory implements ITypeFactory {
@Override
public IType1 createType1() {
return new JsonType1();
}
@Override
public IType2 createType2() {
return new JsonType2();
}
}
抽象工场与简单工场相比,抽象工场可以创建多个维度,不过场景比较狭窄。
建造者模式
模式侧重点:对象创建过程
当某个类的参数过多,构造细节过于复杂时,使用builder
模式。build()
方法可以做一些参数有效性的校验工作
不太好的地方:Builder
类会把主类中的参数做一层冗余。这个在内部看来不太好看。也让人觉得不舒服。在外面用的时候确实比较爽。说明这样的爽是有代价的。
建造者模式与工场模式的区别:
① 建造者模式,重点关注在构造某个类的细节上
② 工场模式,重点关注在产品的种类上
一家必胜客,可以生产牛肉披萨,鸡肉披萨,培根披萨。这种分类使用工场模式来搞。具体牛肉披萨的生产过程,使用建造着模式来搞。他们都是创建,但不冲突。关注点有粗细之分。
这边以com.alibaba.dubbo.common.URL
构造作为案例说明下。
// URL 出自 Dubbo源码,版本2.6.4
// 它原本使用的是多个构造方法的形式。
// 我觉得这边参数较多。在外面使用时,每个参数的提现不是很明显,修改成Builder模式更优一些。
public final class URL implements Serializable {
private static final long serialVersionUID = -1985165475234910535L;
private final String protocol;
private final String username;
private final String password;
// by default, host to registry
private final String host;
// by default, port to registry
private final int port;
private final String path;
private final Map<String, String> parameters;
// ==== cache ====
private volatile transient Map<String, Number> numbers;
private volatile transient Map<String, URL> urls;
private volatile transient String ip;
private volatile transient String full;
private volatile transient String identity;
private volatile transient String parameter;
private volatile transient String string;
protected URL() {
this.protocol = null;
this.username = null;
this.password = null;
this.host = null;
this.port = 0;
this.path = null;
this.parameters = null;
}
public URL(String protocol, String host, int port) {
this(protocol, null, null, host, port, null, (Map<String, String>) null);
}
public URL(String protocol, String host, int port, String[] pairs) { // varargs ... confilict with the following path argument, use array instead.
this(protocol, null, null, host, port, null, CollectionUtils.toStringMap(pairs));
}
public URL(String protocol, String host, int port, Map<String, String> parameters) {
this(protocol, null, null, host, port, null, parameters);
}
public URL(String protocol, String host, int port, String path) {
this(protocol, null, null, host, port, path, (Map<String, String>) null);
}
public URL(String protocol, String host, int port, String path, String... pairs) {
this(protocol, null, null, host, port, path, CollectionUtils.toStringMap(pairs));
}
public URL(String protocol, String host, int port, String path, Map<String, String> parameters) {
this(protocol, null, null, host, port, path, parameters);
}
public URL(String protocol, String username, String password, String host, int port, String path) {
this(protocol, username, password, host, port, path, (Map<String, String>) null);
}
public URL(String protocol, String username, String password, String host, int port, String path, String... pairs) {
this(protocol, username, password, host, port, path, CollectionUtils.toStringMap(pairs));
}
public URL(String protocol, String username, String password, String host, int port, String path, Map<String, String> parameters) {
if ((username == null || username.length() == 0)
&& password != null && password.length() > 0) {
throw new IllegalArgumentException("Invalid url, password without username!");
}
this.protocol = protocol;
this.username = username;
this.password = password;
this.host = host;
this.port = (port < 0 ? 0 : port);
// trim the beginning "/"
while (path != null && path.startsWith("/")) {
path = path.substring(1);
}
this.path = path;
if (parameters == null) {
parameters = new HashMap<String, String>();
} else {
parameters = new HashMap<String, String>(parameters);
}
this.parameters = Collections.unmodifiableMap(parameters);
}
}
// 使用Builder模式重构之后
public final class URL implements Serializable {
private final String protocol;
private final String username;
private final String password;
// by default, host to registry
private final String host;
// by default, port to registry
private final int port;
private final String path;
private final Map<String, String> parameters;
public static class Builder {
private final String protocol;
private String username;
private String password;
// by default, host to registry
private String host;
// by default, port to registry
private int port;
private String path;
private Map<String, String> parameters;
public Builder(String protocol) {
this.protocol = protocol;
}
public Builder setUsernameAndPassword(String username, String password) {
if ((username == null || username.length() == 0)
&& password != null && password.length() > 0) {
throw new IllegalArgumentException("Invalid url, password without username!");
}
this.username = username;
this.password = password;
return this;
}
public Builder setHost(String host) {
this.host = host;
return this;
}
public Builder setPort(int port) {
this.port = Math.max(port, 0);
;
return this;
}
public Builder setPath(String path) {
while (path != null && path.startsWith("/")) {
path = path.substring(1);
}
this.path = path;
return this;
}
public Builder setParameters(Map<String, String> parameters) {
this.parameters = Collections.unmodifiableMap(parameters == null ? new HashMap<>() : new HashMap<>(parameters));
return this;
}
public URL build() {
return new URL(this.protocol, this.username, this.password, this.host, this.port, this.path, this.parameters);
}
}
public URL(String protocol, String username, String password, String host, int port, String path, Map<String, String> parameters) {
this.protocol = protocol;
this.username = username;
this.password = password;
this.host = host;
this.port = port;
this.path = path;
this.parameters = parameters;
}
}
// 在URL对象构建时,对于参数的设置会更加明显,方便阅读
URL url = new URL.Builder("dubbo")
.setUsernameAndPassword("abc", "abc")
.setHost("localhost")
.setPort(20881)
.setPath("/dubbo")
.setParameters(null)
.build();
原型模式
模式侧重点:对象的复制过程
原型模式是在说对象copy
的问题,衍生出:序列化,反序列化,浅克隆和深克隆。RPC过程。
运用场景:对象的创建成本比较大。内部的某些属性需要通过大量的计算得到。或者需要通过远程访问网络或者文件等系统得到。比较耗时耗力。
结构型
享元模式
模式侧重点:特定对象在内存中的共享
目的是共享内存中的某些对象资源
比如棋类游戏,每个棋子可以作为共享资源
public class Chess {
private int id;
private String text;
private Color color;
public Chess(int id, String text, Color color) {
this.id = id;
this.text = text;
this.color = color;
}
public static enum Color {
RED, BLACK;
}
}
public class ChessFactory {
private static final Map<Integer, Chess> PIECES = new HashMap<>();
static {
PIECES.put(1, new Chess(1, "将", Chess.Color.BLACK));
PIECES.put(2, new Chess(2, "将", Chess.Color.RED));
}
public static Chess get(int id) {
return PIECES.get(id);
}
}
我觉得这个模式用的不多,但某些场景非用不可。其实如果让某个程序员设计棋牌类游戏,就算他不知道有享元模式的存在,也会考虑把棋子这类可复用对象缓存起来。即使在最初没有这么设计,在压力测试或者生产内存持续暴涨的情况下,也会往这个方向优化。
门面模式
模式侧重点:让调用者易用
门面模式,偏思想,而非结构。门面模式使得调用者更加易用。
比如slf4j
,比如把几个原子接口做一个整合。
装饰器模式
模式侧重点:功能增强
对象包装对象,目的是功能增强。
最常见的JAVA IO
。
Dubbo
,Spring
中都有一些wapper
类。
// 出自Dubbo源码 ProtocolFilterWrapper,版本2.6.4
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;// 被包装的protocol对象
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
// -----------------省略一些代码-------------------
}
@Override
public int getDefaultPort() {
return protocol.getDefaultPort();
}
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
@Override
public void destroy() {
protocol.destroy();
}
}
上述代码在Dubbo
中的具体原理参考文章 dubbo的filter
Dubbo
的Handler
机制包装非常复杂,可以详细看下。下面给出一张图,供参考。具体的包装过程看文章 dubbo的handler机制
代理模式
模式侧重点:修改执行动作或者执行过程
真实动作的前面和后面分别做一些行为。真实动作使用委托的方式来调用。
静态代理
public interface Subject {
void buyTicket();
}
public class SubjectImpl implements Subject {
@Override
public void buyTicket() {
System.out.println("买票");
}
}
public class SubjectProxy implements Subject {
private Subject subject;
public SubjectProxy(Subject subject) {
this.subject = subject;
}
@Override
public void buyTicket() {
this.doBefore();
subject.buyTicket();
this.doAfter();
}
private void doBefore() {
System.out.println("doBefore");
}
private void doAfter() {
System.out.println("doAfter");
}
}
动态代理
public class DynamicSubjectPorxy implements InvocationHandler {
private Subject subject;
public DynamicSubjectPorxy(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.doBefore();
Object res = method.invoke(subject, args);
this.doAfter();
return res;
}
private void doBefore() {
System.out.println("doBefore");
}
private void doAfter() {
System.out.println("doAfter");
}
}
动态代理的应用会比较多,其实原理挺简单,就是在运行时动态得给某个接口生成一个对象,这个对象实现了目标接口。在JDK
中,开发者只需要实现InvocationHandler
接口,重写invoke
方法。通过 method.invoke(subject, args)
方法实现目标方法的调用。那在调用目标方法之前或者调用之后做些什么完全由开发者控制。甚至目标方法都不一定要调用,或者目标方法都不一定有。
如果对于动态代理不熟悉的朋友,务必要亲自动手去写各种案例,否则很难在脑子里形成非常直接的思维。
代理在RPC
场景中的应用,比如在Dubbo
框架中,服务消费方Consumer
只有接口的声明,并没有具体实现,具体实现在服务提供方。那我们就需要给服务消费方的所有接口生成动态代理。当调用服务时,会进入invoke
方法。invoke
方法中就需要把入参对象等相关信息序列化之后通过TCP
方式发送给服务提供方,后续就不在这边细说了。关于Dubbo
的服务调用方式相关原理可以参考文档 dubbo服务导出和引入
适配器模式
模式侧重点:兼容性的适配
适配器,属于一种补偿模式,用于补偿原有设计的不足之处。
adapter
持有adaptee
目标对象的委托,对其调用。或者继承关系。
继承的方式
public class Adaptee {
public void method1() {
System.out.println("目标方法");
}
}
public class Adapter extends Adaptee implements ITarget {
@Override
public void doMethod1() {
super.method1();
}
}
public class Client {
public static void main(String[] args) {
ITarget target = new Adapter();
target.doMethod1();
}
}
组合的方式
public class Adaptee {
public void method1() {
System.out.println("目标方法");
}
}
public class Adapter2 implements ITarget {
private Adaptee adaptee;
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void doMethod1() {
adaptee.method1();
}
}
public class Client {
public static void main(String[] args) {
ITarget target = new Adapter2(new Adaptee());
target.doMethod1();
}
}
说一个Dubbo
中运用的实际案例。Codec2
是Dubbo
的编码和反编码模块。目前Codec2
模块已经升级到Codec2
,而原来老的Codec
还有些用处。那么所有的上层模块都依赖Codec2
新编码模块之后,怎么兼容Codec
老模块呢?看看他们代码是怎么写的就知道了。
// 新模块
public interface Codec2 {
@Adaptive({Constants.CODEC_KEY})
void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException;
@Adaptive({Constants.CODEC_KEY})
Object decode(Channel channel, ChannelBuffer buffer) throws IOException;
enum DecodeResult {
NEED_MORE_INPUT, SKIP_SOME_INPUT
}
}
// 老模块
public interface Codec {
Object NEED_MORE_INPUT = new Object();
@Adaptive({Constants.CODEC_KEY})
void encode(Channel channel, OutputStream output, Object message) throws IOException;
@Adaptive({Constants.CODEC_KEY})
Object decode(Channel channel, InputStream input) throws IOException;
}
Dubbo
在Codec2
下面设置了一个适配器类CodecAdapter
。CodecAdapter
会持有Codec
的委托。
// CodecAdapter实现了Codec2
public class CodecAdapter implements Codec2 {
// CodecAdapter这个适配器中持有Codec的委托
private Codec codec;
public CodecAdapter(Codec codec) {
Assert.notNull(codec, "codec == null");
this.codec = codec;
}
@Override
public void encode(Channel channel, ChannelBuffer buffer, Object message)
throws IOException {
UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(1024);
codec.encode(channel, os, message);
buffer.writeBytes(os.toByteArray());
}
@Override
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
byte[] bytes = new byte[buffer.readableBytes()];
int savedReaderIndex = buffer.readerIndex();
buffer.readBytes(bytes);
UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream(bytes);
Object result = codec.decode(channel, is);
buffer.readerIndex(savedReaderIndex + is.position());
return result == Codec.NEED_MORE_INPUT ? DecodeResult.NEED_MORE_INPUT : result;
}
public Codec getCodec() {
return codec;
}
}
桥梁模式
模式侧重点:抽象与具体解耦,或者上下层解耦
桥梁模式,我觉得是比较难理解的一个模式,它的定义很简单:将抽象和实现解耦,让它们可以独立变化。深刻理解却不容易。网上有很多案例,但这个模式如果以Demo
来聊,我觉得无法学到它的精髓。这边以Dubbo
中Transporter
层的设计来说说桥梁模式。
这个模式比较隐晦,挺难理解的。什么是抽象,什么是实现?它这里面的抽象指的不是抽象类或者接口。实现也不是指的具体实现类。
我这边来解释下Transporter
层是怎么提现桥梁模式的,声明下:这只是我个人的理解和观点,并非官方给出。
Transporter
层的的抽象是指,Dubbo
抽象了一整套适合Dubbo
的网络传输层的"类库"。比如:看下Transporter
的接口代码。
@SPI("netty")// 默认使用netty服务器
public interface Transporter {
// 抽象出了bind行为,这个行为要完成服务端口暴露的动作,并且返回Server抽象
// 无论netty,mina,grizzly或者其他的一些服务器暴露接口的动作叫啥名字,这边都被抽象成了bind
// Exchange层只需要给URL和handler就可以完成端口暴露的动作
Server bind(URL url, ChannelHandler handler) throws RemotingException;
// 抽象出了connect行为,这个行为要完成客户端与服务端的连接动作,并且返回Client抽象
// 无论netty,mina,grizzly或者其他的一些服务器做客户端连接时叫啥名字,这边都被抽象成了connect
// Exchange层只需要给URL和handler就可以完成端口暴露的动作
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
Dubbo
为每个服务器都做了Transporter
的适配。看下面的类图结构。
除此之外,还有Server
,Client
,EndPoint
等都是Transporter
层做出的抽象。看下:
// 对交互两端的抽象,分别是服务端和客户端。
public interface Endpoint {
URL getUrl();
ChannelHandler getChannelHandler();
InetSocketAddress getLocalAddress();
void send(Object message) throws RemotingException;
void send(Object message, boolean sent) throws RemotingException;
void close();
void close(int timeout);
void startClose();
boolean isClosed();
}
// 对服务端的抽象
public interface Server extends Endpoint, Resetable {
boolean isBound();
Collection<Channel> getChannels();
Channel getChannel(InetSocketAddress remoteAddress);
}
// 对客户端的抽象
public interface Client extends Endpoint, Channel, Resetable {
void reconnect() throws RemotingException;
}
上述所举例子是Transporter
层所提现的抽象,在来看下桥梁模式中的实现。实现指的是跟具体服务器相关的一套类库,分别Netty
,Mina
,Grizzly
各自的类库。
这样的设计完全提现了桥梁模式的定义:将抽象和实现解耦,让它们可以独立变化。
组合模式
模式侧重点:树形结构
不是简单的对象组合关系。
组合模式主要运用在树形结构的场景中。
它是对树形结构的一种对象关系的表达形式。
比如:
1.JDK
中的文件和文件夹的组合表示方式
2.公司人力资源,部分结构,员工之间的组织形式。
这种模式本身并不用去学习或者刻意使用,如果定义好了需求让某个开发人员来设计对象模型,那么他很有可能也会按照这种模式来设计。
其实大量的人并不知道这种树形结构的表现形式被称之为组合模式。而且我感觉这个名字起的一点也不形象。我之前一直认为就是组合委托那套东西。
有了这种关系,我们可以很方便的来做遍历,递归等操作。用起来会很舒服。这就是合理的抽象所带来的价值。
public class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinates;// 下级员工
public Employee(String name, String dept, int salary) {
this.name = name;
this.dept = dept;
this.salary = salary;
subordinates = new ArrayList<>();
}
public void add(Employee e) {
subordinates.add(e);
}
public void remove(Employee e) {
subordinates.remove(e);
}
public List<Employee> getSubordinates() {
return subordinates;
}
}
行为型
模板方法模式
模式侧重点:抽象层模板,定义流程
在抽象层方法中,定义一些列的行为骨架。并且设计好执行顺序(不变的流程)。具体的行为实现,由子类完成。
几乎任何一个框架,任何系统在抽象层都要使用模板方法。因为框架的骨架由实体域,服务域,回话域三大块组成。服务域代表的就是流程,流程的控制基本都得用上模板方法。
public interface ITemplate {
void method1();
void method2();
void method3();
}
public class TemplateImpl implements ITemplate {
@Override
public void method1() {
System.out.println("method1");
}
@Override
public void method2() {
System.out.println("method2");
}
@Override
public void method3() {
System.out.println("method3");
}
}
public class Client {
public static void main(String[] args) {
ITemplate template = new TemplateImpl();
System.out.println("pre");
template.method1();
template.method2();
template.method3();
System.out.println("after");
System.out.println("完成");
}
}
大家都知道的HttpServlet.service就是模板方法的典型应用,看下。
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
doGet(req, resp);// 调用doGet,就是模板
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);// 调用doGet,就是模板
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);// 调用doHead,就是模板
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);// 调用doPost,就是模板
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);// 调用doPut,就是模板
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);// 调用doDelete,就是模板
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);// 调用doOptions,就是模板
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);// 调用doTrace,就是模板
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
还有个一个非常典型的案例,Junit
这个地方基本一看就知道是模板方法。
// TestCase.runBare
public void runBare() throws Throwable {
Throwable exception = null;
setUp();// 方法1
try {
runTest();// 方法2
} catch (Throwable running) {
exception = running;
} finally {
try {
tearDown();// 方法3
} catch (Throwable tearingDown) {
if (exception == null) exception = tearingDown;
}
}
if (exception != null) throw exception;
}
观察者模式
模式侧重点:被观察者与观察者之间的关系
两种角色. 1.观察者. 2.被观察者
被观察这一般会持有一个观察者的列表。当某些相关事件发生之后,循环调用每个观察者触发其update行为。
至于观察者的update行为怎么实现,被观察这不关心。
这也体现了角色之前行为的解耦。他们的唯一耦合是观察者列表。
public interface ISubject {
void register(IObserver observer);
void remove(IObserver observer);
void update();
}
public class ConcreateSubject implements ISubject {
private List<IObserver> observers = new ArrayList<>();
@Override
public void register(IObserver observer) {
observers.add(observer);
}
@Override
public void remove(IObserver observer) {
observers.remove(observer);
}
@Override
public void update() {
observers.forEach(observer -> observer.update(""));
}
}
public interface IObserver {
void update(String message);
}
public class ConcreateObserver implements IObserver {
@Override
public void update(String message) {
System.out.println("被通知了");
}
}
public class Client {
public static void main(String[] args) {
ISubject subject = new ConcreateSubject();
IObserver observer = new ConcreateObserver();
subject.register(observer);
subject.update();
}
}
上面的这种模板式的案例,能让初学者看到一个形状,但其中的思想不是那么明显。我们还是用实际案例来说说。
1.java swing
的按钮就是观察者。按钮是被观察者,当按钮被按下之后触发事件,产生回调。
2.java web
的listener
。比如org.springframework.web.context.ContextLoaderListener
,Spring
启动初始化的Listener
。容器启动时会回调ServletContextListener.contextInitialized
方法。
3.App
消息推送
4.监控告警通知
6.zookeeper
的节点变化通知
责任链模式
模式侧重点:多个过滤或者拦截
过滤器链,拦截器链
public interface IHandler {
boolean doIt();
}
public class Handler1 implements IHandler {
@Override
public boolean doIt() {
System.out.println("handler 1");
return true;
}
}
public class Handler2 implements IHandler {
@Override
public boolean doIt() {
System.out.println("handler 2");
return true;
}
}
public class HandlerChain {
private List<IHandler> handlers = new ArrayList<>();
public void addHandler(IHandler handler) {
handlers.add(handler);
}
public void doHandlers() {
for (IHandler handler : handlers) {
boolean res = handler.doIt();
if (!res) {
break;
}
}
}
}
public class Client {
public static void main(String[] args) {
HandlerChain handlerChain = new HandlerChain();
handlerChain.addHandler(new Handler1());
handlerChain.addHandler(new Handler2());
handlerChain.doHandlers();
}
}
树下应用,挺多的。
① Struts2
的过滤器链
② Java web
的过滤器链
③ SpringMVC
的拦截器链
④ Dubbo
的过滤器链 参考文档 dubbo的filter
策略模式
模式侧重点:策略种类和策略算法本身
在行为级别。把if
else
,switch
替换掉的最好方式。
策略类型的存放,有些人喜欢用static map
,我喜欢用Enum
,我感觉更优雅。
Enum
作为策略类型,实现类的beanId
直接写到Enum
中。今后的可扩展点就是新增或者删除实现类,并且对Enum
作调整。
这边的Enum
就相当于是配置了。
这边举一个简单的案例,比如要去上海旅游,有3中策略:跑去,开车,飞机。来看代码。
public enum TravelStrategyType {
RUN,// 跑去
DRIVE,// 开车
FLY// 飞机
;
}
再提供1个接口和3种策略的实现方式
public interface ITravelStrategy {
void go2shanghai();
}
public class RunTravelStrategy implements ITravelStrategy {
@Override
public void go2shanghai() {
System.out.println("run to shanghai");
}
}
public class DriveTravelStrategy implements ITravelStrategy {
@Override
public void go2shanghai() {
System.out.println("drive to shanghai");
}
}
public class FlyTravelStrategy implements ITravelStrategy {
@Override
public void go2shanghai() {
System.out.println("fly to shanghai");
}
}
再提供一个执行类
public class TravelDispatcher {
public void doTravel(TravelStrategyType type) {
ITravelStrategy strategy = null;
switch (type) {
case RUN:
strategy = new RunTravelStrategy();
case FLY:
strategy = new FlyTravelStrategy();
case DRIVE:
strategy = new DriveTravelStrategy();
}
strategy.go2shanghai();
}
}
看到上面有3种策略,在执行分发器中使用了switch
的方式来判断该用哪种策略,是否有更好的方式呢?有,需要巧妙的使用Enum
。参考文档 优雅代码 - 善用Enum
状态
模式侧重点:状态转移过程
这个模式提现的是一个target状态转移的过程。
比如:新冠肺炎,某人,被传染(event
),得病越来越严重转移到重症状态。被上呼吸机治疗(event),又被治愈,状态转移到正常人。
这里面有几个重点:
1.状态的数量
2.事件
3.转移过程(实际上是被隐含的)
我们设计开发要做的事情,基本上是将状态与事件做乘积处理,最坏的情况是笛卡尔积。
当然在业务边界限定的情况下,会不一样。
迭代器模式
模式侧重点:迭代
两个角色
1.容器(会实现Iterable
接口,实现iterator()
方法来获得Iterator
的具体实现))
2.迭代器抽象层(hasNext
,next
),迭代器实现层(需要根据特定容器来开发)
备忘录模式
模式侧重点:历史记录
历史状态的存储
命令模式
模式侧重点:命令对象本身,还有命令的传递
这个模式有2个角色
1.命令本身 Command
(持有Receiver
的引用)
2.命令接收者 Receiver
(真正的执行者)
中介模式
模式侧重点:中间解耦
这个模式如果从高的概念角度就比较大了
最初的电话交换机就是中介模式
现在的各种平台都是中介
淘宝是卖家与卖家之间的中介
咸鱼是二手货的中介
角色:
1.中介,调停者
2.同事,伙伴(中介服务的对象),可能需要细分。比如买方,买方,比如被告和原告。
这个模式更偏思想层面,中介内部怎么持有双发的引用,并且怎么协调双方的细节,业务倒向占主要因素。代码或者类的关系层面没有特别的要求。