
1.创建型模式
① 创建型模式的作用就是创建对象,说到创建一个对象,最熟悉的就是 new 一个对象,然后 set 相关属性
② 但是很多场景下,需要给客户端提供更加友好的创建对象的方式,尤其是那种我们定义了类,但是需要提供给其他开发者用的时候
工厂方法模式
public class FoodFactory {
public static Food makeFood(String name) {
if (name.equals("noodle")) {
Food noodle = new NoodleFood();
noodle.addSpicy("more");
return noodle;
} else if (name.equals("rice")) {
Food rice = new RiceFood();
rice.addDosage("bowl");
return rice;
} else {
return null;
}
}
}
① NoodleFood 和 RiceFood 都继承自 Food
② 简单地说,简单工厂模式通常就是这样,一个工厂类 XxxFactory,里面有一个静态方法,根据不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象
③ 强调职责单一原则,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种
抽象工厂模式
① 围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂
② 提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类
参考地址:抽象工厂
单例模式
// 饿汉式
public class Singleton {
// 首先,将 new Singleton() 堵死
private Singleton() {};
// 创建私有静态实例,意味着这个类第一次使用的时候就会进行创建
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
// 如果里面写 static 方法,会怎么样?
}
// 懒汉式
public class Singleton {
// 首先,也是先堵死 new Singleton() 这条路
private Singleton() {}
// 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
// 加锁
synchronized (Singleton.class) {
// 这一次判断也是必须的,不然会有并发问题
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 内部类
public class Singleton {
private Singleton() {}
// 主要是使用内部类可以访问外部类的静态属性和静态方法 的特性
private static class Holder {
private static Singleton3 instance = new Singleton3();
}
public static Singleton getInstance() {
return Holder.instance;
}
}
// 通过枚举实现单例
① 单例模式就是只初始化一个对象
② 枚举很特殊,在类加载的时候会初始化里面的所有的实例,而且 JVM 保证不会再被实例化
建造者模式
Food food = new FoodBuilder().type().taste().price().build();
Food food = new NoodleFood().setType().setTaste().setPrice();
① 类似于 XxxBuilder 的类,通常都是建造者模式的产物,建造者模式其实有很多的变种,但是对于客户端来说,使用通常都是一个模式的
② 建造者模式的链式写法观赏性高,可以使用 lombok 的 @Builder 注解实现
③ lombok 还提供 set 方式的写法,所有的 setter 方法都让其 return this,注解 @Accessors(chain = true)
原型模式
① 有一个原型实例,基于这个原型实例产生新的实例,也就是克隆
② Object 类中有一个 clone() 方法,它用于生成一个新的对象,ava 要求类必须先实现 Cloneable 接口;接口没有定义任何方法,但是不这么做的话,在 clone() 的时候,会抛出 CloneNotSupportedException 异常
③ java 的克隆是浅克隆,碰到对象引用的时候,克隆出来的对象和原对象中的引用将指向同一个对象;通常实现深克隆的方法是将对象进行序列化,然后再进行反序列化
创建型模式总结

2.结构型模式
结构型模式旨在通过改变代码结构来达到解耦的目的,使得代码容易维护和扩展
代理模式
// 接口
public interface BasketballService {
String play(String str);
}
// 实现类
public class BasketballServiceImpl implements BasketballService {
public String play(String str) {
System.out.println(str);
return str;
}
}
// 代理类的实现
public class BasketballServiceProxy implements BasketballService {
// 实例化实现类
private BasketballService basketballService = new BasketballServiceImpl();
public String play(String str) {
System.out.println("唱、跳、rap");
return basketballService.play();
}
}
// 这里用代理类来实例化
BasketballService basketballService = new BasketballServiceProxy();
basketballService.play("打篮球");
① 最常使用的模式之一,通过代理隐藏具体实现类的实现细节,通常在业务实现的前后添加一部分逻辑,例如:Spring AOP
② 代理模式的实现通常用动态代理的方式实现,JdkDynamic 和 Cglib 两种方式
③ JDK 中的代理对象的生成:java.lang.reflect.Proxy
适配器模式
① 定义:适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题
② 主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式
桥接模式
处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展在抽象层建立关联
// 电脑品牌
public interface Brand {
void sale();
}
// 创建电脑牌子
class Lenovo implements Brand {
@Override
public void sale() {
System.out.println("出售联想");
}
}
class Dell implements Brand {
@Override
public void sale() {
System.out.println("出售戴尔");
}
}
class Shenzhou implements Brand {
@Override
public void sale() {
System.out.println("出售神州");
}
}
// 电脑类型 + 获得品牌的引用
public class Computer {
protected Brand brand;
public Computer(Brand b) {
this.brand = b;
}
public void sale(){
brand.sale();
}
}
// 创建电脑类型
class Desktop extends Computer {
public Desktop(Brand b) {
super(b);
}
@Override
public void sale() {
super.sale();
System.out.println("出售台式电脑");
}
}
class Laptop extends Computer {
public Laptop(Brand b) {
super(b);
}
@Override
public void sale() {
super.sale();
System.out.println("出售笔记本");
}
}
public class Client {
public static void main(String[] args) {
// 这样就笔记轻松的获得牌子 + 类型
Computer c = new Laptop(new Lenovo());
c.sale();
Computer c2 = new Desktop(new Shenzhou());
c2.sale();
}
}
桥接模式总结
① 桥接模式可以取代多层继承的方案,多层继承违背了单一职责原则,复用性较差,类的个数也非常多;桥接模式可以极大的减少子类的个数,从而降低管理和维护的成本
② 桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统,符合开闭原则
装饰模式
通过继承和组合的方式,给一个对象添加行为,虽然使用继承能够很好拥有父类的行为,但是它存在几个缺陷:
① 对象之间的关系复杂的话,系统变得复杂不利于维护。
② 容易产生“类爆炸”现象。
③ 是静态的
// 饮料抽象类
public abstract class Beverage {
// 返回描述
public abstract String getDescription();
// 返回价格
public abstract double cost();
}
// 红茶
public class BlackTea extends Beverage {
public String getDescription() {
return "红茶";
}
public double cost() {
return 10;
}
}
// 绿茶
public class GreenTea extends Beverage {
public String getDescription() {
return "绿茶";
}
public double cost() {
return 11;
}
}
// 调料
public abstract class Condiment extends Beverage {
}
// 柠檬调料
public class Lemon extends Condiment {
private Beverage bevarage;
// 这里很关键,需要传入具体的饮料,如需要传入没有被装饰的红茶或绿茶,
// 当然也可以传入已经装饰好的芒果绿茶,这样可以做芒果柠檬绿茶
public Lemon(Beverage bevarage) {
this.bevarage = bevarage;
}
public String getDescription() {
// 装饰
return bevarage.getDescription() + ", 加柠檬";
}
public double cost() {
// 装饰
return beverage.cost() + 2; // 加柠檬需要 2 元
}
}
// 芒果调料
public class Mango extends Condiment {
private Beverage bevarage;
public Mango(Beverage bevarage) {
this.bevarage = bevarage;
}
public String getDescription() {
return bevarage.getDescription() + ", 加芒果";
}
public double cost() {
return beverage.cost() + 3; // 加芒果需要 3 元
}
}
// 客户端调用
public static void main(String[] args) {
// 首先,我们需要一个基础饮料,红茶、绿茶或咖啡
Beverage beverage = new GreenTea();
// 开始装饰
beverage = new Lemon(beverage); // 先加一份柠檬
beverage = new Mongo(beverage); // 再加一份芒果
System.out.println(beverage.getDescription() + " 价格:¥" + beverage.cost());
//"绿茶, 加柠檬, 加芒果 价格:¥16"
}
总结
① 装饰模式就是把其他的一些对象作为属性添加到对象中,就像用其他类作为属性装饰目标类
② 这样目标类获取其他类就可以不用继承的方式,获取其他对象相当于获取对象的属性
门面模式
① 门面模式(也叫外观模式,Facade Pattern)在许多源码中有使用
② 类与类之间的耦合越低,可复用性就越好,两个类不需要通信,两个类就不需要有关联关系
③ 如果需要调用里面的方法,可以通过第三者来转发调用
④ 门面模式提供统一的接口,访问子系统,让一个应用程序中子系统间的相互依赖关系减少到最少
⑤ 通过使用门面模式,使得客户端对子系统的引用变得简单,实现客户与子系统之间的松耦合,但违背了"开闭原则",因为增加新的子系统可能需要修改外观类或客户端的源代码
组合模式
① 组合模式组合多个对象形成树形结构以表示"整体-部分"的结构层次
② 定义如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理
③ 在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口,这就是组合模式能够将叶子节点和对象节点进行一致处理的原因
public class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinates; // 下属
public Employee(String name,String dept, int sal) {
this.name = name;
this.dept = dept;
this.salary = sal;
subordinates = new ArrayList<Employee>();
}
public void add(Employee e) {
subordinates.add(e);
}
public void remove(Employee e) {
subordinates.remove(e);
}
public List<Employee> getSubordinates(){
return subordinates;
}
public String toString(){
return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary+" ]");
}
}
享元模式
① 享元模式(Flyweight Pattern)的理解就是复用的意思,翻译的不太友好
② 复用对象最简单的方式是,用一个 HashMap 来存放每次新生成的对象,每次需要一个对象的时候,先到 HashMap 中看看有没有,如果没有,再生成新的对象,然后将这个对象放入 HashMap 中
结构型模式总结

