Java设计模式
设计模式的七大原则
*本节全部代码均可在这里找到
单一职责原则
先来看一个小案例
public class SingleResponsibility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("motor");
vehicle.run("car");
vehicle.run("plane");
}
}
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle + " is running on the road...");
}
}
在本设计中,违反了单一职责原则
解决方式:根据交通工具运行的不同方式分解为不同的类
public class SingleResponsibility2 {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("motor");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("plane");
WaterVehicle waterVehicle = new WaterVehicle();
waterVehicle.run("submarine");
}
}
class RoadVehicle{
public void run(String vehicle) {
System.out.println(vehicle + " is running on the road...");
}
}
class AirVehicle{
public void run(String vehicle) {
System.out.println(vehicle + " is running in the air...");
}
}
class WaterVehicle{
public void run(String vehicle) {
System.out.println(vehicle + " is running in the water...");
}
}
方案二遵守了单一模式原则,但是改动很大。
由此,给出方案三:
public class SingleResponsibility3 {
public static void main(String[] args) {
Vehicle3 vehicle3 = new Vehicle3();
vehicle3.run("motor");
vehicle3.runWater("submarine");
vehicle3.runAir("plane");
}
}
class Vehicle3 {
public void run(String vehicle) {
System.out.println(vehicle + " is running on the road...");
}
public void runAir(String vehicle) {
System.out.println(vehicle + " is running in the air...");
}
public void runWater(String vehicle) {
System.out.println(vehicle + " is running in the water...");
}
}
方式三没有对原来的类做大的修改,只是增加了方法。方式三虽然没有在类的级别上遵守单一职责原则,但在方法的级别上遵守单一职责原则。
单一职责原则的注意事项和细节
- 降低类的复杂度,一个类只负责一项职责
- 提高类的可读性,可维护性
- 降低变更带来的风险
- 通常情况下应当遵守单一职责原则,仅当逻辑足够简单,类中方法足够少的时候才可以在方法级别保持单一职责原则
接口隔离原则
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小接口上
错误示例
public class Segregation1 {
}
interface Interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
//A类依赖Interface中的123三个方法
class A {
public void depend1(Interface1 interface1){
interface1.operation1();
}
public void depend2(Interface1 interface1){
interface1.operation2();
}
public void depend3(Interface1 interface1){
interface1.operation3();
}
}
//C类依赖Interface中的145三个方法
class C {
public void depend1(Interface1 interface1){
interface1.operation1();
}
public void depend4(Interface1 interface1){
interface1.operation4();
}
public void depend5(Interface1 interface1){
interface1.operation5();
}
}
class B implements Interface1{
@Override
public void operation1() {
System.out.println("B implements operation1");
}
@Override
public void operation2() {
System.out.println("B implements operation2");
}
@Override
public void operation3() {
System.out.println("B implements operation3");
}
@Override
public void operation4() {
System.out.println("B implements operation4");
}
@Override
public void operation5() {
System.out.println("B implements operation5");
}
}
class D implements Interface1{
@Override
public void operation1() {
System.out.println("D implements operation1");
}
@Override
public void operation2() {
System.out.println("D implements operation2");
}
@Override
public void operation3() {
System.out.println("D implements operation3");
}
@Override
public void operation4() {
System.out.println("D implements operation4");
}
@Override
public void operation5() {
System.out.println("D implements operation5");
}
}
类图
可以看出A仅用到了B的123方法,但B还是需要实现45方法,故应将接口Interface1拆分为三个接口。
修改过后的代码
public class Segregation2 {
public static void main(String[] args) {
A2 a2 = new A2();
//a通过接口去依赖b
a2.depend1(new B2());
a2.depend2(new B2());
a2.depend3(new B2());
}
}
interface Interface2_1{
void operation1();
}
interface Interface2_2{
void operation2();
void operation3();
}
interface Interface2_3{
void operation4();
void operation5();
}
//A类依赖Interface中的123三个方法
class A2 {
public void depend1(Interface2_1 interface1){
interface1.operation1();
}
public void depend2(Interface2_2 interface2){
interface2.operation2();
}
public void depend3(Interface2_2 interface2){
interface2.operation3();
}
}
//C类依赖Interface中的145三个方法
class C2 {
public void depend1(Interface2_1 interface1){
interface1.operation1();
}
public void depend4(Interface2_3 interface3){
interface3.operation4();
}
public void depend5(Interface2_3 interface3){
interface3.operation5();
}
}
class B2 implements Interface2_1, Interface2_2{
@Override
public void operation1() {
System.out.println("B implements operation1");
}
@Override
public void operation2() {
System.out.println("B implements operation2");
}
@Override
public void operation3() {
System.out.println("B implements operation3");
}
}
class D2 implements Interface2_1, Interface2_3{
@Override
public void operation1() {
System.out.println("D implements operation1");
}
@Override
public void operation4() {
System.out.println("D implements operation4");
}
@Override
public void operation5() {
System.out.println("D implements operation5");
}
}
依赖倒转原则
- 高层模块不应该依赖于低层模块,二者都应该依赖于其抽象
- 抽象不应该依赖于细节,细节应当依赖抽象
- 依赖倒转的中心思想是面向接口编程
- 依赖倒转原则是基于相对于细节的多变性抽象的东西要稳定很多。以抽象为基础的架构比以细节为基础的架构稳定很多
- 使用接口或抽象的目的是制定好规范,而不涉及任何具体的操作,把展现细节交给他们的实现类去完成
错误示例
public class DependencyInversion {
public static void main(String[] args) {
}
}
class Email{
public String getInfo(){
return "Email: get info...";
}
}
//receive直接依赖于Email类
//若要接受其他消息则只能增加新类和新方法
class Person{
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
改进后的代码
//修改1中的代码,增加了一个IReceiver,表示接收者接口,email,wechat均属于IReceiver
//Person仅需要与IReceiver建立连接即可
public class DependencyInversion2 {
public static void main(String[] args) {
new Person2().receive(new Email2());
new Person2().receive(new WeChat());
}
}
interface IReceiver{
String getInfo();
}
class Email2 implements IReceiver{
@Override
public String getInfo() {
return "Email: hello...";
}
}
class WeChat implements IReceiver{
@Override
public String getInfo() {
return "WeChat: hello...";
}
}
class Person2{
public void receive(IReceiver receiver){
System.out.println(receiver.getInfo());
}
}
依赖关系传递的三种方式
- 接口传递
- 构造方法传递
- setter方式传递
注意事项和细节
- 低层模块尽量都要有抽象类或接口,或者两者均有,程序稳定性更好
- 变量的声明类型尽量是抽象类或接口,这样在使用变量引用和实际对象之间就存在一个缓冲层,利于程序的扩展和优化
- 继承时遵循里氏替换原则
李氏替换原则
继承时需要注意的问题和规范
继承给程序带来了一些侵入性,增加了对象之间的耦合性,如果一个类被其它类所继承,当这个类需要修改时必须考虑到对子类造成的影响。
基本介绍
- 所有引用基类的地方必须能透明的使用其子类对象
- 在使用继承时,遵循李氏替换原则,在子类中尽量不要重写父类方法
- 继承实际上让两个类的耦合性增加了,在适当的情况下,可以通过聚合,组合,依赖来解决问题
public class Liskov {
public static void main(String[] args) {
A a = new A();
System.out.println("11 - 3 = " + a.func1(11, 3));
System.out.println("1 - 8 = " + a.func1(1, 8));
System.out.println("-------------------lalala---------------------");
B b = new B();
System.out.println("11 - 3 = " + b.func1(11, 3));
System.out.println("1 - 8 = " + b.func1(1, 8));
}
}
class A {
public int func1(int num1, int num2) {
return num1 - num2;
}
}
class B extends A {
public int func1(int num1, int num2) {
return num1 + num2;
}
public int func2(int num1, int num2) {
return func1(num1, num2) + 9;
}
}
本段代码中由于B重写了A中的方法而导致程序可能出现错误,为了解决这个问题,我们通常会把其中共有的部分抽取出来形成一个更一般的基类,原来的两个类共同继承这个类。
开闭原则
基本介绍
- 最基本,最重要的设计原则
- 一个软件实体应对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
- 编程中遵循其他原则,以及使用设计模式的目的最终均是为了遵循开闭原则
错误示例
public class OCP {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawCircle(new Circle());
graphicEditor.drawRectangle(new Rectangle());
}
}
class GraphicEditor {
public void drawShape(Shape s) {
if (s.m_type == 1) {
drawRectangle(s);
} else if (s.m_type == 2) {
drawCircle(s);
}
}
public void drawRectangle(Shape r) {
System.out.println("Rectangle...");
}
public void drawCircle(Shape r) {
System.out.println("Circle...");
}
}
class Shape {
int m_type;
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
违反了OCP原则,当我们新增一种图形,如三角形时,需要较多的改动
以下通过OCP对代码进行改动
public class OCP2 {
public static void main(String[] args) {
GraphicEditor2 graphicEditor2 = new GraphicEditor2();
graphicEditor2.drawShape(new Circle2());
graphicEditor2.drawShape(new Rectangle2());
}
}
abstract class Shape2{
int m_type;
public abstract void draw();
}
class GraphicEditor2 {
public void drawShape(Shape2 s) {
s.draw();
}
}
class Rectangle2 extends Shape2 {
Rectangle2() {
super.m_type = 1;
}
@Override
public void draw() {
System.out.println("Rectangle...");
}
}
class Circle2 extends Shape2 {
Circle2() {
super.m_type = 2;
}
@Override
public void draw() {
System.out.println("Circle...");
}
}
迪米特法则
基本介绍
- 一个对象应该对其他对象保持最少的了解
- 类与类的关系越密切,耦合度越大
- 迪米特法则又叫最小直到原则,即一个类对自己依赖的类知道的越少越好。也就是一个类不管多么复杂,都应将其逻辑封装在类的内部,对外除了提供public方法之外,不对外泄露任何信息
- 也有另一个更加简单的定义:只与直接朋友通信
- 直接的朋友:只要两个对象有耦合关系,就是有朋友关系。耦合的方式有很多,依赖,关联,组合,聚合等。其中,我们称成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
合成复用原则
尽量使用合成/聚合的方式,而不是使用继承
UML类图简要复习
类与类之间的关系:依赖,泛化(继承),实现,关联,聚合,组合
依赖:只要是类中用到了对方,那么他们之间就存在依赖关系
泛化:实际上就是继承关系
实现关系:
关联关系:
聚合关系:表示部分与整体的关系,并且部分与整体可以分离
组合关系:表示部分与整体的关系,并且部分与整体不可分离
UML箭头方向
- 依赖于
- 包含于
- 扩展于
- 继承于
- 除了包含方向外,其它都是“小”的指向“大”的,“子”指向“父”,“一般”指向“抽象”
设计模式
分为三种类型:
- 创建型模式:单例模式,抽象工厂模式,原型模式,建造者模式,工厂模式
- 结构型模式:适配器模式,桥接模式,*装饰者模式**,组合模式,外观模式,享元模式,代理模式***
- 行为型模式:模板方法模式,命令模式,访问者模式,迭代器模式,**观察者模式**,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,责任链模式
单例模式
八种方式:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
饿汉式(静态常量)
步骤:
- 构造器私有化
- 类的内部创建对象
- 向外暴露一个静态的公共方法
public class Singleton01 {
private Singleton01() {
}
private final static Singleton01 instance = new Singleton01();
public static Singleton01 getInstance(){
return instance;
}
}
存在的问题
优点:
- 实现比较简单
- 避免了多线程的同步问题
缺点:在类装载时就完成实例化,没有达到懒加载的效果,若使终没有用到这个类,便会形成内存浪费。
饿汉式(静态代码块)
public class Singleton2 {
private Singleton2() {
}
private static Singleton2 instance;
static {
instance = new Singleton2();
}
public Singleton2 getInstance() {
return instance;
}
}
此种方式的优缺点与上面一种写法是一样的
懒汉式(线程不安全)
class Singleton{
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance != null) {
instance=new Singleton();
}
return instance;
}
}
优点:可以实现懒加载的效果
缺点:线程不安全,在实际工作中不能使用
懒汉式(线程安全,同步方法)
class Singleton{
private static Singleton4 instance;
private Singleton(){
}
public static synchronized Singleton4 getInstance(){
if (instance != null) {
instance=new Singleton4();
}
return instance;
}
}
优点:解决了懒加载的问题,线程不安全的问题
缺点:每一次均要进行同步,造成效率变低,在实际开发中不推荐使用
懒汉式(线程不安全,同步代码块)
class Singleton {
private static Singleton4 instance;
private Singleton() {
}
public static Singleton4 getInstance() {
if (instance != null) {
synchronized (Singleton.class) {
instance = new Singleton4();
}
}
return instance;
}
}
并没有解决线程安全问题,还是不好~
双重检查
class Singleton {
private Singleton() {
}
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
*volatile在这里保证内存可见性,此外,volatile还可以放止指令重排
优点:解决线程安全,效率问题
关于双重检查还有些兼容问题,例如在JSR133以前的标准,双重检查可能会带来一些线程安全问题,详见https://blog.csdn.net/shadow_zed/article/details/79650874
静态内部类
静态内部类特点:
- 当外部类装载时,内部类并不会装载
- 仅当内部类被调用时才会被装载
- 类装载过程中是线程安全的
class Singleton7 {
private Singleton7() {
}
private static class SingletonInstance {
private static final Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance() {
return SingletonInstance.INSTANCE;
}
}
优点:
- 采用类装载机制来保证保证实例化时仅有一个线程
- 静态内部类在实例化时不会立即实例化,在需要的时候才会实例化
- 仅有一次类装载,且装载过程是线程安全的
枚举
enum SingletonEnum{
INSTANCE;
public void sayHello(){
System.out.println("hello");
}
}
值得一提的是以上说的单例模式(除枚举)也会存在一些问题,例如可以通过反射和序列化破坏单例模式,详见:https://blog.csdn.net/siying8419/article/details/82152921
单例模式的使用
在jdk中的Runtime类中使用的就是饿汉式单例模式,以下是部分源码:
public class Runtime {
private static final Runtime currentRuntime = new Runtime();
private static Runtime.Version version;
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {
}
使用场景
- 需要频繁创建和销毁对象
- 创建对象耗时过多
- 工具类,数据源或者工厂
工厂模式
简单工厂模式
错误的方式
public class PizzaStore {
public static void main(String[] args) {
OrderPizza orderPizza = new OrderPizza();
orderPizza.orderPizza();
}
}
此方式违反了OCP原则,当需要增加种类时,还需对代码做较大的改动
简单工厂模式
- 简单工厂模式属于创建型模式,是由一个工厂对象决定创建出哪一种产品类的实例
- 定义一个创建对象的类,由这个类来封装实例化对象的行为
- 软件开发中,当需要用到大量创建某种类时就会使用到工厂模式
public class SimpleFactory {
public Pizza createPizza(String orderType){
Pizza pizza = null;
//订购的类型
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("greek pizza");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("cheese pizza");
}
return pizza;
}
}
public class OrderPizza {
public void orderPizza() {
SimpleFactory simpleFactory = new SimpleFactory();
do {
String orderType = getType();
Pizza pizza = simpleFactory.createPizza(orderType);
if (pizza!=null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("No such pizza...");
}
} while (true);
}
private String getType() {
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Input pizza type: ");
String pizzaType = bufferedReader.readLine();
return pizzaType;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
简单工厂模式,有时也会将工厂创建写为静态的,也叫静态工厂模式
工厂方法模式
将实例化功能抽象成抽象方法,在不同的子类中具体实现。将对象的实例化推迟到子类中
public class BJOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String orderType) {
Pizza pizza=null;
if (orderType.equals("cheese")) {
pizza = new BJCheesePizza();
pizza.setName("cheese pizza");
} else if (orderType.equals("greek")) {
pizza = new BJGreekPizza();
pizza.setName("greek pizza");
}
return pizza;
}
}
抽象工厂模式
- 定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合
- 将工厂抽象成两层,抽象工厂和具体工厂,程序员可以根据创建对象的类型使用对应的工厂子类。将单个简单工厂变成了工厂簇,更利于代码的维护与扩展
public interface AbsFactory {
public Pizza createPizza(String orderType);
}
public class OrderPizza {
AbsFactory absFactory;
public void setAbsFactory(AbsFactory absFactory) {
this.absFactory = absFactory;
String orderType;
Pizza pizza;
do {
orderType = getType();
pizza = absFactory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("No such type...");
break;
}
} while (true);
}
private String getType() {
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Input pizza type: ");
String pizzaType = bufferedReader.readLine();
return pizzaType;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
public class LDFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
Pizza pizza=null;
if (orderType.equals("cheese")) {
pizza = new LDCheesePizza();
pizza.setName("cheese");
} else if(orderType.equals("greek")){
pizza=new LDGreekPizza();
pizza.setName("greek");
}
return pizza;
}
}
工厂模式在JDK中的应用
在Calendar类中使用的就是工厂模式
public static Calendar getInstance() {
Locale aLocale = Locale.getDefault(Category.FORMAT);
return createCalendar(defaultTimeZone(aLocale), aLocale);
}
小结
- 将实例化的对象从代码提取出来,放到一个类中统一管理和维护,到达和主项目依赖关系的解耦,从而提高项目的扩展和维护性
- 三种工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)
- 设计模式的依赖抽象原则
- 设计对象时,不要直接new,变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者接口
- 不要覆盖基类中已经实现的方法
原型模式
Java中Object类提供了clone()的方法,类需要实现Cloneable接口
基本介绍:
- 原型模式是指用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型的模式,允许一个对象再创建另一个可定制的对象而无需知道其细节
- 工作原理:通过将一个原型对象传给那个要发动创建的对象,这个对象通过请求原型对象拷贝他们自己来实现
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Sheep implements Cloneable{
String name;
int age;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Sheep tom = new Sheep("Tom", 1);
Sheep sheep2= (Sheep) tom.clone();
}
}
原型模式在Spring框架中的使用
public class ProtoType {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object bean1 = applicationContext.getBean("id01");
Object bean2 = applicationContext.getBean("id01");
System.out.println(bean1.hashCode() == bean2.hashCode());
}
}
Spring中部分源码
public Object getBean(String name) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(name);
}
浅拷贝与深拷贝问题
浅拷贝介绍
- 对于基本类型数据,浅拷贝会直接进行值传递
- 对于大多数引用类型变量,浅拷贝会进行引用传递
*需特别注意的是对于String类型以及Integer等包装类都是不可变的对象,当需要修改不可变对象的值时,需要在内存中生成一个新的对象来存放新的值,然后将原来的引用指向新的地址。
深拷贝介绍
- 复制对象的所有基本数据类型的成员变量值
- 对对象中的引用类型也要进行拷贝
- 深拷贝实现方式一:重写clone方法来实现深拷贝
- 深拷贝实现方式二:通过对象序列化实现深拷贝
实现方式一:
public class DeepProtoType implements Serializable, Cloneable {
public String name;
public DeepCloneTarget deepCloneTarget;
//方式一
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
deep = super.clone();
//对引用类型属性进行单独处理
DeepCloneTarget deepCloneTarget = (DeepCloneTarget) deep;
Object clone = deepCloneTarget.clone();
return deep;
}
}
方式二
//通过序列化实现深拷贝
public Object deepClone() {
//创建流对象
ByteArrayOutputStream byteArrayOutputSteam = null;
ObjectOutputStream objectOutputStream = null;
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
try {
byteArrayOutputSteam = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputSteam);
objectOutputStream.writeObject(this);
byteArrayInputStream=new ByteArrayInputStream(byteArrayOutputSteam.toByteArray());
objectInputStream=new ObjectInputStream(byteArrayInputStream);
DeepProtoType deepProtoType = (DeepProtoType) objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
objectInputStream.close();
byteArrayInputStream.close();
objectOutputStream.close();
byteArrayOutputSteam.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return deepCloneTarget;
}
小结
- 创建比较复杂的对象时可以利用原型模式简化对象创建过程,也能提高效率
- 不用重新初始化对象,而是动态的获取对象运行时的状态
- 如果原始对象发生了变化,克隆对象也会发生变化二无需修改代码
建造者模式
又称生成器模式,将复杂对象的建造过程抽象出来,使这个抽象过程的不同抽象方法可以构建出不同表象(属性)的对象
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就能构建他们,用户不需要知道内部的具体细节
建造者模式四个角色
- Product产品角色
- Builder抽象建造者
- ConcreteBuilder具体建造者
- Director指挥者
建造房子实例
public class HouseDirector {
HouseBuilder houseBuilder=null;
HouseDirector(HouseBuilder houseBuilder){
this.houseBuilder=houseBuilder;
}
public House constructHouse(){
houseBuilder.buildBase();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
public abstract class HouseBuilder {
protected House house=new House();
public abstract void buildBase();
public abstract void buildWalls();
public abstract void roofed();
public House buildHouse(){
return house;
}
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class House {
private String base;
private String wall;
private String roofed;
}
public class HighBuilding extends HouseBuilder {
@Override
public void buildBase() {
System.out.println("common house base...");
}
@Override
public void buildWalls() {
System.out.println("high house walls...");
}
@Override
public void roofed() {
System.out.println("high house roof...");
}
}
public class CommonHouse extends HouseBuilder {
@Override
public void buildBase() {
System.out.println("common house base...");
}
@Override
public void buildWalls() {
System.out.println("common house walls...");
}
@Override
public void roofed() {
System.out.println("common house roof...");
}
}
public class Client {
public static void main(String[] args) {
CommonHouse commonHouse = new CommonHouse();
HouseDirector houseDirector = new HouseDirector(commonHouse);
House house = houseDirector.constructHouse();
}
}
建造者模式在StringBuilder中的使用
public final class StringBuilder extends AbstractStringBuilder implements Serializable, Comparable<StringBuilder>, CharSequence
abstract class AbstractStringBuilder implements Appendable, CharSequence
public interface Appendable {
Appendable append(CharSequence var1) throws IOException;
Appendable append(CharSequence var1, int var2, int var3) throws IOException;
Appendable append(char var1) throws IOException;
}
StringBuilder:充当了指挥者,又为一个具体的建造者(它继承了AbstractStringBuilder)
Appendable:定义了多个抽象方法,为抽象建造者
AbstractStringBuilder:实现了Appendable接口的方法,相当于建造者
小结
- 客户端不必知道产品内部细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每一个具体的建造者都相对独立,而与其他的建造者无关,用户可以使用不同的建造者即可得到不同的产品对象
- 可以更加精细的控制产品的创建过程
- 增加新的具体建造者无需修改原有类库的代码,指挥者针对抽象建造者编程复合OCP原则
- 建造者模式所创建的产品一般具有较多的共同点,如果产品之间差异较大,则不适合用建造者模式
- 类的内部较为复杂时不太适合使用建造者模式
- 抽象工厂模式不需关心构建过程只关心什么产品由什么工厂生产即可。而建造者模式则是按照指定的要求建造产品,目的是通过组装零配件而生产一个新产品
适配器模式
基本介绍
- 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,其别名为包装器
- 分为三类:类适配模式,对象适配模式,接口适配模式
类适配模式
public interface IVoltage5V {
public int output5V();
}
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
int srcV = output220V();
int destV = srcV / 44;
return destV;
}
}
- 类适配模式需要继承src类,就要求dst类必须为接口,有一定的局限性
- src的类方法在Adapter中都会暴露出来,增加了使用成本
- 由于其继承了src类,所以可以根据需求重写src类的方法,增加了一定的灵活性
对象适配器
对象适配器只是将Adapter类做修改,持有src类的实例实现dst接口
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V;
VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
if (voltage220V != null) {
int srcV = voltage220V.output220V();
int destV = srcV / 44;
return destV;
}
return -1;
}
}
接口适配器模式
也称为缺省适配器模式
当不需要全部实现类的接口时,可先设计一个抽象类实现接口中的所有方法,该抽象类的子类可以有选择的覆盖父类的某些方法来实现需求
适配器模式在SpringMVC中的分析
DispatcherServlet
public class DispatcherServlet extends FrameworkServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception{
...
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
....
}
HanlderAdapter有多种实现子类,通过适配器使得每一种Controller有对应的实现方式
小结
- 三种命名方式,根据src是以怎样的形式给到Adapter来命名的
- Adapter模式最重要的作用是将原本不兼容的接口结合在一起工作
- 在现实应用中,不仅限于以上三种方式
桥接模式
基本介绍
- 将实现与抽象放在两个不同的层次中,使两个类层次可以独立改变
- 桥接模式基于类的最小设计原则,通过使用封装,聚合及继承等行为让不同的类承担不同的责任,主要特点是把抽象与行为实现分离开来,从而可以保证各部分的独立性以及应对他们功能的扩展
下面看一个示例
public interface Brand {
void open();
void close();
void call();
}
public abstract class Phone {
private Brand brand;
Phone(Brand brand) {
this.brand = brand;
}
protected void open() {
brand.open();
}
protected void close() {
brand.open();
}
protected void call() {
brand.open();
}
}
public class FoldedPhone extends Phone {
FoldedPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
System.out.println("folded phone...");
}
public void close() {
super.close();
System.out.println("folded phone...");
}
public void call() {
super.call();
System.out.println("folded phone...");
}
}
public class OPPO implements Brand{
public void open() {
System.out.println("OPPO open...");
}
public void close() {
System.out.println("OPPO close...");
}
public void call() {
System.out.println("OPPO call...");
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new FoldedPhone(new OPPO());
}
}
桥接模式在JDBC源码中的分析
JDBC中的Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有MySQL,Oracle的Driver,这些可以当作接口类
小结
- 实现了抽象与实现的分离,从而极大的提供了系统的灵活性,有助于系统进行分层设计,从而产生更好的结构化系统
- 对于高层部分,仅需知道抽象部分和实现部分的接口就可以了,其他部分由具体的业务类来完成
- 桥接模式代替多层继承,可以减少子类的个数,降低系统管理和维护的成本
- 桥接模式增加了系统理解和设计的难度,由于聚合关联关系建立在抽象层要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,既需要有这样的使用场景
应用场景:对于那些不希望继承或者因为多继承导致系统类急剧增加的系统
装饰者模式
装饰者模式是指动态的将新功能附加到对象上,在对象功能扩充方面,它比继承更有弹性,装饰者模式也体现了OCP原则
public class Decorator extends Drink {
private Drink obj;
public Decorator(Drink obj) {
this.obj = obj;
}
public float cost() {
return super.getPrice() + obj.cost();
}
public String getDes() {
return super.getDes() + " " + super.getPrice() + " && " + obj.getDes();
}
}
*这里还请看Github上的源码,实在是太多辽
装饰者模式在FilterInputSteam中的应用
- InputStream相当于抽象类,类似我们前面讲的Drink
- FileInputStream相对于Decaf, LongBlack
- FilterInputStream相当于Decorator
- DataInputStream相当于Milk
- FilterInputStream中含有protected volatile InputStream in,相当于被装饰者
组合模式
基本介绍
- 组合模式又叫部分整体模式,他创建的树形结构,将对象组合成树状结构以表示“整体–部分”的关系
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能使客户以一致性的方式处理个别对象以及组合对象
示例
@AllArgsConstructor
@Setter
@Getter
public abstract class OrganizationComponent {
private String name;
private String description;
protected void add(OrganizationComponent organizationComponent) {
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponent organizationComponent){
throw new UnsupportedOperationException();
}
protected abstract void print();
}
public class Department extends OrganizationComponent {
public Department(String name, String description) {
super(name, description);
}
@Override
protected void print() {
}
}
public class University extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
public University(String name, String description) {
super(name, description);
}
protected void print() {
System.out.println(getName());
organizationComponents.forEach(System.out::println);
}
@Override
protected void add(OrganizationComponent organizationComponent){
organizationComponents.add(organizationComponent);
}
@Override
protected void remove(OrganizationComponent organizationComponent){
organizationComponents.remove(organizationComponent);
}
}
组合模式在HashMap的源码
public class Composite {
public static void main(String[] args) {
Map<Integer, String> hashMap = new HashMap<>();
hashMap.put(1,"ysz001");//相当于直接放叶子节点(node)
}
}
Map相当于Component,AbstractMap实现了Map接口,也相当于Component。
HashMap继承了AbstractMap,相当于Composite。
HashMap内有一静态内部类Node,相当于Leaf
外观模式
基本介绍
- 外观模式也叫过程模式,外观模式为子系统的一组接口提供了一个一致的界面,此模式定义了一个高层的接口,这个接口使得这一子系统更加容易使用
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需和这个接口发生调用,而无需关心这个子系统内部的细节
外观类:为调用提供统一的接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当的子系统对象
调用者:外观接口的调用者
子系统的集合:指的是模块或者子系统,处理外观类指派的任务,为功能实际的提供者
public class HomeTheaterFacade {
private Popcorn popcorn;
private DVDPlayer dvdPlayer;
private Projector projector;
public HomeTheaterFacade() {
this.popcorn = Popcorn.getInstance();
this.dvdPlayer = DVDPlayer.getInstance();
this.projector = Projector.getInstance();
}
public void ready(){
popcorn.on();
dvdPlayer.on();
projector.on();
}
}
外观模式在MyBatis源码中的应用
小结
- 外观模式屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
- 外观模式通过客户端与子系统的耦合关系,让子系统内部模块更容易维护和扩展
- 通过合理使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要分层设计时,可以考虑使用外观模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得十分难以维护和扩展,此时可以考虑为系统开发一个facade类,来提供遗留系统的比较清晰简单的接口,让新系统与facade类交互,提高复用性
- 不能让过多或者不合理的使用外观模式
享元模式
基本介绍
- 又称蝇量模式,运用共享技术有效的支持大量细粒度的问题
- 常用于系统底层的开发,解决系统的性能问题,例如数据库连接池
- 享元模式能够解决重复对象的内存浪费问题,当系统中有大量相似对象时,可以直接从缓冲池中拿
内部状态和外部状态
内部状态是指对象共享出来的信息,存储在对象内部且不随环境的变化而变化
外部状态是指对象得以依赖的一个标记,是随环境变化而变化,不可共享的状态
<img src="D:\program\blog\source\images\DesignPattern\FlyWeight\website.png" style="zoom:80%;" />
public abstract class WebSite {
public abstract void use();
}
public class ConcreteWebSite extends WebSite {
public String type;
public ConcreteWebSite(String type) {
this.type = type;
}
public void use() {
System.out.println("Concrete website...");
}
}
public class WebSiteFactory {
private HashMap<String, ConcreteWebSite> pool =new HashMap<String, ConcreteWebSite>();
public WebSite getWebSite(String type) {
if (!pool.containsKey(type)) {
pool.put(type,new ConcreteWebSite(type));
}
return pool.get(type);
}
}
public class Client {
public static void main(String[] args) {
WebSiteFactory webSiteFactory = new WebSiteFactory();
WebSite news = webSiteFactory.getWebSite("news");
}
}
享元模式在Integer中的应用
当Integer的大小位于-128 – +127之间时具有相同大小的Integer会返回相同的一个对象,当位于其他范围时会返回不同的对象
代理模式
基本介绍
- 总共有三种代理模式:静态代理,动态代理(JDK代理,接口代理),Cglib代理(可以在内存中动态创建对象,而不需要实现接口)
- 被代理的对象可以是远程对象,创建开销大的对象或需要安全控制的对象
静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起事项相同的接口或者是继承相同的父类
public interface ITeacherDao {
public void teach();
}
public class Teacher implements ITeacherDao {
public void teach() {
System.out.println("Teacher is teaching...");
}
}
public class TeacherDAOProxy implements ITeacherDao {
private ITeacherDao iTeacherDao;
public void teach() {
iTeacherDao.teach();
}
}
public class Client {
public static void main(String[] args) {
Teacher teacher = new Teacher();
TeacherDAOProxy teacherDAOProxy = new TeacherDAOProxy(teacher);
teacherDAOProxy.teach();
}
}
缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多的代理类
一旦接口增加方法,目标对象与代理对象都要维护
动态代理
- 动态代理不需要实现接口,但目标对象仍需实现接口
- 代理对象的生成是利用JDK的API动态的在内存中构建对象
- 动态代理也叫做JDK代理,接口代理
@AllArgsConstructor
public class ProxyFactory {
private Object target;
public Object getProxyInstance(){
return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader()
, target.getClass().getInterfaces()
, new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return method.invoke(o, objects);
}
});
}
}
public class Client {
public static void main(String[] args) {
TeacherDAO teacherDAO = new TeacherDAO();
ProxyFactory proxyFactory = new ProxyFactory(teacherDAO);
Object proxyInstance = proxyFactory.getProxyInstance();
System.out.println("proxyInstance = " + proxyInstance);
}
}
Cglib代理
- 有时目标对象只是一个单独的对象,并没有任何的接口,这个时候就可以使用目标对象子类来实现代理,即Cglib代理
- Cglib代理也叫做子类代理,它在内存中构建一个子类对象从而实现对目标对象的功能扩展
- Cglib是一个功能强大的高性能代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的被用在许多AOP框架中,例如Spring AOP实现方法拦截
- Cglib代理的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
@AllArgsConstructor
public class ProxyFactory implements MethodInterceptor {
private Object target;
public Object getProxyInstance(){
//创建一个工具类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(target.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建子类对象,即代理对象
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib on...");
Object invoke = method.invoke(target, objects);
return invoke;
}
}
其他常见的代理
- 防火墙代理
- 缓存代理
- 远程代理:RPC-DUBBO
- 同步代理
模板模式
基本介绍
- 模板方法模式,又叫模板模式,在一个抽象类公开定义了执行它的方法模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
- 模板方法模式定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定的步骤
示例
public abstract class SoyaMilk {
public void make(){
add();
heat();
}
public abstract void add();
public abstract void heat();
}
public class PeanutSoyaMilk extends SoyaMilk {
public void add() {
System.out.println("add peanut...");
}
public void heat() {
System.out.println("heat peanut milk...");
}
}
模板方法模式中的钩子方法
在父类中定义一个类,它默认不做任何事,子类可以视情况决定要不要覆盖它,此方法称为钩子
模板方法模式在Spring IOC中的应用
ConfigurableApplicationContext中的refresh方法就为一个模板方法,postProcessBeanFactory(), onRefresh()为钩子方法
命令模式
基本介绍
- 在软件设计过程中,需要向对象发送请求,但不知道请求的具体接收者时,仅需在运行时指定具体的请求接受者即可
- 命令模式使得请求发送者与请求接收者解耦
- 在命令模式中,会将一个请求封装为一个对象,以便不同参数来表示不同的请求,同时命令模式也支持撤销操作
Invoker:调用者
Command:命令角色,需要执行的命令都放在这里,可以是抽象类或接口
Receiver:接收者对象,知道如何实施和执行一个请求相关操作
ConcreteCommand:将一个接收者对象与一个动作绑定,调用接收者相应的动作,实现execute
public class LightReceiver {
public void on() {
System.out.println("Light on...");
}
public void off(){
System.out.println("Light off...");
}
}
public interface Command {
public void execute();
public void undo();
}
public class LightOnCommand implements Command {
LightReceiver lightReceiver;
public void execute() {
lightReceiver.on();
}
public void undo() {
lightReceiver.off();
}
}
命令模式在Spring JDBCTemplate中的应用
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations
@Override
public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
query(sql, new RowCallbackHandlerResultSetExtractor(rch));
}
@Override
@Nullable
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query [" + sql + "]");
}
/**
* Callback to execute the query.
*/
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
@FunctionalInterface
public interface StatementCallback<T> {
/*...*/
@Nullable
T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}
StatementCallback接口,类似命令接口Command
QueryStatementCallback实现了命令接口,同时也充当了命令接收者
JdbcTemplate是命令调用者,其中execute方法中,调用action.doInStatement方法,不同的实现StatementCallback对象,对应不同的doInStatement实现逻辑
另外实现StatementCallment命令接口的子类还有QueryStatementCallback
小结
- 将发起请求的对象与执行请求的对象进行解耦,即请求发起者与请求执行者通过命令对象实现解耦
- 容易设计一个命令队列,只要把命令放入命令队列中,可以实现多线程执行命令
- 容易实现对请求的撤销与重做
- 缺点:可能造成过多的命令类,增加了系统的复杂度
- 空命令也是一种命令,省去了判空的操作
- 命令模式经典应用场景:按钮对应一个命令,模拟CMD(DOS命令),订单的撤销/恢复,触发-反馈机制
访问者模式
基本介绍
- 封装了一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作
- 主要将数据结构与数据操作分离,解决数据结构和操作的耦合性问题
- 访问者模式的基本原理是:在被访问的类里面加一个对外提供接待访问者的接口
- 访问者模式主要应用于有着很多不同操作的对象
- Vistor是抽象访问者,为该对象中的每一个ConcreteElement声明一个visit操作
- ConcreteVisitor是一个具体的访问值,实现每个Visitor声明的操作i,是每个操作实现的部分
- ObjectStructure能枚举它的元素,可以提供一个接口,访问者者可以通过这个接口访问元素
- Element定义了一个accept方法,接受一个访问者
- ConcreteElement为具体元素,实现了accept方法
示例
public abstract class Action {
public abstract void getManConclusion(Man man);
public abstract void getWomanConclusion(Woman women);
}
public class Success extends Action {
public void getManConclusion(Man man) {
System.out.println("Man: great...");
}
public void getWomanConclusion(Woman women) {
System.out.println("Woman: great...");
}
}
public abstract class Person {
public abstract void accept(Action action);
}
public class Man extends Person {
public void accept(Action action) {
action.getManConclusion(this);
}
}
public class ObjectStructure {
private List<Person> personList= new LinkedList<>();
public void attach(Person person) {
personList.add(person);
}
public void detach(Person person) {
personList.remove(person);
}
public void diaPlay(Action action) {
for (Person person : personList) {
person.accept(action);
}
}
}
小结
- 复合单一职责原则,让程序具有优秀的扩展性,灵活性高
- 可以对功能进行统一,适用于数据结构相对稳定的系统
- 具体的元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样就造成了具体元素变更比较困难
- 违背了依赖倒转原则,访问者依赖的是具体元素而不是抽象
- 如果系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式比较适用
迭代器模式
提供了一种遍历集合元素的统一的接口,用一致的方式遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部结构、
示例
迭代器模式在JDK中的应用
小结
- 提供一个统一的方法遍历对象
- 隐藏了聚合的内部结构,客户端要遍历时只取到迭代器,而不知道聚合的具体组成
- 提供了一种设计思想,一个类只有一种变化的原因(单一职责),将管理的对象与迭代器分开,在改变时互不影响
- 当要展示一组相似的对象或者遍历一组相同的对象时,适合使用迭代器模式
- 每个聚合对象均需要一个迭代器,会生成多个迭代器不易管理类
观察者模式
观察者模式,会以集合的形式来管理用户(Observer),包括注册,移除和通知
这样,在增加观察者时就不需要去修改核心类WeatherData的代码,遵守了OCP原则
示例
观察者模式在Observable中的应用
- Observable的作用和地位等价于Subject
- Observable是类,不是接口,类中已经实现了核心的方法
- Observer的作用的地位等价于Observer
- Observable是类,通过继承来实现观察者模式
中介者模式
基本介绍
- 用一个中介对象来封装一系列对象的交互,使其松耦合,而且可以独立的改变他们之间的交互
- 中介者模式属于行为型模式,是代码易于维护
- 比如MVC中的Controller起到了中介者的作用
示例
小结
- 多个类相互调用会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦
- 减少类间依赖,降低了耦合,复合迪米特原则
- 中介者承担了较多的责任,一旦中介者出了问题,整个系统就会受到影响
- 如果设计不当,中介者本身变得过于复杂,这点在使用时需特别注意
备忘录模式
基本介绍
- 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态
- 备忘录模式主要用于记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据
示例
小结
- 给用户提供了一个可以恢复状态的机制,可以使用户比较方便的回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 如果类成员较多,会占用较多的资源
- 为了节约内存,备忘录模式可以和原型模式配合使用
解释器模式
- 编译原理中的词法分析和语法分析都可以视为一个解释器
- 解释器时对给定的一种表达式,定义的文法和一个解释器
- 应用
- 将一个需要解释执行的语言中的句子表示成一颗抽象的语法树
- 一些重复问题可以使用一种简单语言来描述
- 一个简单语法需要解释的场景
- 编译器,运算表达式,正则表达式,机器人
解释器模式在Spring框架中的应用
- Expression表达式接口下面有不同的实现类,如SqelExpression,使用的时候根据不同的Parser对象,返回不同的Expression对象,最后使用Expression对象,调用GetValue解释执行表达式,最后得到结果
小结
当一个语言需要解释执行,可将语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有更好的扩展性
解释器模式可能引起类膨胀,由于采用递归调用,将导致调用调试十分复杂,效率低
状态模式
基本介绍
- 主要用于解决对象在多种状态转换时,需要对外输出不同的行为,状态与行为是一一对应的,状态之间可以相互转换
- 当一个类内在状态改变时,允许其改变行为,这个对象看起来就行改编成为了另一个类
- Context类为环境角色,用于维护State实例,这个实例定义当前状态
- State是抽象状态角色,定义一个接口封装与Context的一个特点接口相关的行为
- ConcreteState是具体的状态角色,每个子类实现一个与Context一个状态的相关行为
小结
- 代码具有很强的可读性,将每个状态的行为封装到一个类中
- 方便维护。将容易产生问题的if-else代码删除
- 复合开闭原则,容易增加状态
- 会产生很多类,每个状态都要一个对应的类,当状态过多时会产生很多的类,增加维护难度
- 当一个事件或者对象有很多状态时,状态之间相互转换,对不同的状态要求不同的行为时可以考虑使用状态模式
策略模式
基本介绍
- 定义算法簇,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户
- 这个算法体现了几个设计原则
- 将变化的代码从不变的代码中分离出来
- 针对接口编程而不是具体类
- 多用组合/聚合,少用继承
示例

