浅谈模式 - 汇总篇

本篇只是总结下各个设计模式要表达的核心思想。算是我的笔记。

创建型

单例模式

模式侧重点:单实例

对于某个类,在进程级别只允许一个实例存在。当然还有多进程间共享单例(一般也不常用)或者线程级别的单例。

实现方式:

① 恶汉式(最常见,最简单)

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,直接使用SpringSingleton即可。除非要是技术框架开发,那就要看实际情况了。

工场模式

模式侧重点:创建对象的类型

简单工场(使用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

DubboSpring中都有一些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

DubboHandler机制包装非常复杂,可以详细看下。下面给出一张图,供参考。具体的包装过程看文章 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中运用的实际案例。Codec2Dubbo的编码和反编码模块。目前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;
}

DubboCodec2下面设置了一个适配器类CodecAdapterCodecAdapter会持有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来聊,我觉得无法学到它的精髓。这边以DubboTransporter层的设计来说说桥梁模式。

这个模式比较隐晦,挺难理解的。什么是抽象,什么是实现?它这里面的抽象指的不是抽象类或者接口。实现也不是指的具体实现类。

我这边来解释下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的适配。看下面的类图结构。

除此之外,还有ServerClientEndPoint等都是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层所提现的抽象,在来看下桥梁模式中的实现。实现指的是跟具体服务器相关的一套类库,分别NettyMinaGrizzly各自的类库。

这样的设计完全提现了桥梁模式的定义:将抽象和实现解耦,让它们可以独立变化。

组合模式

模式侧重点:树形结构

不是简单的对象组合关系。

组合模式主要运用在树形结构的场景中。

它是对树形结构的一种对象关系的表达形式。

比如:

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 weblistener。比如org.springframework.web.context.ContextLoaderListenerSpring启动初始化的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 elseswitch替换掉的最好方式。

策略类型的存放,有些人喜欢用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.迭代器抽象层(hasNextnext),迭代器实现层(需要根据特定容器来开发)

备忘录模式

模式侧重点:历史记录

历史状态的存储

命令模式

模式侧重点:命令对象本身,还有命令的传递

这个模式有2个角色

1.命令本身 Command(持有Receiver的引用)

2.命令接收者 Receiver(真正的执行者)

中介模式

模式侧重点:中间解耦

这个模式如果从高的概念角度就比较大了

最初的电话交换机就是中介模式

现在的各种平台都是中介

淘宝是卖家与卖家之间的中介

咸鱼是二手货的中介

角色:

1.中介,调停者

2.同事,伙伴(中介服务的对象),可能需要细分。比如买方,买方,比如被告和原告。

这个模式更偏思想层面,中介内部怎么持有双发的引用,并且怎么协调双方的细节,业务倒向占主要因素。代码或者类的关系层面没有特别的要求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值