3.行为型模式
各个类之间的相互作用,将职责划分清楚
策略模式
① 一件事可以有多种方式来实现它,然后返回相同的结果,这就是策略模式
② 策略模式中它将这些解决问题的方法定义成一个算法群,每一个方法都对应着一个具体的算法,这里的一个算法称之为一个策略
③ 对于策略的选择需要客户端来做,客户端必须要清楚的知道每个算法之间的区别和在什么时候什么地方使用什么策略是最合适的,这样就增加客户端的负担
④ 策略模式完美符合"开闭原则",用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为,但是一个策略对应一个类将会是系统产生很多的策略类
// 策略接口
public interface Strategy {
public void draw(int radius, int x, int y);
}
// 策略实现
public class RedPen implements Strategy {
@Override
public void draw(int radius, int x, int y) {
System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
}
}
public class GreenPen implements Strategy {
@Override
public void draw(int radius, int x, int y) {
System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
}
}
public class BluePen implements Strategy {
@Override
public void draw(int radius, int x, int y) {
System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
}
}
// 策略上下文
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeDraw(int radius, int x, int y){
return strategy.draw(radius, x, y);
}
}
// 客户端调用
public static void main(String[] args) {
Context context = new Context(new BluePen());
context.executeDraw(10, 0, 0);
}
观察者模式
① 观察者模式定义对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新
② 发生改变的对象称之为观察目标,而被通知的对象称之为观察者
③ 一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展
④ 观察者提供一种对象设计,让主题和观察者之间以松耦合的方式结合
⑤ jdk 提供相似的支持,具体可以参考 java.util.Observable 和 java.util.Observer 这两个类
⑥ 如果要实现单机观察者模式,可以使用 Guava 中的 EventBus,有同步实现也有异步实现
// 主题 (观察目标)
public class Subject {
// 观察者
private List<Observer> observers = new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
// 数据已变更,通知观察者们
notifyAllObservers();
}
// 注册观察者
public void attach(Observer observer) {
observers.add(observer);
}
// 通知观察者们
public void notifyAllObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// 观察者接口
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
// 观察者实现 1
public class BinaryObserver extends Observer {
// 在构造方法中进行订阅主题
public BinaryObserver(Subject subject) {
this.subject = subject;
// 通常在构造方法中将 this 发布出去的操作一定要小心
this.subject.attach(this);
}
// 该方法由主题类在数据变更的时候进行调用
@Override
public void update() {
String result = Integer.toBinaryString(subject.getState());
System.out.println("订阅的数据发生变化,新的数据处理为二进制值为:" + result);
}
}
// 观察者实现 2
public class HexaObserver extends Observer {
public HexaObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
String result = Integer.toHexString(subject.getState()).toUpperCase();
System.out.println("订阅的数据发生变化,新的数据处理为十六进制值为:" + result);
}
}
// 客户端调用
public static void main(String[] args) {
// 先定义一个主题
Subject subject1 = new Subject();
// 定义观察者
new BinaryObserver(subject1);
new HexaObserver(subject1);
// 模拟数据变更,这个时候,观察者们的 update 方法将会被调用
subject.setState(11);
}
// 可以看到有调用到观察者
责任链模式
① 责任链模式描述的请求如何沿着对象所组成的链来传递的
② 将对象组成一条链,发送者将请求发给链的第一个接收者,并且沿着这条链传递,直到有一个对象来处理它或者直到最后也没有对象处理而留在链末尾端
③ 比如流程审批就是一个很好的例子,只要终端用户提交申请,根据申请的内容信息,自动建立一条责任链,然后就可以开始流转了
④ 避免请求发送者与接收者耦合在一起,使得每一个对象都有可能来处理请求,从而实现了请求的发送者和接收者之间的解耦
// 有这么一个场景,用户参加一个活动可以领取奖品,但是活动需要进行很多的规则校验然后才能放行,比如首先需要校验用户是否是新用户、今日参与人数是否有限额、全场参与人数是否有限额等等。设定的规则都通过后,才能让用户领走奖品
// 责任链基类
public abstract class RuleHandler {
// 后继节点
protected RuleHandler successor;
public abstract void apply(Context context);
public void setSuccessor(RuleHandler successor) {
this.successor = successor;
}
public RuleHandler getSuccessor() {
return successor;
}
}
// 检测是否是新用户
public class NewUserRuleHandler extends RuleHandler {
public void apply(Context context) {
if (context.isNewUser()) {
// 如果有后继节点的话,传递下去
if (this.getSuccessor() != null) {
this.getSuccessor().apply(context);
}
} else {
throw new RuntimeException("该活动仅限新用户参与");
}
}
}
// 检验所在区域能够参加
public class LocationRuleHandler extends RuleHandler {
public void apply(Context context) {
boolean allowed = activityService.isSupportedLocation(context.getLocation);
if (allowed) {
if (this.getSuccessor() != null) {
this.getSuccessor().apply(context);
}
} else {
throw new RuntimeException("非常抱歉,您所在的地区无法参与本次活动");
}
}
}
// 检验奖品是否已经领完
public class LimitRuleHandler extends RuleHandler {
public void apply(Context context) {
int remainedTimes = activityService.queryRemainedTimes(context); // 查询剩余奖品
if (remainedTimes > 0) {
if (this.getSuccessor() != null) {
this.getSuccessor().apply(userInfo);
}
} else {
throw new RuntimeException("您来得太晚了,奖品被领完了");
}
}
}
// 客户端调用
public static void main(String[] args) {
RuleHandler newUserHandler = new NewUserRuleHandler();
RuleHandler locationHandler = new LocationRuleHandler();
RuleHandler limitHandler = new LimitRuleHandler();
// 假设本次活动仅校验地区和奖品数量,不校验新老用户
locationHandler.setSuccessor(limitHandler);
locationHandler.apply(context);
}
模板方法模式
① 模板方法模式就是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中;模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
② 模板方法模式就是基于继承的代码复用技术的;在模板方法模式中,可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中
// 抽象类
public abstract class AbstractTemplate {
// 这就是模板方法
public void templateMethod() {
init();
apply(); // 这个是重点
end(); // 可以作为钩子方法
}
protected void init() {
System.out.println("init 抽象层已经实现,子类也可以选择覆写");
}
// 留给子类实现
protected abstract void apply();
protected void end() {
}
}
// 实现类
public class ConcreteTemplate extends AbstractTemplate {
public void apply() {
System.out.println("子类实现抽象方法 apply");
}
public void end() {
System.out.println("我们可以把 method3 当做钩子方法来使用,需要的时候覆写就可以了");
}
}
// 客户端调用
public static void main(String[] args) {
AbstractTemplate t = new ConcreteTemplate();
// 调用模板方法
t.templateMethod();
}
状态模式
① 对象的行为依赖于它的一个或者多个变化的属性,这些可变的属性我们称之为状态,也就是说行为依赖状态
② 当该对象因为在外部的互动而导致他的状态发生变化,从而它的行为也会做出相应的变化
③ 对于这种情况,我们是不能用行为来控制状态的变化,而应该站在状态的角度来思考行为,即是什么状态就要做出什么样的行为,这个就是状态模式
// 需求:商品库存中心有个最基本的需求是减库存和补库存
// 定义状态接口
public interface State {
public void doAction(Context context);
}
// 定义减库存状态
public class DeductState implements State {
public void doAction(Context context) {
System.out.println("商品卖出,准备减库存");
context.setState(this);
//... 执行减库存的具体操作
}
public String toString() {
return "Deduct State";
}
}
// 定义补库存状态
public class RevertState implements State {
public void doAction(Context context) {
System.out.println("给此商品补库存");
context.setState(this);
//... 执行加库存的具体操作
}
public String toString() {
return "Revert State";
}
}
// Context 类的定义
public class Context {
private State state;
private String name;
public Context(String name) {
this.name = name;
}
public void setState(State state) {
this.state = state;
}
public void getState() {
return this.state;
}
}
// 客户端端调用
public static void main(String[] args) {
// 我们需要操作的是 iPhone X
Context context = new Context("iPhone X");
// 看看怎么进行补库存操作
State revertState = new RevertState();
revertState.doAction(context);
// 同样的,减库存操作也非常简单
State deductState = new DeductState();
deductState.doAction(context);
// 如果需要我们可以获取当前的状态
// context.getState().toString();
}
迭代子模式
① 对于迭代在编程过程中我们经常用到,能够游走于聚合内的每一个元素,同时还可以提供多种不同的遍历方式,这就是迭代器模式的设计动机
② 在我们实际的开发过程中,我们可能会需要根据不同的需求以不同的方式来遍历整个对象,但是我们又不希望在聚合对象的抽象接口中充斥着各种不同的遍历操作,于是我们就希望有某个东西能够以多种不同的方式来遍历一个聚合对象,这时迭代器模式出现了
备忘录模式
① 备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态
② 它实现了对信息的封装,使得客户不需要关心状态保存的细节
③ 保存就要消耗资源,所以备忘录模式的缺点就在于消耗资源。
④ 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存
访问者模式
① 在我们软件开发中我们可能会对同一个对象有不同的处理,如果我们都做分别的处理,将会产生灾难性的错误
② 访问者模式即表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作
③ 访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变
④ 访问者模式是适用于那些数据结构比较稳定的,因为他是将数据的操作与数据结构进行分离了,如果某个系统的数据结构相对稳定,但是操作算法易于变化的话,就比较适用适用访问者模式
中介者模式
① 和房地产的中介类似,购买者和被购买者不需要进行直接交互,可以通过中介进行交互
② 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
③ 在中介者模式中,各个对象不需要互相知道了解,他们只需要知道中介者对象即可,但是中介者对象就必须要知道所有的对象和他们之间的关联关系,正是因为这样就导致了中介者对象的结构过于复杂,承担了过多的职责,同时它也是整个系统的核心所在,它有问题将会导致整个系统的问题
解释器模式
① 所谓解释器模式就是定义语言的文法,并且建立一个解释器来解释该语言中的句子
② 解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中
③ 它描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子
命令模式
① 有些时候我们想某个对象发送一个请求,但是我们并不知道该请求的具体接收者是谁,具体的处理过程是如何的,我们只知道在程序运行中指定具体的请求接收者即可,对于这样将请求封装成对象的我们称之为命令模式
② 命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,同时命令模式支持可撤销的操作
③ 命令模式可以将请求的发送者和接收者之间实现完全的解耦,发送者和接收者之间没有直接的联系,发送者只需要知道如何发送请求命令即可,其余的可以一概不管,甚至命令是否成功都无需关心
④ 同时我们可以非常方便的增加新的命令,但是可能就是因为方便和对请求的封装就会导致系统中会存在过多的具体命令类
行为型模式总结

参考地址
- 23中设计模式-csdn
- https://www.javadoop.com/post/design-pattern