策略模式在Arrays中的应用
Arrays.sort()中使用的是策略模式
- 实现了Comparator接口(策略接口),匿名类对象new Comparator
(){…} - 对象new Comparator
(){…}就是实现了策略接口的对象 - public int compare(Integer o1, Integer o2){}指定了具体的处理方式
小结
- 策略模式关键是分析项目中的变化部分与不变部分
- 核心思想是:多用组合/聚合,少用继承;用行为类组合而不是继承,更有弹性
- 体现了OCP原则,客户端增加行为只需要添加一种策略即可,避免看多重if
- 提供了可以替换继承关系的方法,策略模式将算法封装在独立的Strategy中使得我们可以独立于其Context改变它,使它易于切换,理解,扩展
- 每添加一个策略就需要增加一个类,当策略过多时会导致类的数目庞大
责任链模式
基本介绍
- 责任链模式又叫职责链模式,为请求创建一个接收者对象的链,对请求的发送者和接收者进行解耦
- 责任链模式通常每个接受者都包含对另一个接收者的引用,如果一个对象不能处理改请求,则将相同的请求传给下一个接收者,以此类推
示例
责任链模式在Spring MVC中的应用
- 在Spring MVC请求过程中,执行了拦截器相关方法interceptor.preHandler等
- HandlerExecutionChain主要职责是将请求拦截器的执行和处理请求,但是他本身并不处理请求,只是将请求分配给链上注册的处理器执行,减少责任链本身与处理逻辑之间的耦合,规范了处理流程
- HandlerExecutionChain维护了HandlerIntercepter集合,可以向其中注册相应事务拦截器
小结
- 将请求和处理分离,实现解耦,提高系统灵活性
- 简化了对象,使对象不需知道链的结构
- 在链比较长时,性能可能会受到影响,因此需要控制最大节点数量
- 调试不方便,调试时逻辑可能比较复杂
- 应用场景:有多个对象同时处理同一请求,如:多级请求,请假/加薪等审批流程,Tomcat中对Encoding的处理,拦截器
参考链接与致谢
- Java 之浅拷贝、深拷贝,你到底知多少?https://zhuanlan.zhihu.com/p/95686213
- 单例模式详解(包括反射破坏和序列化破坏)https://blog.csdn.net/siying8419/article/details/82152921
- Java 单例模式中使用双重检查(Double-Check)https://blog.csdn.net/shadow_zed/article/details/79650874