一、模板模式
模板模式:在方法定义了执行方式or算法的骨架,子类实现具体的步骤。可以在执行架构不变的情况下调整具体的算法实现方式。针对有共同的执行步骤但是具体的执行内容不同的模块是一种很好的封装方式。
例子:
1.Arrays中的sort(T[])方法和Comparable接口的组合方式就是使用了模板模式。
2.如果我们要对某些对象进行排序,是这个类继承Comparable接口实现compareTo(Object object)方法。将需要排序的所有对象封装到一个数组中,然后调用sort方法对这些对象进行排序。
好处:
1.针对固定的执行步骤在超类中进行固化,增加代码重复利用率,并且防止执行步骤被不小心弄混乱导致程序异常。
缺点:
1.使用继承来保证代码的重用性,在弹性和可扩展性方面比不上组合(策略模式)。
二、状态模式
状态模式:当对象内部状态变更时,会跟随状态的变化而改变其行为。
例子:
1.设计一个跨越障碍的游戏只有一个按钮,按一下是跳跃、在跳跃状态下再按一下就是翻滚跳跃能跳的更高。
2.可以把普通跳跃和翻滚跳跃抽象成两个独立状态,并且加入一个落地状态在落地状态情况下能进行一次跳跃。每个状态对象有自己对应的动作并且进行状态间的变更。
3.封装一个按钮类,当按下按钮时转换状态并且执行状态动作。
状态接口
/**
* 状态接口,每个状态都有其对应的状态方法
*/
public interface Status {
void action();
}
跳跃状态
/**
* 第一次按下按钮执行跳跃,并把当前状态转换到翻滚跳跃状态
*/
public class JumpStatus implements Status {
private GameButton gameButton;
public JumpStatus(GameButton gameButton) {
this.gameButton = gameButton;
}
@Override
public void action() {
System.out.println("First time press button.Dumping!");
gameButton.setNowStatus(gameButton.getSecPressStatus());
}
}
翻滚跳跃状态
/**
* 第二次按下按钮进行翻滚跳跃,并把状态转换为null
*/
public class RollJumpStatus implements Status {
private GameButton gameButton;
public RollJumpStatus(GameButton gameButton) {
this.gameButton = gameButton;
}
@Override
public void action() {
System.out.println("Second time press button.Rolling and Dumping!");
gameButton.setNowStatus(null);
}
}
按钮操作
public class GameButton {
private Status firstPressStatus;
private Status secPressStatus;
//当前所属状态
private Status nowStatus = null;
public GameButton() {
firstPressStatus = new JumpStatus(this);
secPressStatus = new RollJumpStatus(this);
}
public synchronized void pressButton() {
/**
* 当按下按钮时并且当前为无状态,状态转换到
* 首次按下按钮并执行
*/
if (nowStatus == null) {
nowStatus = firstPressStatus;
nowStatus.action();
AutoCycleStatus autoCycleStatus = new AutoCycleStatus();
autoCycleStatus.start();
} else {
/**
* 如果为有状态直接执行状态行为,并且会自动转换状态
*/
nowStatus.action();
}
}
/**
* 单首次按下按钮时,并且在100ms内没有第二次按下按钮,
* 主动将按钮重置为无状态
*/
private class AutoCycleStatus extends Thread{
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (nowStatus != null)
setNowStatus(null);
}
}
public Status getFirstPressStatus() {
return firstPressStatus;
}
public Status getSecPressStatus() {
return secPressStatus;
}
public Status getNowStatus() {
return nowStatus;
}
public synchronized void setNowStatus(Status nowStatus) {
this.nowStatus = nowStatus;
}
}
好处:
1.可以通过状态模式来替代根据大量if来判定状态是否变化而根据具体的状态来调用对应方法,是代码更整洁。
2.每个状态对应的行为封装到状态对象中,使类结构根据合理代码根据容易理解。
3.针对可变部分进行了封装,符合对扩展开发,修改封板原则。
缺点:
1.每个状态要对应一个类,增加了程序中类的数目;
补充点:
1.状态模式通常适合固定接口行为但是有多种状态转换的模块进行封装。
2.状态模式看上去和策略模式非常相似,都是封装可变化通过组合来实现其行为。但是策略模式偏重于通过动态方式组合合适的行为对象并调用其方法,状态模式是随着状态的变化来改变其行为对象。
三、迭代器模式:
迭代器模式:通过一种方式顺序访问一群聚合对象,不用暴露这群对象的聚合方式。实现了java.util.Iterator接口的List、Set等集合就有用到迭代器模式。
1.例子java中的集合超类Collection就有用到迭代器模式,继承了java.util.Iterator迭代接口;
2.ArrayList、HashSet、HashTable等作为Connection的子类,都有迭代功能,通过迭代器方式无论是哪类接口都可以通过Iterator进行迭代,在多种集合都需要遍历时可以通过统一的接口进行遍历。
3.创建一个Human对象,有ArrayList、HashSet两个不同类型的集合来存储Human对象,可以需要根据需求能动态的遍历这两类不同集合的Human对象。
Human类
public class Human {
private int age;
private String sex;
public void setAge(int age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public String getSex() {
return sex;
}
}
创建迭代器接口
public interface HumanIterator {
Iterator createIterator();
}
Human的ArrayList集合
public class HumanArrayList implements HumanIterator{
private List humanList;
public HumanArrayList() {
humanList = new ArrayList<>();
}
public void addHuman(Human human) {
humanList.add(human);
}
public void removeHuman(Human human) {
humanList.remove(human);
}
public Iterator createIterator() {
return humanList.iterator();
}
}
human的HashSet集合
public class HumanSet implements HumanIterator{
private Set humanSet;
public HumanSet() {
humanSet = new HashSet<>();
}
public void addHuman(Human human) {
humanSet.add(human);
}
public void removeHuman(Human human) {
humanSet.remove(human);
}
public Iterator createIterator() {
return humanSet.iterator();
}
}
遍历human集合
public class TraverseHuman {
public void traverse(HumanIterator humanIterator) {
Iterator iterator = humanIterator.createIterator();
if (iterator.hasNext()) {
Human human = iterator.next();
System.out.print("Human age:" + human.getAge() + ", sex:" + human.getSex());
}
}
}
好处:
1.当需要对多种不同聚合方式存储的对象群进行遍历时,不需要对每种聚合类型进行判断从而选择与聚合方式对应的遍历方式,只需要通过调用统一的迭代接口方法就可以遍历所有不同聚合方式的聚合对象。减少判断代码,增加扩展性。
四、桥接模式
什么是桥接模式,就是将抽象化部分与它的实现部分分离开来,使它们都可以独立变化。看这个定义是比较抽象难理解的,按我个人的理解用白话来说就是在将现实中的事物抽象成一个接口模型的时候,发现抽象后的接口模型的接口其实可以按不同的演变方向可以分成不同几组,每组都有独立演变方向(举个例子对于一个统一的遥控器模型它的遥控方式演变和其遥控的对象的演变是不是分别独立互不重叠的演化方向),如果按传统的继承来解决这些演化那就是类爆炸了。这时候就该桥接上场了,具体的抽象类的演变和其他独立的演变分离开来,具体的抽象类演变又引用其他独立演变接口,这样把拆分后的不同演变又可以很灵活的组合成一个整体,这就桥接了。
下面看看例子,如果有看过设计模式-优雅代码(一)的朋友会发现又似曾相识的赶脚,先买个关子。
1.首先声明个遥控器的抽象,遥控器用来控制各类不同的品牌不同型号的电视,同时遥控器也有针对不同的的分类。
2.遥控器抽象有controlTV()方法用了控制电视,同时无论何种彩电都支持这个控制。
3.彩电和遥控器都有自己的演化方向,通过桥接能很好的解决这类问题。
遥控器抽象类
public abstract class Control {
private TV tv;
public Control(TV tv) {
this.tv = tv;
}
public abstract void controlTV();
}
电视接口
public interface TV {
void controlTV();
default void playshow() {
System.out.println("start playing TV shows");
}
}
按钮遥控电视
public class ButtonTVControl extends Control {
public ButtonTVControl(TV tv) {
super(tv);
}
@Override
public void controlTV() {
System.out.println("Using button control");
tv.controlTV();
}
}
手机遥控电视
public class PhoneTVControl extends Control {
public PhoneTVControl(TV tv) {
super(tv);
}
@Override
public void controlTV() {
System.out.println("Using phone control");
tv.controlTV();
}
}
海尔电视
public class HairTV implements TV {
@Override
public void controlTV() {
System.out.println("control hair TV");
}
}
小米电视
public class MiTV implements TV {
@Override
public void controlTV() {
System.out.println("control Mi TV");
}
}
使用案例
public class ConsoleTVTest {
@Test
void consoleTest() {
//用手机遥控小米电视
TV miTV = new MiTV();
Control phoneControl = new PhoneTVControl(miTV);
phoneControl.controlTV();
}
}
看完上面的例子是不是感觉很熟悉,与设计模式-优雅代码(一)中的策略模式简直不要太像,其实很多人都会有这个疑问,桥接模式不就是策略模式么,这两者的实现方式确实非常雷同,但是要注意它们的目的其实是不一致的。我大致说下它们的区别:
先从定义来看两个模式之间的区别,策略模式是定义一序列的会变化的算法族,分别独立封装处理,可以根据需求动态替换不同的算法,简单来说就是封装变化、动态替换。桥接模式呢是将抽象化部分与它的实现部分分离开来,使它们都可以独立变化。可以看出策略模式是倾向于将所有可能的变化部分全部独立封装出去,在运行时动态的进行组合,强调动态变化和全部独立,是为了扩展和修改,策略模式也被归类为对象模式的一种。桥接模式呢是将具备有不同演变方向的定义分离出来,让其可以朝着各自的反向进行演变互不干扰,并且使用应用将其与指定的演化关联起来,桥接模式更倾向将不统一的接口合成一个统一的接口,在运行时不太会去更换组合。
同时可以发现对应桥接模式TV和遥控器都有独立的不同演变方式。对于策略模式中的TV是固定不变的,遥控器有多种不同的实现方式。
优点:
1.分离抽象和实现部分,提供了比继承更好的解决方案。
2.提高了系统的扩展性,可以在任务多个不同维度的演变反向直接进行组合,同时可以任意扩展一个维度的演变都不需要改变原有的代码。
缺点:
1.桥接模式会对程序的设计和代码的可读性增加难度。同时在使用上有一定的局限性,要针对有不同演化反向的抽象场景。
五、生成器模式
生成器模式的定义是:将一个复杂的对象的构建和和对象的表示进行分离,在同样的构建过程中可以创建不同的表示对象。
从定义上看,生成器模式也是用来实例化对象和工厂模式的目的是一样,它们有什么区别呢:
1.生成器模式呢是通过多个不同的步骤来创建复杂的对象的。工厂模式呢是通过统一的的一个步骤来创建对象。
2.模式的代码结构是不同的,对于工厂模式有创建者(工厂类)和产品类的概念。对于生成器模式有生成器(创建产品的各个部件)、指导者(使用生成器的类)和产品的概念。
例子:
1.有一个线程池配置类,里面有一系列属性比如,最大线程数、最小线程数、线程允许空闲时间等属性;
2.在创建线程池配置对象时,可以使用Builder来根据不同属性来创建不同的线程池对象。
线程池配置类
public class ThreadPoolConfig {
private final int minThreadNum;
private final int maxThreadNum;
private final long idleTime;
private final String poolName;
private ThreadPoolConfig(Builder builder) {
minThreadNum = builder.minThreadNum;
maxThreadNum = builder.maxThreadNum;
idleTime = builder.idleTime;
poolName = builder.poolName;
}
public int getMinThreadNum() {
return minThreadNum;
}
public int getMaxThreadNum() {
return maxThreadNum;
}
public long getIdleTime() {
return idleTime;
}
public String getPoolName() {
return poolName;
}
public static class Builder {
private int minThreadNum = 1;
private int maxThreadNum = 1;
private long idleTime = 60000;
private String poolName = "default";
public Builder minThreadNum(int minThreadNum) {
this.minThreadNum = minThreadNum;
return this;
}
public Builder maxThreadNum(int maxThreadNum) {
this.maxThreadNum = maxThreadNum;
return this;
}
public Builder idleTime(long idleTime) {
this.idleTime = idleTime;
return this;
}
public Builder poolName(String poolName) {
this.poolName = poolName;
return this;
}
public ThreadPoolConfig build() {
return new ThreadPoolConfig(this);
}
}
}
测试类
public class PoolConfigTest {
@Test
void buildTest() {
ThreadPoolConfig config = new ThreadPoolConfig.Builder().idleTime(3000)
.poolName("ThreadConfigPool").build();
Assert.assertEquals(config.getIdleTime(), 3000);
}
}
优点:
1.针对复杂的对象的创建过程,将其封装起来客户无需知道其内部的实现。
2.可以灵活的根据需求来创建不同的对象。
缺点:
1.需要对要创建对象的类了解更加透彻才能灵活的使用。并且只能生产同一类的对象。
六、蝇量模式
蝇量模式:针对拥有相同细粒度的大量对象,通过共享来支持这些对象来减少创建对象的数量。在高吞吐or高并发的程序中,往往充斥着大量的各类短生命周期的对象,这些对象的创建和销毁占用了大量的系统资源和增加了垃圾回收的频率。往往针对垃圾回收和系统资源的优化,减少大量的短生命周期对象有显著的效果。
蝇量模式的的使用在于区分共享变量(内部状态)和不共享变量(外部状态,由外部传入,可变的)。
什么时候适合使用蝇量模式呢,当一个类有许多实例,这些实例能被同一个方法控制的时候就可以使用。
例子:
1.TV又出现了,使用TV作为需要大量创建的拥有相同细粒度的对象,有非共享变量电视品牌(brand)、分辨率(dpi)、尺寸(size)。拥有共享的变量查询彩电信息方法showTV。
2.将变量电视品牌(brand)、分辨率(dpi)、尺寸(size)剥离出来交给TVManager管理。
TV类
public class TV {
public void showTV(String brand, String dpi, int size) {
System.out.println("brand:" + brand + ",dpi:" + dpi + ",size:" + size);
}
}
TV对象管理类
public class TVManager {
private Map> tvPropertyMap = new TreeMap<>();
private TV tv = new TV();
public void addTVProperty(String tvId, String brand, String dpi, int size) {
List
优点:
1.减少运行时对象数量,从而减少JVM堆内存的使用,同时减轻垃圾回收压力;
2.对对象进行集中管理。
缺点:
1.将对象的属性固定了,对于单个对象无法拥有独立而不同的行为。