模板方法(Template Method)是一个比较简单的模式。
它的主要思想是,定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现好了,这样不同的子类就可以定义出不同的步骤。
本文将从以下几点对模版方法设计模式进行讨论:
- 模版方法的特点
- 模版方法的使用场景
- 模版方法的实现
- 模版方法的应用
1. 模版方法的特点
- 把子类中不变的逻辑抽离到父类(模版类)中,去除了子类中的重复代码,易于阅读和维护;
- 模版类中的具体实现细节留给子类实现,有助于功能的扩展和维护;
- 模版类的扩展和修改交给子类,符合开闭原则。
2. 使用场景
- 当多个子类具有公用的方法,却执行流程逻辑相同时。可以把核心的算法和重要的功能设计为模板方法,子类去实现相关细节功能;
- 重要的、复杂的方法,且可能有不同的实现,可以考虑作为模板方法。
举个例子🌰:
现在有一个查询的业务,具体的流程很简单,就是调用方法从数据库查询数据。但是可能使用MySQL,也可能使用Oracle或者Postgre SQL等,也可能在查询数据库之前先走一遍缓存等。但是整体流程的动作都是查询数据库,然后拿到数据对其数据做相应的业务操作。那么我们就可以尝试采用模版模式来实现,使用不同的子类各自实现queryData(Condition condition)方法。 ^1e1832
3. 模版方法的实现
经过上面的描述,对其模版方法模式有了一个初步的认识。接下来我们通过两个例子来进一步了解其实现方法。
3.1 案例一
案例场景为 [[设计模式——模版方法#^1e1832 | 2 中举的查询数据库业务的例子]]。
1、先写模版类QueryServiceTemplate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public abstract class QueryServiceTemplate { public Data query(String condition){ Data data = queryFromDB(condition); Data res = doSomething(data); return res; } abstract protected Data queryFromDB(String condition); private Data doSomething(Data data){ return data; } }
|
2、实现通过MySQL的查询
1 2 3 4 5 6 7 8
| public class MySqlQueryService extends QueryServiceTemplate{ @Override public Data queryFromDB(String condition) { return data; } }
|
3、如果要对其扩展,使用Oracle数据库查询呢?
1 2 3 4 5 6 7 8
| public class OracleQueryService extends QueryServiceTemplate{ @Override public Data queryFromDB(String condition) { return data; } }
|
4、具体的使用
1 2 3 4 5 6 7 8 9 10 11
| public class Test { public static void main(String[] args) { QueryServiceTemplate mySqlQueryService = new MySqlQueryService(); Data data = mySqlQueryService.query("id == 1");
OracleQueryService oracleQueryService = new OracleQueryService(); Data query = oracleQueryService.query("id == 1"); } }
|
3.2 案例二
此案例由于是实际项目中的,要相对案例一复杂一点点。案例场景:在简单实现RPC的项目中的负载均衡部分的实现。已知负载均衡的策略有多种,切负载均衡的方案比较多,比如轮训、最短响应时间、一致性哈希等方案。由于业务不是本文的重点,因此不多介绍其业务相关的内容。先看看用例图:

1、同样,第一步先创建模版类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Slf4j public abstract class AbstractLoadBalancer {
private final Map<String, LoadBalanceSelector> SERVICE_SELECTOR_CACHE = new ConcurrentHashMap<>(); public InetSocketAddress selectServiceAddr(String serviceName) { log.debug("Get the node serving: {}", serviceName); LoadBalanceSelector selector = SERVICE_SELECTOR_CACHE.get(serviceName); if (selector == null) { List<InetSocketAddress> inetSocketAddresses = XRpcBootStrap.getInstance().getConfiguration().getRegistry().lookUp(serviceName);
selector = getLoadBalanceSelector(inetSocketAddresses); SERVICE_SELECTOR_CACHE.put(serviceName, selector); }
return selector.getNext(); }
protected abstract LoadBalanceSelector getLoadBalanceSelector(List<InetSocketAddress> inetSocketAddresses); }
|
2、实现轮训负载均衡策略的负载均衡器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @Slf4j public class RoundRobinLoadBalancer extends AbstractLoadBalancer{ @Override protected LoadBalanceSelector getLoadBalanceSelector(List<InetSocketAddress> inetSocketAddresses) { return new RoundRobinSelector(inetSocketAddresses); }
private static class RoundRobinSelector implements LoadBalanceSelector{ private final List<InetSocketAddress> serviceList; private final AtomicInteger idx; public RoundRobinSelector(List<InetSocketAddress> serviceList) { this.serviceList = serviceList; this.idx = new AtomicInteger(0); } @Override public InetSocketAddress getNext() { if(serviceList == null || serviceList.isEmpty()){ log.error("The service list is empty when a node is selected for load balancing"); throw new LoadBalancerException(); } InetSocketAddress inetSocketAddress = serviceList.get(idx.get()); if(idx.get() == serviceList.size() - 1){ idx.set(0); }else{ idx.incrementAndGet(); } return inetSocketAddress; } } }
|
3、调用部分
1 2
| InetSocketAddress addr = conf.getLoadBalancer().selectServiceAddr(interfaceRef.getName());
|
4、如果此时还要扩展一个新的一致性哈希负载均衡器呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Slf4j public class ConsistentHashLoadBalancer extends AbstractLoadBalancer{ @Override protected LoadBalanceSelector getLoadBalanceSelector(List<InetSocketAddress> inetSocketAddresses) { return new ConsistentHashSelector(inetSocketAddresses); } private static class ConsistentHashSelector implements LoadBalanceSelector { @Override public InetSocketAddress getNext() { } } }
|
4. 模版方法的应用
4.1 抽象同步队列——AQS
提到模板方法的应用,首当其冲的就是AQS了。AQS就是典型的模版方法的使用案例。它将大部分的流程都已经实现,只提供了tryAcquire()、tryRelease()、tryAcquireShared()、tryReleaseShared()、isHeldExclusively()几个方法用于子类的实现。ReentrantLock、CountDownLatch、Semaphore等同步组件都是以它为基础实现的。如AQS 中的acquire(int arg)方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } }
|
其中tryAcquire()是抽象方法,即靠子类来实现细节。
4.2 JDK中的IO模块
此模块中用到模版方法的地方有很多,如InputStream、OutputStream、Reader、Writer等。比如在InputStream中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public abstract class InputStream implements Closeable { public abstract int read() throws IOException; public int read(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); if (c == -1) { return -1; } b[off] = (byte)c;
int i = 1; try { for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; } }
|
4.3 集合
在集合模块中,也有很多地方用到了模版方法的设计模式。比如AbstractList。
1 2 3 4 5 6 7 8 9 10 11
| public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { public boolean add(E e) { add(size(), e); return true; }
public void add(int index, E element) { throw new UnsupportedOperationException(); }
}
|
以上的 4.1 和4.3中的例子中,可以发现,模版类中的留给子类实现的方法也可以不作为抽象方法。而是抛出了一个不支持此操作的异常。为什么要这么做呢?
这是因为有时候模版方法定义的一些留给子类实现的方法,不一定要求每个子类都实现。
比如AQS中的一些供共享锁实现的方法,那么如果实现非共享锁的话,就无需再实现一遍,就可以这么操作。如果将其作为抽象方法的话。那么所有子类都必须要实现所有的抽象方法,包括无需实现的。会多出很多无效的代码。这个方法也可以在自己的项目中借鉴。
5. 总结
模板方法模式的一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过目标模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制框架的功能