文章目录

  • 1. Java设计模式大体上分为三大类介绍
  • 2. 设计模式遵循的六大原则
  • 3. Java的23种设计模式:
  • 3.1 创建型模式
    • 3.1.1. 工厂模式(Factory Method)
    • 3.1.2. 抽象工厂模式(Abstract Factory)
    • 3.1.3. 建造者模式(生成器模式)(Builder)
    • 3.1.4. 单例模式(Singleton)
      • 3.1.4.1 懒汉模式
      • 3.1.4.2 双重同步锁单例模式
      • 3.1.4.3 静态内部类单例模式
      • 3.1.4.4 饿汉单例模式
      • 3.1.4.5 反射破坏单例模式
      • 3.1.4.6 反射破坏单例模式
      • 3.1.4.7 枚举单例模式
    • 3.1.5. 原型模式(Protype)
  • 3.2 结构型模式
    • 3.2.1. 适配器模式(Adapter)
    • 3.2.2. 装饰模式(Decorator)
    • 3.2.3. 代理模式(Proxy)
      • 3.2.3.1 静态代理
      • 3.2.3.2 动态代理
      • 3.2.3.3 CGLib代理
      • 3.2.3.4 Spring 对代理模式的拓展
    • 3.2.4. 外观模式(Facade)
    • 3.2.5. 桥接模式(Bridge)
    • 3.2.6. 享元模式(Flyweight)
    • 3.2.7. 组合模式(Composite Pattern)
  • 3.3 行为型模式
    • 3.3.1. 模板模式(Template Pattern)
    • 3.3.2. 迭代器模式(Iterator Pattern)
    • 3.3.3. 策略模式(Strategy Pattern)
    • 3.3.4. 解释器模式(Interpreter Pattern)
    • 3.3.5. 观察者模式(Observer Pattern)
    • 3.3.6. 备忘录模式(Memento Pattern)
    • 3.3.7. 命令模式(Command Pattern)
    • 3.3.8. 中介者模式(Mediator Pattern)
    • 3.3.9. 责任链模式(Chain of Responsibility Pattern)
    • 3.3.10. 访问者模式(Visitor Pattern)
    • 3.3.11. 状态模式(State Pattern)
  • 4. 完结,撒花✌✌✌

本文代码基本都有很多没有初始化等等问题,主要是为了减少代码量,达到一眼就能了解大概情况的目的。

参考资料

  1. 菜鸟教程:https://www.runoob.com/design-pattern/design-pattern-tutorial.html
  2. Mr Bird 的博客:https://mrbird.cc/Java%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.html

1. Java设计模式大体上分为三大类介绍


  1. 创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  2. 结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其实还有两类:并发型模式和线程池模式。

用一个图片来整体描述一下:

2. 设计模式遵循的六大原则


  1. 开闭原则Open Close Principle):对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
  2. 里氏代换原则Liskov Substitution Principle):只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
  3. 依赖倒转原则Dependence Inversion Principle):这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。
  4. 接口隔离原则Interface Segregation Principle):使用多个隔离的借口来降低耦合度。
  5. 迪米特法则(最少知道原则)(Demeter Principle):一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
  6. 合成复用原则Composite Reuse Principle):原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。

3. Java的23种设计模式:


从这一块开始,我们详细介绍Java中23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析:

3.1 创建型模式


3.1.1. 工厂模式(Factory Method)


工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。


介绍
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

主要解决:主要解决接口选择的问题。

何时使用:我们明确地计划不同条件下创建不同实例时。

如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

关键代码:创建过程在其子类执行。

应用实例:

  1. 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
  2. Hibernate 换数据库只需换方言和驱动就可以。

优点:

  1. 一个调用者想创建一个对象,只要知道其名称就可以了。
  2. 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  3. 屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

使用场景:

  1. 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
  2. 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
  3. 设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。

注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。


实现
我们将创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactory。

FactoryPatternDemo,我们的演示类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。

工厂模式的 UML 图:

步骤 1
创建一个接口:
Shape.java

public interface Shape {void draw();
}

步骤 2
创建实现接口的实体类。
Rectangle.java

public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("Inside Rectangle::draw() method.");}
}

Square.java

public class Square implements Shape {@Overridepublic void draw() {System.out.println("Inside Square::draw() method.");}
}

Circle.java

public class Circle implements Shape {@Overridepublic void draw() {System.out.println("Inside Circle::draw() method.");}
}

步骤 3
创建一个工厂,生成基于给定信息的实体类的对象。

ShapeFactory.java
public class ShapeFactory {//使用 getShape 方法获取形状类型的对象public Shape getShape(String shapeType){if(shapeType == null){return null;}        if(shapeType.equalsIgnoreCase("CIRCLE")){return new Circle();} else if(shapeType.equalsIgnoreCase("RECTANGLE")){return new Rectangle();} else if(shapeType.equalsIgnoreCase("SQUARE")){return new Square();}return null;}
}

步骤 4
使用该工厂,通过传递类型信息来获取实体类的对象。

FactoryPatternDemo.java
public class FactoryPatternDemo {public static void main(String[] args) {ShapeFactory shapeFactory = new ShapeFactory();//获取 Circle 的对象,并调用它的 draw 方法Shape shape1 = shapeFactory.getShape("CIRCLE");//调用 Circle 的 draw 方法shape1.draw();//获取 Rectangle 的对象,并调用它的 draw 方法Shape shape2 = shapeFactory.getShape("RECTANGLE");//调用 Rectangle 的 draw 方法shape2.draw();//获取 Square 的对象,并调用它的 draw 方法Shape shape3 = shapeFactory.getShape("SQUARE");//调用 Square 的 draw 方法shape3.draw();}
}

执行程序,输出结果:

Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.

3.1.2. 抽象工厂模式(Abstract Factory)


抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。


介绍
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

主要解决:主要解决接口选择的问题。

何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。

如何解决:在一个产品族里面,定义多个产品。

关键代码:在一个工厂里聚合多个同类产品。

应用实例:工作了,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装(成套,一系列具体产品)、时尚装(成套,一系列具体产品),甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。假设一种情况(现实中是不存在的,要不然,没法进入共产主义了,但有利于说明抽象工厂模式),在您的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用 OOP 的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。

优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

使用场景:

  1. QQ 换皮肤,一整套一起换。
  2. 生成不同操作系统的程序。

注意事项:产品族难扩展,产品等级易扩展。


实现
我们将创建 Shape 和 Color 接口和实现这些接口的实体类。下一步是创建抽象工厂类 AbstractFactory。接着定义工厂类 ShapeFactory 和 ColorFactory,这两个工厂类都是扩展了 AbstractFactory。然后创建一个工厂创造器/生成器类 FactoryProducer。

AbstractFactoryPatternDemo,我们的演示类使用 FactoryProducer 来获取 AbstractFactory 对象。它将向 AbstractFactory 传递形状信息 Shape(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。同时它还向 AbstractFactory 传递颜色信息 Color(RED / GREEN / BLUE),以便获取它所需对象的类型。

抽象工厂模式的 UML 图:

步骤 1
为形状创建一个接口。
Shape.java

public interface Shape {void draw();
}

步骤 2
创建实现接口的实体类。
Rectangle.java

public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("Inside Rectangle::draw() method.");}
}

Square.java

public class Square implements Shape {@Overridepublic void draw() {System.out.println("Inside Square::draw() method.");}
}

Circle.java

public class Circle implements Shape {@Overridepublic void draw() {System.out.println("Inside Circle::draw() method.");}
}

步骤 3
为颜色创建一个接口。
Color.java

public interface Color {void fill();
}

步骤4
创建实现接口的实体类。
Red.java

public class Red implements Color {@Overridepublic void fill() {System.out.println("Inside Red::fill() method.");}
}

Green.java

public class Green implements Color {@Overridepublic void fill() {System.out.println("Inside Green::fill() method.");}
}

Blue.java

public class Blue implements Color {@Overridepublic void fill() {System.out.println("Inside Blue::fill() method.");}
}

步骤 5
为 Color 和 Shape 对象创建抽象类来获取工厂。
AbstractFactory.java

public abstract class AbstractFactory {public abstract Color getColor(String color);public abstract Shape getShape(String shape) ;
}

步骤 6
创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象。
ShapeFactory.java

public class ShapeFactory extends AbstractFactory {@Overridepublic Shape getShape(String shapeType){if(shapeType == null){return null;}        if(shapeType.equalsIgnoreCase("CIRCLE")){return new Circle();} else if(shapeType.equalsIgnoreCase("RECTANGLE")){return new Rectangle();} else if(shapeType.equalsIgnoreCase("SQUARE")){return new Square();}return null;}@Overridepublic Color getColor(String color) {return null;}
}

ColorFactory.java

public class ColorFactory extends AbstractFactory {@Overridepublic Shape getShape(String shapeType){return null;}@Overridepublic Color getColor(String color) {if(color == null){return null;}        if(color.equalsIgnoreCase("RED")){return new Red();} else if(color.equalsIgnoreCase("GREEN")){return new Green();} else if(color.equalsIgnoreCase("BLUE")){return new Blue();}return null;}
}

步骤 7
创建一个工厂创造器/生成器类,通过传递形状或颜色信息来获取工厂。
FactoryProducer.java

public class FactoryProducer {public static AbstractFactory getFactory(String choice){if(choice.equalsIgnoreCase("SHAPE")){return new ShapeFactory();} else if(choice.equalsIgnoreCase("COLOR")){return new ColorFactory();}return null;}
}

步骤 8
使用 FactoryProducer 来获取 AbstractFactory,通过传递类型信息来获取实体类的对象。
AbstractFactoryPatternDemo.java

public class AbstractFactoryPatternDemo {public static void main(String[] args) {//获取形状工厂AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");//获取形状为 Circle 的对象Shape shape1 = shapeFactory.getShape("CIRCLE");//调用 Circle 的 draw 方法shape1.draw();//获取形状为 Rectangle 的对象Shape shape2 = shapeFactory.getShape("RECTANGLE");//调用 Rectangle 的 draw 方法shape2.draw();//获取形状为 Square 的对象Shape shape3 = shapeFactory.getShape("SQUARE");//调用 Square 的 draw 方法shape3.draw();//获取颜色工厂AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");//获取颜色为 Red 的对象Color color1 = colorFactory.getColor("RED");//调用 Red 的 fill 方法color1.fill();//获取颜色为 Green 的对象Color color2 = colorFactory.getColor("Green");//调用 Green 的 fill 方法color2.fill();//获取颜色为 Blue 的对象Color color3 = colorFactory.getColor("BLUE");//调用 Blue 的 fill 方法color3.fill();}
}

步骤 9
执行程序,输出结果:

Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside Red::fill() method.
Inside Green::fill() method.
Inside Blue::fill() method.

3.1.3. 建造者模式(生成器模式)(Builder)


建造者模式(Builder),也被称为生成器模式。

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。


介绍
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

何时使用:一些基本部件不会变,而其组合经常变化的时候。

如何解决:将变与不变分离开。

关键代码:建造者:创建和提供实例;导演:管理建造出来的实例的依赖关系。

应用实例:

  1. 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
  2. JAVA 中的 StringBuilder。

优点:

  1. 建造者独立,易扩展。
  2. 便于控制细节风险。

缺点:

  1. 产品必须有共同点,范围有限制。
  2. 如内部变化复杂,会有很多的建造类。

使用场景:

  1. 需要生成的对象具有复杂的内部结构。
  2. 需要生成的对象内部属性本身相互依赖。

注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。


实现
我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。

我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。

然后我们创建一个 Meal 类,带有 Item 的 ArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilder。BuilderPatternDemo,我们的演示类使用 MealBuilder 来创建一个 Meal。

建造者模式的 UML 图:

步骤 1
创建一个表示食物条目和食物包装的接口。
Item.java

public interface Item {public String name();public Packing packing();public float price();
}

Packing.java

public interface Packing {public String pack();
}

步骤 2
创建实现 Packing 接口的实体类。
Wrapper.java

public class Wrapper implements Packing {@Overridepublic String pack() {return "Wrapper";}
}

Bottle.java

public class Bottle implements Packing {@Overridepublic String pack() {return "Bottle";}
}

步骤 3
创建实现 Item 接口的抽象类,该类提供了默认的功能。
Burger.java

public abstract class Burger implements Item {@Overridepublic Packing packing() {return new Wrapper();}@Overridepublic abstract float price();
}

ColdDrink.java

public abstract class ColdDrink implements Item {@Overridepublic Packing packing() {return new Bottle();}@Overridepublic abstract float price();
}

步骤 4
创建扩展了 Burger 和 ColdDrink 的实体类。
VegBurger.java

public class VegBurger extends Burger {@Overridepublic float price() {return 25.0f;}@Overridepublic String name() {return "Veg Burger";}
}

ChickenBurger.java

public class ChickenBurger extends Burger {@Overridepublic float price() {return 50.5f;}@Overridepublic String name() {return "Chicken Burger";}
}

Coke.java

public class Coke extends ColdDrink {@Overridepublic float price() {return 30.0f;}@Overridepublic String name() {return "Coke";}
}

Pepsi.java

public class Pepsi extends ColdDrink {@Overridepublic float price() {return 35.0f;}@Overridepublic String name() {return "Pepsi";}
}

步骤 5
创建一个 Meal 类,带有上面定义的 Item 对象。
Meal.java

import java.util.ArrayList;
import java.util.List;public class Meal {private List<Item> items = new ArrayList<Item>();    public void addItem(Item item){items.add(item);}public float getCost(){float cost = 0.0f;for (Item item : items) {cost += item.price();}        return cost;}public void showItems(){for (Item item : items) {System.out.print("Item : "+item.name());System.out.print(", Packing : "+item.packing().pack());System.out.println(", Price : "+item.price());}        }
}

步骤 6
创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象。
MealBuilder.java

public class MealBuilder {public Meal prepareVegMeal (){Meal meal = new Meal();meal.addItem(new VegBurger());meal.addItem(new Coke());return meal;}   public Meal prepareNonVegMeal (){Meal meal = new Meal();meal.addItem(new ChickenBurger());meal.addItem(new Pepsi());return meal;}
}

步骤 7
BuiderPatternDemo 使用 MealBuider 来演示建造者模式(Builder Pattern)。
BuilderPatternDemo.java

public class BuilderPatternDemo {public static void main(String[] args) {MealBuilder mealBuilder = new MealBuilder();Meal vegMeal = mealBuilder.prepareVegMeal();System.out.println("Veg Meal");vegMeal.showItems();System.out.println("Total Cost: " +vegMeal.getCost());Meal nonVegMeal = mealBuilder.prepareNonVegMeal();System.out.println("\n\nNon-Veg Meal");nonVegMeal.showItems();System.out.println("Total Cost: " +nonVegMeal.getCost());}
}

步骤 8
执行程序,输出结果:

Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.5

3.1.4. 单例模式(Singleton)


单例模式目的是为了一个类只有一个实例

在内部创建一个实例,构造器全部设置为private,所有方法均在该实例上改动,在创建上要注意类的实例化只能执行一次,可以采用许多种方法来实现,如Synchronized关键字,或者利用内部类等机制来实现。

优点:

  1. 内存中只有一个实例,减少了内存开销;
  2. 可以避免对资源的多重占用;
  3. 设置全局访问点,严格控制访问。

缺点:

  1. 没有接口,拓展困难。

3.1.4.1 懒汉模式

懒汉模式下的单例写法是最简单的,但它是线程不安全的:

public class LazySingleton {private static LazySingleton lazySingleton = null;private LazySingleton() {}public static LazySingleton getInstance() {if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}
}

可加同步锁解决线程安全问题

public class LazySingleton {private static LazySingleton lazySingleton = null;private LazySingleton() {}public static LazySingleton getInstance() {synchronized (LazySingleton.class) {if (lazySingleton == null) {lazySingleton = new LazySingleton();}}return lazySingleton;}
}

但是同步锁锁的是整个类,比较消耗资源,并且即使运行内存中已经存在LazySingleton,调用其getInstance还是会上锁,所以这种写法也不是很好。


3.1.4.2 双重同步锁单例模式

public class LazyDoubleCheckSingleton {private static volatile LazyDoubleCheckSingleton instance = null;private LazyDoubleCheckSingleton() {}public static LazyDoubleCheckSingleton getInstance() {if (instance == null) {synchronized (LazyDoubleCheckSingleton.class) {if (instance == null) {instance = new LazyDoubleCheckSingleton();}}}return instance;}
}

上面例子虽然加了同步锁,但它还是线程不安全的。虽然上面的例子不会出现多次初始化LazyDoubleCheckSingleton实例的情况,但是由于指令重排的原因,某些线程可能会获取到空对象,后续对该对象的操作将触发空指针异常。

要修复这个问题,只需要阻止指令重排即可,所以可以给instance属性加上volatile关键字

public class LazyDoubleCheckSingleton {private volatile static LazyDoubleCheckSingleton instance = null;private LazyDoubleCheckSingleton() {}public static LazyDoubleCheckSingleton getInstance() {if (instance == null) {synchronized (LazyDoubleCheckSingleton.class) {if (instance == null) {instance = new LazyDoubleCheckSingleton();}}}return instance;}
}

上面这种写法是不但确保了线程安全,并且当LazyDoubleCheckSingleton实例创建好后,后续再调用其getInstance方法不会上锁。


3.1.4.3 静态内部类单例模式

看例子:

public class StaticInnerClassSingleton {private StaticInnerClassSingleton() {}private static class InnerClass {private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();}public static StaticInnerClassSingleton getInstance() {return InnerClass.instance;}
}

为什么这个例子是可行的呢?主要有两个原因:

  1. JVM在类的初始化阶段会加Class对象初始化同步锁,同步多个线程对该类的初始化操作;
  2. 静态内部类InnerClass的静态成员变量instance在方法区中只会有一个实例。

在Java规范中,当以下这些情况首次发生时,A类将会立刻被初始化:

  1. A类型实例被创建;
  2. A类中声明的静态方法被调用;
  3. A类中的静态成员变量被赋值;
  4. A类中的静态成员被使用(非常量);

3.1.4.4 饿汉单例模式

饿汉:意指在类加载的时候就初始化:

public class HungrySingleton {private final static HungrySingleton instance = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return instance;}
}

这种模式在类加载的时候就完成了初始化,所以并不存在线程安全性问题;但由于不是懒加载,饿汉模式不管需不需要用到实例都要去创建实例,如果创建了不使用,则会造成内存浪费。


3.1.4.5 反射破坏单例模式

前面的单例例子在实现序列化接口后都能被序列化的方式破坏,比如HungrySingleton,让其实现序列化接口:

public class HungrySingleton implements Serializable {private static final long serialVersionUID = -8073288969651806838L;private final static HungrySingleton instance = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return instance;}
}

然后创建Application测试一下如何破坏:

public class Application {public static void main(String[] args) throws IOException, ClassNotFoundException {// 演示序列化破坏单例HungrySingleton instance = HungrySingleton.getInstance();ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("file"));outputStream.writeObject(instance);ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("file"));HungrySingleton newInstance = (HungrySingleton) inputStream.readObject();System.out.println(instance);System.out.println(newInstance);System.out.println(instance == newInstance);}
}

输出如下所示:

cc.mrbird.design.pattern.creational.singleton.HungrySingleton@7f31245a
cc.mrbird.design.pattern.creational.singleton.HungrySingleton@6d03e736
false

可以看到,虽然是单例模式,但却成功创建出了两个不一样的实例,单例遭到了破坏。

要让反序列化后的对象和序列化前的对象是同一个对象的话,可以在HungrySingleton里加上readResolve方法:

public class HungrySingleton implements Serializable {private static final long serialVersionUID = -8073288969651806838L;private final static HungrySingleton instance = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return instance;}// 新增private Object readResolve() {return instance;}
}

再次运行Application的main方法后:

cc.mrbird.design.pattern.creational.singleton.HungrySingleton@7f31245a
cc.mrbird.design.pattern.creational.singleton.HungrySingleton@7f31245a
true

可以看到,这种方式最终反序列化出来的对象和序列化对象是同一个对象。但这种方式反序列化过程内部还是会重新创建HungrySingleton实例,只不过因为HungrySingleton类定义了readResolve方法(方法内部返回instance引用),反序列化过程会判断目标类是否定义了readResolve该方法,是的话则通过反射调用该方法。


3.1.4.6 反射破坏单例模式

除了序列化能破坏单例外,反射也可以,举个反射破坏HungrySingleton的例子:

public class Application {public static void main(String[] args) throws Exception {HungrySingleton instance = HungrySingleton.getInstance();// 反射创建实例Class<HungrySingleton> c = HungrySingleton.class;// 获取构造器Constructor<HungrySingleton> constructor = c.getDeclaredConstructor();// 打开构造器权限constructor.setAccessible(true);HungrySingleton newInstance = constructor.newInstance();System.out.println(instance);System.out.println(newInstance);System.out.println(instance == newInstance);}
}

输出如下所示:

cc.mrbird.design.pattern.creational.singleton.HungrySingleton@1b6d3586
cc.mrbird.design.pattern.creational.singleton.HungrySingleton@4554617c
false

可以看到,我们通过反射破坏了私有构造器权限,成功创建了新的实例。

对于这种情况,饿汉模式下的例子可以在构造器中添加判断逻辑来防御(懒汉模式的就没有办法了),比如修改HungrySingleton的代码如下所示:

public class HungrySingleton {private final static HungrySingleton instance = new HungrySingleton();private HungrySingleton() {if (instance != null) {throw new RuntimeException("forbidden");}}public static HungrySingleton getInstance() {return instance;}
}

再次运行Applicationmain方法:

Exception in thread "main" java.lang.reflect.InvocationTargetExceptionat sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)at java.lang.reflect.Constructor.newInstance(Constructor.java:423)at cc.mrbird.design.pattern.creational.singleton.Application.main(Application.java:33)
Caused by: java.lang.RuntimeException: forbiddenat cc.mrbird.design.pattern.creational.singleton.HungrySingleton.<init>(HungrySingleton.java:16)... 5 more

3.1.4.7 枚举单例模式

枚举单例模式是推荐的单例模式,它不仅可以防御序列化攻击,也可以防御反射攻击。举个枚举单例模式的代码:

public enum EnumSingleton {INSTANCE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static EnumSingleton getInstance(){return INSTANCE;}
}

验证下是否是单例的:

public class Application {public static void main(String[] args) throws Exception {EnumSingleton instance = EnumSingleton.getInstance();instance.setData(new Object());EnumSingleton newInstance = EnumSingleton.getInstance();System.out.println(instance);System.out.println(newInstance);System.out.println(instance.getData());System.out.println(newInstance.getData());}
}

输出如下所示:

INSTANCE
INSTANCE
java.lang.Object@1b6d3586
java.lang.Object@1b6d3586

测试下序列化攻击:

public class Application {public static void main(String[] args) throws Exception {EnumSingleton instance = EnumSingleton.getInstance();instance.setData(new Object());ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("file"));outputStream.writeObject(instance);ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("file"));EnumSingleton newInstance = (EnumSingleton) inputStream.readObject();System.out.println(instance);System.out.println(newInstance);System.out.println(instance == newInstance);System.out.println(instance.getData());System.out.println(newInstance.getData());System.out.println(instance.getData() == newInstance.getData());}
}

输出如下所示:

INSTANCE
INSTANCE
true
java.lang.Object@568db2f2
java.lang.Object@568db2f2
true

可以看到序列化和反序列化后的对象是同一个。

原理:跟踪ObjectInputStream#readObject源码,其中当反编译对象为枚举类型时,将调用readEnum方法:

name为枚举类里的枚举常量,对于线程来说它是唯一的,存在方法区,所以通过Enum.valueOf((Class)cl, name)方法得到的枚举对象都是同一个。

再测试一下反射攻击:

public class Application {public static void main(String[] args) throws Exception {EnumSingleton instance = EnumSingleton.getInstance();Class<EnumSingleton> c = EnumSingleton.class;// 枚举类只包含一个(String,int)类型构造器Constructor<EnumSingleton> constructor = c.getDeclaredConstructor(String.class, int.class);constructor.setAccessible(true);EnumSingleton newInstance = constructor.newInstance("hello", 1);System.out.println(instance == newInstance);}
}

运行输出如下:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objectsat java.lang.reflect.Constructor.newInstance(Constructor.java:417)at cc.mrbird.design.pattern.creational.singleton.Application.main(Application.java:71)

可以看到抛异常了,查看Constructor类的 417 行代码可以发现原因:

Java 禁止通过反射创建枚举对象。

正是因为枚举类型拥有这些天然的优势,所以用它创建单例是不错的选择,这也是Effective Java推荐的方式

3.1.5. 原型模式(Protype)


原型实例指定创建对象的种类,通过拷贝这些原型创建新的对象。(原型模式就是讲一个对象作为原型,使用clone()方法来创建新的实例。)

适用于:

  1. 类初始化消耗较多资源;
  2. 循环体中生产大量对象的时候。

优点:

  1. 原型模式性能比直接new一个对象性能好;
  2. 简化创建对象过程。

缺点:

  1. 对象必须重写Object克隆方法;
  2. 复杂对象的克隆方法写起来较麻烦(深克隆、浅克隆)

举例:

新建一个学生类Student,实现克隆接口,并重写Object的克隆方法(因为都是简单属性,所以浅克隆即可):

public class Student implements Cloneable {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}

Application中测试一波:

public class Application {public static void main(String[] args) throws CloneNotSupportedException {Student student = new Student();ArrayList<Student> list = new ArrayList<>();for (int i = 0; i < 3; i++) {Student s = (Student) student.clone();s.setName("学生" + i);s.setAge(20 + i);list.add(s);}System.out.println(list);}
}

输出如下所示:

[Student{name='学生0', age=20}, Student{name='学生1', age=21}, Student{name='学生2', age=22}]

这种方式会比直接在循环中创建Student性能好。

当对象包含引用类型属性时,需要使用深克隆,比如Student包含Date属性时:

public class Student implements Cloneable {private String name;private int age;private Date birthday;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", birthday=" + birthday +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {Student student = (Student) super.clone();// 引用类型深克隆Date birthday = (Date) student.getBirthday().clone();student.setBirthday(birthday);return student;}
}

值得注意的是,克隆会破坏实现了Cloneable接口的单例对象。

3.2 结构型模式


3.2.1. 适配器模式(Adapter)


适配器模式的作用就是在原来的类上提供新功能,将一个类的接口转换为期望的另一个接口,使原本不兼容的类可以一起工作。主要可分为3种:

  1. 类适配:创建新类,继承源类,并实现新接口,例如 :
    class adapter extends oldClass implements newFunc{}

  2. 对象适配:创建新类持源类的实例,并实现新接口,例如:
    class adapter implements newFunc { private oldClass oldInstance ;}

  3. 接口适配:创建新的抽象类实现旧接口方法。例如 :
    abstract class adapter implements oldClassFunc { void newFunc();}

适用于:

  1. 已存在的类,它的方法和需求不匹配时(方法结果相同或者相似)

优点:

  1. 提高类的透明性和复用,现有的类复用但不需改变;
  2. 目标类和适配器类解耦,提高程序拓展性;
  3. 符合开闭原则。

缺点:

  1. 适配器编写过程需要全面考虑,可能会增加系统的复杂性;
  2. 降低代码可读性。

分为:类适配器模式和对象适配器模式。


先举个类适配器模式的例子:
假如项目里原有一条水果的产品线,比如包含一个树莓类Raspberry

public class Raspberry {public void addRaspberry() {System.out.println("添加点树莓");}
}

随着项目的拓展,现在新增了水果派产品线,新建Pie接口:

public interface Pie {void make();
}

要将Raspberry加入到Pie产品线,又不想修改Raspberry类的代码,则可以创建一个适配器RaspberryPieAdaptor

public class RaspberryPieAdaptor extends Raspberry implements Pie{@Overridepublic void make() {System.out.println("制作一个派");super.addRaspberry();}
}

适配器继承被适配的类,实现新的产品线接口。

在Application里测试一波:

public class Application {public static void main(String[] args) {Pie pie = new RaspberryPieAdaptor();pie.make();}
}

输出:

制作一个派
添加点树莓

成功通过适配器制造了树莓派。

对象适配器模式只需要将RaspberryPieAdaptor修改为:

public class RaspberryPieAdaptor implements Pie{private Raspberry raspberry = new Raspberry();@Overridepublic void make() {System.out.println("制作一个派");raspberry.addRaspberry();}
}

这种模式不直接继承被适配者,而是在适配器里创建被适配者。

3.2.2. 装饰模式(Decorator)


在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案。给一类对象增加新的功能,装饰方法与具体的内部逻辑无关。

适用于:

  1. 拓展一个类的功能;
  2. 动态给对象添加功能,并且动态撤销。

优点:

  1. 继承的有力补充,不改变原有对象的情况下给对象拓展功能;
  2. 通过使用不同的装饰类、不同的组合方式,实现不同的效果。
  3. 符合开闭原则。

缺点:

  1. 增加程序复杂性;

举个水果沙拉的例子:

比如在点水果沙拉外卖时,可以往水果沙拉里加各种水果,价格也会相应的调整,要让程序支持不同水果自由组合,并计算相应的价格,则可以使用装饰者模式来完成。

定义一个抽象的水果沙拉类AbstractFruitSalad:

public abstract class AbstractFruitSalad {public abstract String remark();public abstract int price();
}

包含备注和价格抽象方法。

接着创建一个抽象的装饰器AbstractDecorator(关键点,继承抽象水果沙拉类):

public class AbstractDecorator extends AbstractFruitSalad{private AbstractFruitSalad fruitSalad;public AbstractDecorator(AbstractFruitSalad fruitSalad){this.fruitSalad = fruitSalad;}@Overridepublic String remark() {return fruitSalad.remark();}@Overridepublic int price() {return fruitSalad.price();}
}

创建具体的水果沙拉类FruitSalad

public class FruitSalad extends AbstractFruitSalad{@Overridepublic String remark() {return "水果沙拉(标准)\n";}@Overridepublic int price() {return 9;}
}

该沙拉是标准的水果沙拉,价格是9元。

如果我们的水果沙拉还允许客户添加猕猴桃和西瓜,那么我们可以添加两个新的装饰器。添加猕猴桃装饰器KiwiDecorator

public class KiwiDecorator extends AbstractDecorator {public KiwiDecorator(AbstractFruitSalad fruitSalad) {super(fruitSalad);}@Overridepublic String remark() {return super.remark() + "加份猕猴桃切\n";}@Overridepublic int price() {return super.price() + 2;}
}

可以看到,加一份猕猴桃需要在原有基础上加2元。

接着继续创建西瓜装饰器WaterMelonDecorator

public class WaterMelonDecorator extends AbstractDecorator {public WaterMelonDecorator(AbstractFruitSalad fruitSalad) {super(fruitSalad);}@Overridepublic String remark() {return super.remark() + "加份西瓜切\n";}@Overridepublic int price() {return super.price() + 3;}
}

最后创建客户端Application测试一下:

public class Application {public static void main(String[] args) {// 点了份水果沙拉,并加了两份猕猴桃和一份西瓜,看看最终价格是多少?AbstractFruitSalad fruitSalad = new FruitSalad();fruitSalad = new KiwiDecorator(fruitSalad);fruitSalad = new KiwiDecorator(fruitSalad);fruitSalad = new WaterMelonDecorator(fruitSalad);System.out.println(fruitSalad.remark() + "价格是:" + fruitSalad.price());}
}

上面的写法也可以改为:

public class Application {public static void main(String[] args) {// 点了份水果沙拉,并加了两份猕猴桃和一份西瓜,看看最终价格是多少?AbstractFruitSalad fruitSalad = new FruitSalad();fruitSalad = new WaterMelonDecorator(new KiwiDecorator(new KiwiDecorator(fruitSalad)));System.out.println(fruitSalad.remark() + "价格是:" + fruitSalad.price());}
}

程序输出如下:

水果沙拉(标准)
加份猕猴桃切
加份猕猴桃切
加份西瓜切
价格是:16

通过不同的装饰器自由组合,我们可以灵活的组装出各式各样的水果沙拉,这正是装饰者模式的优点,但明显可以看出代码变复杂了。

3.2.3. 代理模式(Proxy)


客户端通过代理类访问,代理类实现具体的实现细节,客户只需要使用代理类即可实现操作。
为其他对象提供一种代理,以控制对这个对象的访问,代理对象在客户端和目标对象之间起到了中介的作用。

这种模式可以对旧功能进行代理,用一个代理类调用原有的方法,且对产生的结果进行控制。

适用于:

  1. 保护目标对象;
  2. 增强目标对象。

优点:

  1. 将代理对象和真实被调用的目标对象分离;
  2. 降低耦合,拓展性好;
  3. 保护目标对象,增强目标对象。

缺点:

  1. 造成类的数目增加,增加复杂度;
  2. 客户端和目标对象增加代理对象,会造成处理速度变慢。

3.2.3.1 静态代理

通过在代码中显式地定义了一个代理类,在代理类中通过同名的方法对目标对象的方法进行包装,客户端通过调用代理类的方法来调用目标对象的方法。

举个静态代理的例子:

新建一个派的制作接口PieService

public interface PieServcie {void makePie();
}

创建其实现类PieServiceImpl

public class PieServiceImpl implements PieServcie{public void makePie() {System.out.println("制作水果派");}
}

要对PieServiceImplmakePie方法增强,我们需要创建一个代理对象PieServiceProxy

public class PieServiceProxy {private PieServcie pieServcie;public void makePie() {beforeMethod();pieServcie = new PieServiceImpl();pieServcie.makePie();afterMethod();}private void beforeMethod() {System.out.println("准备材料");}private void afterMethod() {System.out.println("保鲜");}
}

PieServiceProxy中我们创建了一个和PieServcie一致的同名方法makePie,方法内部调用了PieServiceImplmakePie方法,并且在方法调用前调用了代理类的beforeMethod方法,方法调用后调用了代理类的afterMethod方法。

创建客户端Application,测试:

public class Application {public static void main(String[] args) {PieServiceProxy proxy = new PieServiceProxy();proxy.makePie();}
}

输出:

准备材料
制作水果派
保鲜

3.2.3.2 动态代理

JDK的动态代理只能代理接口,通过接口的方法名在动态生成的代理类中调用业务实现类的同名方法。

静态代理的缺点就是每需要代理一个类,就需要手写对应的代理类。这个问题可以用动态代理来解决。举个动态代理的例子:

新建冰淇淋制作接口IceCreamService

public interface IceCreamService {void makeIceCream(String fruit);
}

实现类IceCreamServiceImpl

public class IceCreamServiceImpl implements IceCreamService {public void makeIceCream(String fruit) {System.out.println("制作" + fruit + "冰激凌");}
}

现在需要对IceCreamServiceImpl进行代理增强,如果使用静态代理,我们需要编写一个IceCreamServiceImplProxy类,使用动态代理的话,我们可以动态生成对应的代理类。

创建DynamicProxy

public class DynamicProxy implements InvocationHandler {// 代理的目标对象private Object object;public DynamicProxy(Object object) {this.object = object;}public Object proxy() {Class<?> clazz = object.getClass();// 生成代理对象return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(), this);}/*** @param proxy  动态生成的代理对象* @param method 代理方法* @param args   代理方法的方法参数* @return 结果* @throws Throwable*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}}

动态代理类通过实现InvocationHandlerinvoke方法实现,proxy用于生成代理对象。剩下的步骤和静态代理类似,完善DynamicProxy

public class DynamicProxy implements InvocationHandler {// 代理的目标对象private Object object;public DynamicProxy(Object object) {this.object = object;}public Object proxy() {Class<?> clazz = object.getClass();// 生成代理对象return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(), this);}/*** @param proxy  动态生成的代理对象* @param method 代理方法* @param args   代理方法的方法参数* @return 结果* @throws Throwable*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {beforeMethod(object);// 反射执行代理对象的目标方法Object result = method.invoke(object, args);afterMethod(object);return result;}private void beforeMethod(Object object) {if (object instanceof PieServcie) {System.out.println("准备派的材料");} else if (object instanceof IceCreamService) {System.out.println("准备冰淇淋材料");} else {throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");}}private void afterMethod(Object object) {if (object instanceof PieServcie) {System.out.println("保鲜派");} else if (object instanceof IceCreamService) {System.out.println("保鲜冰淇淋");} else {throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");}}
}

创建客户端Application测试:

public class Application {public static void main(String[] args) {PieServcie pieServiceDynamicProxy = (PieServcie) new DynamicProxy(new PieServiceImpl()).proxy();pieServiceDynamicProxy.makePie();System.out.println("-----------------");IceCreamService iceCreamServiceDynamicProxy = (IceCreamService) new DynamicProxy(new IceCreamServiceImpl()).proxy();iceCreamServiceDynamicProxy.makeIceCream("草莓");}
}

结果:

准备派的材料
制作水果派
保鲜派
-----------------
准备冰淇淋材料
制作草莓冰激凌
保鲜冰淇淋

可以看到,通过动态代理我们实现了目标方法增强,并且不需要手写目标类的代理对象。


3.2.3.3 CGLib代理

通过继承来实现,生成的代理类就是目标对象类的子类,通过重写业务方法来实现代理。


3.2.3.4 Spring 对代理模式的拓展

  1. 当Bean有实现接口时,使用JDK动态代理;
  2. 当Bean没有实现接口时,使用CGLib代理。

可以通过以下配置强制使用CGLib代理:

spring:aop:proxy-target-class: true

3.2.4. 外观模式(Facade)


外观模式又叫门面模式,提供了统一得接口,用来访问子系统中的一群接口。

为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使得这一子系统更加容易使用。

适用于:

  1. 子系统越来越复杂,增加外观模式提供简单接口调用;
  2. 构建多层系统结构,利用外观对象作为每层的入口,简化层间调用。

优点:

  1. 简化了调用过程,无需了解深入子系统;
  2. 减低耦合度;
  3. 更好的层次划分;
  4. 符合迪米特法则。

缺点:

  1. 增加子系统,拓展子系统行为容易引入风险;
  2. 不符合开闭原则。

举个订外卖的例子。

创建一个外卖实体类Takeaway

public class Takeaway {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}

订外卖过程一般分为三个步骤:下单、支付和配送,所以我们创建三个Service对应这三个过程。新建下单服务OrderService

public class OrderService {public boolean placeAnOrder(Takeaway takeaway) {System.out.println(takeaway.getName() + "下单成功");return true;}
}

新建支付服务PayService

public class PayService {public boolean pay(Takeaway takeaway) {System.out.println("商品" + takeaway.getName() + "支付成功");return true;}
}

新建配送服务DeliveryService

public class DeliveryService {public void delivery(Takeaway takeaway) {System.out.println(takeaway.getName() + "已由骑手XX接单,订单派送中");}
}

基于外观模式法则,我们需要创建一个Service来聚合这三个服务,客户端只需要和这个Service交互即可。新建外卖服务TakeawayService

public class TakeawayService {private OrderService orderService = new OrderService();private PayService payService = new PayService();private DeliveryService deliveryService = new DeliveryService();public void takeOrder(Takeaway takeaway) {if (orderService.placeAnOrder(takeaway)) {if (payService.pay(takeaway)) {deliveryService.delivery(takeaway);}}}
}

新建个客户端测试一波:

public class Application {public static void main(String[] args) {Takeaway takeaway = new Takeaway();takeaway.setName("泡椒青蛙");TakeawayService takeawayService = new TakeawayService();takeawayService.takeOrder(takeaway);}
}

可以看到,客户端只需要调用TakeawayService即可,无需关系内部具体经历了多少个步骤,运行main方法输出如下:

泡椒青蛙下单成功
商品泡椒青蛙支付成功
泡椒青蛙已由骑手XX接单,订单派送中

3.2.5. 桥接模式(Bridge)


桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。

我们通过下面的实例来演示桥接模式(Bridge Pattern)的用法。其中,可以使用相同的抽象类方法但是不同的桥接实现类,来画出不同颜色的圆。


介绍:
意图: 将抽象部分与实现部分分离,使它们都可以独立的变化。

主要解决: 在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。

何时使用: 实现系统可能有多个角度分类,每一种角度都可能变化。

如何解决: 把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。

关键代码: 抽象类依赖实现类。

应用实例:

  1. 猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。
  2. 墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。

优点:

  1. 抽象和实现的分离。
  2. 优秀的扩展能力。
  3. 实现细节对客户透明。

缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

使用场景:

  1. 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  2. 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
  3. 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。

注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。


实现:
我们有一个作为桥接实现的 DrawAPI 接口和实现了 DrawAPI 接口的实体类 RedCircleGreenCircleShape 是一个抽象类,将使用 DrawAPI 的对象。BridgePatternDemo,我们的演示类使用 Shape 类来画出不同颜色的圆。

步骤 1:创建桥接实现接口DrawAPI.java

public interface DrawAPI {public void drawCircle(int radius, int x, int y);
}

步骤 2:创建实现了 DrawAPI 接口的实体桥接实现类RedCircle.java

public class RedCircle implements DrawAPI {@Overridepublic void drawCircle(int radius, int x, int y) {System.out.println("Drawing Circle[ color: red, radius: "+ radius +", x: " +x+", "+ y +"]");}
}
GreenCircle.java
public class GreenCircle implements DrawAPI {@Overridepublic void drawCircle(int radius, int x, int y) {System.out.println("Drawing Circle[ color: green, radius: "+ radius +", x: " +x+", "+ y +"]");}
}

步骤 3:使用 DrawAPI 接口创建抽象类 Shape.java

public abstract class Shape {protected DrawAPI drawAPI;protected Shape(DrawAPI drawAPI){this.drawAPI = drawAPI;}public abstract void draw();
}

步骤 4:创建实现了 Shape 接口的实体类Circle.java

public class Circle extends Shape {private int x, y, radius;public Circle(int x, int y, int radius, DrawAPI drawAPI) {super(drawAPI);this.x = x;  this.y = y;  this.radius = radius;}public void draw() {drawAPI.drawCircle(radius,x,y);}
}

步骤 5:使用 ShapeDrawAPI 类画出不同颜色的圆:

BridgePatternDemo.java
public class BridgePatternDemo {public static void main(String[] args) {Shape redCircle = new Circle(100,100, 10, new RedCircle());Shape greenCircle = new Circle(100,100, 10, new GreenCircle());redCircle.draw();greenCircle.draw();}
}

执行程序,输出结果:

Drawing Circle[ color: red, radius: 10, x: 100, 100]
Drawing Circle[  color: green, radius: 10, x: 100, 100]

3.2.6. 享元模式(Flyweight)


享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。


介绍
意图:运用共享技术有效地支持大量细粒度的对象。

主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用

  1. 系统中有大量对象。
  2. 这些对象消耗大量内存。
  3. 这些对象的状态大部分可以外部化。
  4. 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。
  5. 系统不依赖于这些对象身份,这些对象是不可分辨的。

如何解决:用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

关键代码:用 HashMap 存储这些对象。

应用实例

  1. JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。
  2. 数据库的数据池。

优点:大大减少对象的创建,降低系统的内存,使效率提高。

缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

使用场景

  1. 系统有大量相似对象。
  2. 需要缓冲池的场景。

注意事项

  1. 注意划分外部状态和内部状态,否则可能会引起线程安全问题。
  2. 这些类必须有一个工厂对象加以控制。

实现:
我们将创建一个 Shape 接口和实现了 Shape 接口的实体类 Circle。下一步是定义工厂类 ShapeFactory

ShapeFactory 有一个 CircleHashMap,其中键名为 Circle 对象的颜色。无论何时接收到请求,都会创建一个特定颜色的圆。ShapeFactory 检查它的 HashMap 中的 circle 对象,如果找到 Circle 对象,则返回该对象,否则将创建一个存储在 hashmap 中以备后续使用的新对象,并把该对象返回到客户端。

FlyWeightPatternDemo,我们的演示类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(red / green / blue/ black / white),以便获取它所需对象的颜色。

步骤 1:创建一个接口Shape.java

public interface Shape {void draw();
}

步骤 2:创建实现接口的实体类Circle.java

public class Circle implements Shape {private String color;private int x;private int y;private int radius;public Circle(String color){this.color = color;     }public void setX(int x) {this.x = x;}public void setY(int y) {this.y = y;}public void setRadius(int radius) {this.radius = radius;}@Overridepublic void draw() {System.out.println("Circle: Draw() [Color : " + color +", x : " + x +", y :" + y +", radius :" + radius);}
}

步骤 3:创建一个工厂,生成基于给定信息的实体类的对象ShapeFactory.java

import java.util.HashMap;public class ShapeFactory {private static final HashMap<String, Shape> circleMap = new HashMap<>();public static Shape getCircle(String color) {Circle circle = (Circle)circleMap.get(color);if(circle == null) {circle = new Circle(color);circleMap.put(color, circle);System.out.println("Creating circle of color : " + color);}return circle;}
}

步骤 4:使用该工厂,通过传递颜色信息来获取实体类的对象FlyweightPatternDemo.java

public class FlyweightPatternDemo {private static final String colors[] = { "Red", "Green", "Blue", "White", "Black" };public static void main(String[] args) {for(int i=0; i < 20; ++i) {Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor());circle.setX(getRandomX());circle.setY(getRandomY());circle.setRadius(100);circle.draw();}}private static String getRandomColor() {return colors[(int)(Math.random()*colors.length)];}private static int getRandomX() {return (int)(Math.random()*100 );}private static int getRandomY() {return (int)(Math.random()*100);}
}

执行程序,输出结果:

Creating circle of color : Black
Circle: Draw() [Color : Black, x : 36, y :71, radius :100
Creating circle of color : Green
Circle: Draw() [Color : Green, x : 27, y :27, radius :100
Creating circle of color : White
Circle: Draw() [Color : White, x : 64, y :10, radius :100
Creating circle of color : Red
Circle: Draw() [Color : Red, x : 15, y :44, radius :100
Circle: Draw() [Color : Green, x : 19, y :10, radius :100
Circle: Draw() [Color : Green, x : 94, y :32, radius :100
Circle: Draw() [Color : White, x : 69, y :98, radius :100
Creating circle of color : Blue
Circle: Draw() [Color : Blue, x : 13, y :4, radius :100
Circle: Draw() [Color : Green, x : 21, y :21, radius :100
Circle: Draw() [Color : Blue, x : 55, y :86, radius :100
Circle: Draw() [Color : White, x : 90, y :70, radius :100
Circle: Draw() [Color : Green, x : 78, y :3, radius :100
Circle: Draw() [Color : Green, x : 64, y :89, radius :100
Circle: Draw() [Color : Blue, x : 3, y :91, radius :100
Circle: Draw() [Color : Blue, x : 62, y :82, radius :100
Circle: Draw() [Color : Green, x : 97, y :61, radius :100
Circle: Draw() [Color : Green, x : 86, y :12, radius :100
Circle: Draw() [Color : Green, x : 38, y :93, radius :100
Circle: Draw() [Color : Red, x : 76, y :82, radius :100
Circle: Draw() [Color : Blue, x : 95, y :82, radius :100

3.2.7. 组合模式(Composite Pattern)


组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

我们通过下面的实例来演示组合模式的用法。实例演示了一个组织中员工的层次结构。


介绍
意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

何时使用

  1. 您想表示对象的部分-整体层次结构(树形结构)。
  2. 您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。

关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component

应用实例

  1. 算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作数、操作符和另一个操作数。
  2. JAVA AWTSWING 中,对于 ButtonCheckbox 是树叶,Container 是树枝。

优点

  1. 高层模块调用简单。
  2. 节点自由增加。

缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。

注意事项:定义时为具体类。


实现
我们有一个类 Employee,该类被当作组合模型类。CompositePatternDemo,我们的演示类使用 Employee 类来添加部门层次结构,并打印所有员工。

组合模式的 UML 图:

步骤 1:创建 Employee.java 类,该类带有 Employee 对象的列表:

import java.util.ArrayList;
import java.util.List;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+" ]");}
}

步骤 2:使用 Employee类 来创建和打印员工的层次结构CompositePatternDemo.java

public class CompositePatternDemo {public static void main(String[] args) {Employee CEO = new Employee("John","CEO", 30000);Employee headSales = new Employee("Robert","Head Sales", 20000);Employee headMarketing = new Employee("Michel","Head Marketing", 20000);Employee clerk1 = new Employee("Laura","Marketing", 10000);Employee clerk2 = new Employee("Bob","Marketing", 10000);Employee salesExecutive1 = new Employee("Richard","Sales", 10000);Employee salesExecutive2 = new Employee("Rob","Sales", 10000);CEO.add(headSales);CEO.add(headMarketing);headSales.add(salesExecutive1);headSales.add(salesExecutive2);headMarketing.add(clerk1);headMarketing.add(clerk2);//打印该组织的所有员工System.out.println(CEO); for (Employee headEmployee : CEO.getSubordinates()) {System.out.println(headEmployee);for (Employee employee : headEmployee.getSubordinates()) {System.out.println(employee);}}        }
}

执行程序,输出结果为:

Employee :[ Name : John, dept : CEO, salary :30000 ]
Employee :[ Name : Robert, dept : Head Sales, salary :20000 ]
Employee :[ Name : Richard, dept : Sales, salary :10000 ]
Employee :[ Name : Rob, dept : Sales, salary :10000 ]
Employee :[ Name : Michel, dept : Head Marketing, salary :20000 ]
Employee :[ Name : Laura, dept : Marketing, salary :10000 ]
Employee :[ Name : Bob, dept : Marketing, salary :10000 ]

3.3 行为型模式


3.3.1. 模板模式(Template Pattern)

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。


介绍
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

主要解决:一些方法通用,却在每一个子类都重新写了这一方法。

何时使用:有一些通用的方法。

如何解决:将这些通用算法抽象出来。

关键代码:在抽象类实现,其他步骤在子类实现。

应用实例

  1. 在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。
  2. 西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。
  3. spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。

优点

  1. 封装不变部分,扩展可变部分。
  2. 提取公共代码,便于维护。
  3. 行为由父类控制,子类实现。

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景

  1. 有多个子类共有的方法,且逻辑相同。
  2. 重要的、复杂的方法,可以考虑作为模板方法。

注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。


实现
我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。CricketFootball 是扩展了 Game 的实体类,它们重写了抽象类的方法。

TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。

步骤 1:创建一个抽象类Game.java,它的模板方法被设置为 final

public abstract class Game {abstract void initialize();abstract void startPlay();abstract void endPlay();//模板public final void play(){//初始化游戏initialize();//开始游戏startPlay();//结束游戏endPlay();}
}

步骤 2:创建扩展了上述类的实体类:
Cricket.java

public class Cricket extends Game {@Overridevoid endPlay() {System.out.println("Cricket Game Finished!");}@Overridevoid initialize() {System.out.println("Cricket Game Initialized! Start playing.");}@Overridevoid startPlay() {System.out.println("Cricket Game Started. Enjoy the game!");}
}

Football.java

public class Football extends Game {@Overridevoid endPlay() {System.out.println("Football Game Finished!");}@Overridevoid initialize() {System.out.println("Football Game Initialized! Start playing.");}@Overridevoid startPlay() {System.out.println("Football Game Started. Enjoy the game!");}
}

步骤 3:使用 Game 的模板方法 play() 来演示游戏的定义方式,TemplatePatternDemo.java

public class TemplatePatternDemo {public static void main(String[] args) {Game game = new Cricket();game.play();System.out.println();game = new Football();game.play();      }
}

执行程序,输出结果:

Cricket Game Initialized! Start playing.
Cricket Game Started. Enjoy the game!
Cricket Game Finished!Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!

3.3.2. 迭代器模式(Iterator Pattern)


迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

迭代器模式属于行为型模式。


介绍
意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。

主要解决:不同的方式来遍历整个整合对象。

何时使用:遍历一个聚合对象。

如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。

关键代码:定义接口:hasNext, next。

应用实例:JAVA 中的 iterator。

优点

  1. 它支持以不同的方式遍历一个聚合对象。
  2. 迭代器简化了聚合类。
  3. 在同一个聚合上可以有多个遍历。
  4. 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

使用场景

  1. 访问一个聚合对象的内容而无须暴露它的内部表示。
  2. 需要为聚合对象提供多种遍历方式。
  3. 为遍历不同的聚合结构提供一个统一的接口。

注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。


实现
我们将创建一个叙述导航方法的 Iterator 接口和一个返回迭代器的 Container 接口。实现了 Container 接口的实体类将负责实现 Iterator 接口。

IteratorPatternDemo,我们的演示类使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names

迭代器模式的 UML 图:

步骤 1:创建接口:
Iterator.java

public interface Iterator {public boolean hasNext();public Object next();
}
Container.java
public interface Container {public Iterator getIterator();
}

步骤 2:创建实现了 Container 接口的实体类。该类有实现了 Iterator 接口的内部类 NameIterator
NameRepository.java

public class NameRepository implements Container {public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};@Overridepublic Iterator getIterator() {return new NameIterator();}private class NameIterator implements Iterator {int index;@Overridepublic boolean hasNext() {if(index < names.length){return true;}return false;}@Overridepublic Object next() {if(this.hasNext()){return names[index++];}return null;}     }
}

步骤 3:使用 NameRepository 来获取迭代器,并打印名字。
IteratorPatternDemo.java

public class IteratorPatternDemo {public static void main(String[] args) {NameRepository namesRepository = new NameRepository();for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){String name = (String)iter.next();System.out.println("Name : " + name);}  }
}

执行程序,输出结果:

Name : Robert
Name : John
Name : Julie
Name : Lora

3.3.3. 策略模式(Strategy Pattern)


在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。


介绍
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。

如何解决:将这些算法封装成一个一个的类,任意地替换。

关键代码:实现同一个接口。

应用实例

  1. 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
  2. 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
  3. JAVA AWT 中的 LayoutManager

优点

  1. 算法可以自由切换。
  2. 避免使用多重条件判断。
  3. 扩展性良好。

缺点

  1. 策略类会增多。
  2. 所有策略类都需要对外暴露。

使用场景

  1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  2. 一个系统需要动态地在几种算法中选择一种。
  3. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。


实现
我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。

StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。

策略模式的 UML 图:

步骤 1:创建一个接口Strategy.java

public interface Strategy {public int doOperation(int num1, int num2);
}

步骤 2:创建实现接口的实体类。
OperationAdd.java

public class OperationAdd implements Strategy{@Overridepublic int doOperation(int num1, int num2) {return num1 + num2;}
}

OperationSubtract.java

public class OperationSubtract implements Strategy{@Overridepublic int doOperation(int num1, int num2) {return num1 - num2;}
}

OperationMultiply.java

public class OperationMultiply implements Strategy{@Overridepublic int doOperation(int num1, int num2) {return num1 * num2;}
}

步骤 3:创建 Context 类。

public class Context {private Strategy strategy;public Context(Strategy strategy){this.strategy = strategy;}public int executeStrategy(int num1, int num2){return strategy.doOperation(num1, num2);}
}

步骤 4:创建StrategyPatternDemo.java使用 Context 来查看当它改变策略 Strategy 时的行为变化。

public class StrategyPatternDemo {public static void main(String[] args) {Context context = new Context(new OperationAdd());    System.out.println("10 + 5 = " + context.executeStrategy(10, 5));context = new Context(new OperationSubtract());      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));context = new Context(new OperationMultiply());    System.out.println("10 * 5 = " + context.executeStrategy(10, 5));}
}

执行程序,输出结果:

10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

3.3.4. 解释器模式(Interpreter Pattern)


解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。


介绍
意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

主要解决:对于一些固定文法构建一个解释句子的解释器。

何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

如何解决:构建语法树,定义终结符与非终结符。

关键代码:构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。

应用实例:编译器、运算表达式计算。

优点:

  1. 可扩展性比较好,灵活。
  2. 增加了新的解释表达式的方式。
  3. 易于实现简单文法。

缺点:

  1. 可利用场景比较少。
  2. 对于复杂的文法比较难维护。
  3. 解释器模式会引起类膨胀。
  4. 解释器模式采用递归调用方法。

使用场景:

  1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
  2. 一些重复出现的问题可以用一种简单的语言来进行表达。
  3. 一个简单语法需要解释的场景。

注意事项:可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。


实现
我们将创建一个接口 Expression 和实现了 Expression 接口的实体类。定义作为上下文中主要解释器的 TerminalExpression 类。其他的类 OrExpressionAndExpression 用于创建组合式表达式。

InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。

解释器模式的 UML 图:

步骤 1
创建一个表达式接口。
Expression.java

public interface Expression {public boolean interpret(String context);
}

步骤 2
创建实现了上述接口的实体类。
TerminalExpression.java

public class TerminalExpression implements Expression {private String data;public TerminalExpression(String data){this.data = data; }@Overridepublic boolean interpret(String context) {if(context.contains(data)){return true;}return false;}
}

OrExpression.java

public class OrExpression implements Expression {private Expression expr1 = null;private Expression expr2 = null;public OrExpression(Expression expr1, Expression expr2) { this.expr1 = expr1;this.expr2 = expr2;}@Overridepublic boolean interpret(String context) {      return expr1.interpret(context) || expr2.interpret(context);}
}

AndExpression.java

public class AndExpression implements Expression {private Expression expr1 = null;private Expression expr2 = null;public AndExpression(Expression expr1, Expression expr2) { this.expr1 = expr1;this.expr2 = expr2;}@Overridepublic boolean interpret(String context) {      return expr1.interpret(context) && expr2.interpret(context);}
}

步骤 3
InterpreterPatternDemo 使用 Expression 类来创建规则,并解析它们。
InterpreterPatternDemo.java

public class InterpreterPatternDemo {//规则:Robert 和 John 是男性public static Expression getMaleExpression(){Expression robert = new TerminalExpression("Robert");Expression john = new TerminalExpression("John");return new OrExpression(robert, john);    }//规则:Julie 是一个已婚的女性public static Expression getMarriedWomanExpression(){Expression julie = new TerminalExpression("Julie");Expression married = new TerminalExpression("Married");return new AndExpression(julie, married);    }public static void main(String[] args) {Expression isMale = getMaleExpression();Expression isMarriedWoman = getMarriedWomanExpression();System.out.println("John is male? " + isMale.interpret("John"));System.out.println("Julie is a married women? " + isMarriedWoman.interpret("Married Julie"));}
}

执行程序,输出结果:

John is male? true
Julie is a married women? true

3.3.5. 观察者模式(Observer Pattern)

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。


介绍
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

应用实例:

  1. 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。
  2. 西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。

优点:

  1. 观察者和被观察者是抽象耦合的。
  2. 建立一套触发机制。

缺点:

  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

注意事项:

  1. JAVA 中已经有了对观察者模式的支持类。
  2. 避免循环引用。
  3. 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

实现
观察者模式使用三个类 SubjectObserverClientSubject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。

ObserverPatternDemo,我们的演示类使用 Subject 和实体类对象来演示观察者模式。

观察者模式的 UML 图:

步骤 1
创建 Subject 类。
Subject.java

import java.util.ArrayList;
import java.util.List;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();}}
}

步骤 2
创建 Observer 类。
Observer.java

public abstract class Observer {protected Subject subject;public abstract void update();
}

步骤 3
创建实体观察者类。
BinaryObserver.java

public class BinaryObserver extends Observer{public BinaryObserver(Subject subject){this.subject = subject;this.subject.attach(this);}@Overridepublic void update() {System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) ); }
}

OctalObserver.java

public class OctalObserver extends Observer{public OctalObserver(Subject subject){this.subject = subject;this.subject.attach(this);}@Overridepublic void update() {System.out.println( "Octal String: " + Integer.toOctalString( subject.getState() ) ); }
}

HexaObserver.java

public class HexaObserver extends Observer{public HexaObserver(Subject subject){this.subject = subject;this.subject.attach(this);}@Overridepublic void update() {System.out.println( "Hex String: " + Integer.toHexString( subject.getState() ).toUpperCase() ); }
}

步骤 4
使用 Subject 和实体观察者对象。
ObserverPatternDemo.java

public class ObserverPatternDemo {public static void main(String[] args) {Subject subject = new Subject();new HexaObserver(subject);new OctalObserver(subject);new BinaryObserver(subject);System.out.println("First state change: 15");   subject.setState(15);System.out.println("Second state change: 10");  subject.setState(10);}
}

执行程序,输出结果:

First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010

3.3.6. 备忘录模式(Memento Pattern)


备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。


介绍
意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

何时使用:很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。

如何解决:通过一个备忘录类专门存储对象状态。

关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。

应用实例:

  1. 后悔药。
  2. 打游戏时的存档。
  3. Windows 里的 ctri + z。
  4. IE 中的后退。
  5. 数据库的事务管理。

优点:

  1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
  2. 实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

使用场景:

  1. 需要保存/恢复数据的相关状态场景。
  2. 提供一个可回滚的操作。

注意事项:

  1. 为了符合迪米特原则,还要增加一个管理备忘录的类。
  2. 为了节约内存,可使用原型模式 + 备忘录模式。

实现
备忘录模式使用三个类 MementoOriginatorCareTakerMemento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。

MementoPatternDemo,我们的演示类使用 CareTakerOriginator 对象来显示对象的状态恢复。

备忘录模式的 UML 图:

步骤 1
创建 Memento 类。
Memento.java

public class Memento {private String state;public Memento(String state){this.state = state;}public String getState(){return state;}
}

步骤 2
创建 Originator 类。
Originator.java

public class Originator {private String state;public void setState(String state){this.state = state;}public String getState(){return state;}public Memento saveStateToMemento(){return new Memento(state);}public void getStateFromMemento(Memento Memento){state = Memento.getState();}
}

步骤 3
创建 CareTaker 类。
CareTaker.java

import java.util.ArrayList;
import java.util.List;public class CareTaker {private List<Memento> mementoList = new ArrayList<Memento>();public void add(Memento state){mementoList.add(state);}public Memento get(int index){return mementoList.get(index);}
}

步骤 4
使用 CareTaker 和 Originator 对象。
MementoPatternDemo.java

public class MementoPatternDemo {public static void main(String[] args) {Originator originator = new Originator();CareTaker careTaker = new CareTaker();originator.setState("State #1");originator.setState("State #2");careTaker.add(originator.saveStateToMemento());originator.setState("State #3");careTaker.add(originator.saveStateToMemento());originator.setState("State #4");System.out.println("Current State: " + originator.getState());    originator.getStateFromMemento(careTaker.get(0));System.out.println("First saved State: " + originator.getState());originator.getStateFromMemento(careTaker.get(1));System.out.println("Second saved State: " + originator.getState());}
}

验证输出:

Current State: State #4
First saved State: State #2
Second saved State: State #3

3.3.7. 命令模式(Command Pattern)


命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。


介绍
意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

何时使用:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

如何解决:通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。

关键代码:定义三个角色:

  1. received 真正的命令执行对象。
  2. Command。
  3. invoker 使用命令对象的入口

应用实例:struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。

优点:

  1. 降低了系统耦合度。
  2. 新的命令可以很容易添加到系统中去。

缺点:使用命令模式可能会导致某些系统有过多的具体命令类。

使用场景:认为是命令的地方都可以使用命令模式,比如:

  1. GUI 中每一个按钮都是一条命令。
  2. 模拟 CMD。

注意事项:系统需要支持命令的撤销 (Undo) 操作和恢复 (Redo) 操作,也可以考虑使用命令模式,见命令模式的扩展。

命令模式结构示意图:


实现
我们首先创建作为命令的接口 Order,然后创建作为请求的 Stock 类。实体命令类 BuyStock 和 SellStock,实现了 Order 接口,将执行实际的命令处理。创建作为调用对象的类 Broker,它接受订单并能下订单。

Broker 对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。CommandPatternDemo,我们的演示类使用 Broker 类来演示命令模式。

命令模式的 UML 图:

步骤 1
创建一个命令接口。
Order.java

public interface Order {void execute();
}

步骤 2
创建一个请求类。
Stock.java

public class Stock {private String name = "ABC";private int quantity = 10;public void buy(){System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] bought");}public void sell(){System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] sold");}
}

步骤 3
创建实现了 Order 接口的实体类。
BuyStock.java

public class BuyStock implements Order {private Stock abcStock;public BuyStock(Stock abcStock){this.abcStock = abcStock;}public void execute() {abcStock.buy();}
}

SellStock.java

public class SellStock implements Order {private Stock abcStock;public SellStock(Stock abcStock){this.abcStock = abcStock;}public void execute() {abcStock.sell();}
}

步骤 4
创建命令调用类。
Broker.java

import java.util.ArrayList;
import java.util.List;public class Broker {private List<Order> orderList = new ArrayList<Order>(); public void takeOrder(Order order){orderList.add(order);      }public void placeOrders(){for (Order order : orderList) {order.execute();}orderList.clear();}
}

步骤 5
使用 Broker 类来接受并执行命令。
CommandPatternDemo.java

public class CommandPatternDemo {public static void main(String[] args) {Stock abcStock = new Stock();BuyStock buyStockOrder = new BuyStock(abcStock);SellStock sellStockOrder = new SellStock(abcStock);Broker broker = new Broker();broker.takeOrder(buyStockOrder);broker.takeOrder(sellStockOrder);broker.placeOrders();}
}

执行程序,输出结果:

Stock [ Name: ABC, Quantity: 10 ] bought
Stock [ Name: ABC, Quantity: 10 ] sold

3.3.8. 中介者模式(Mediator Pattern)


中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。


介绍
意图:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。

何时使用:多个类相互耦合,形成了网状结构。

如何解决:将上述网状结构分离为星型结构。

关键代码:对象 Colleague 之间的通信封装到一个类中单独处理。

应用实例:

  1. 中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。
  2. 机场调度系统。
  3. MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。

优点:

  1. 降低了类的复杂度,将一对多转化成了一对一。
  2. 各个类之间的解耦。
  3. 符合迪米特原则。

缺点:中介者会庞大,变得复杂难以维护。

使用场景:

  1. 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
  2. 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

注意事项:不应当在职责混乱的时候使用。


实现
我们通过聊天室实例来演示中介者模式。实例中,多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息。我们将创建两个类 ChatRoom 和 User。User 对象使用 ChatRoom 方法来分享他们的消息。

MediatorPatternDemo,我们的演示类使用 User 对象来显示他们之间的通信。

中介者模式的 UML 图:

步骤 1
创建中介类。
ChatRoom.java

import java.util.Date;public class ChatRoom {public static void showMessage(User user, String message){System.out.println(new Date().toString()+ " [" + user.getName() +"] : " + message);}
}

步骤 2
创建 user 类。
User.java

public class User {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public User(String name){this.name  = name;}public void sendMessage(String message){ChatRoom.showMessage(this,message);}
}

步骤 3
使用 User 对象来显示他们之间的通信。
MediatorPatternDemo.java

public class MediatorPatternDemo {public static void main(String[] args) {User robert = new User("Robert");User john = new User("John");robert.sendMessage("Hi! John!");john.sendMessage("Hello! Robert!");}
}

执行程序,输出结果:

Thu Jan 31 16:05:46 IST 2013 [Robert] : Hi! John!
Thu Jan 31 16:05:46 IST 2013 [John] : Hello! Robert!

3.3.9. 责任链模式(Chain of Responsibility Pattern)

顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。


介绍
意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

何时使用:在处理消息的时候以过滤很多道。

如何解决:拦截的类都实现统一接口。

关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

应用实例:

  1. 红楼梦中的"击鼓传花"。
  2. JS 中的事件冒泡。
  3. JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

优点:

  1. 降低耦合度。它将请求的发送者和接收者解耦。
  2. 简化了对象。使得对象不需要知道链的结构。
  3. 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
  4. 增加新的请求处理类很方便。

缺点:

  1. 不能保证请求一定被接收。
  2. 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
  3. 可能不容易观察运行时的特征,有碍于除错。

使用场景:

  1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
  2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  3. 可动态指定一组对象处理请求。

注意事项:在 JAVA WEB 中遇到很多应用。


实现
我们创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

责任链模式的 UML 图:

步骤 1
创建抽象的记录器类。
AbstractLogger.java

public abstract class AbstractLogger {public static int INFO = 1;public static int DEBUG = 2;public static int ERROR = 3;protected int level;//责任链中的下一个元素protected AbstractLogger nextLogger;public void setNextLogger(AbstractLogger nextLogger){this.nextLogger = nextLogger;}public void logMessage(int level, String message){if(this.level <= level){write(message);}if(nextLogger !=null){nextLogger.logMessage(level, message);}}abstract protected void write(String message);}

步骤 2
创建扩展了该记录器类的实体类。
ConsoleLogger.java

public class ConsoleLogger extends AbstractLogger {public ConsoleLogger(int level){this.level = level;}@Overrideprotected void write(String message) {    System.out.println("Standard Console::Logger: " + message);}
}

ErrorLogger.java

public class ErrorLogger extends AbstractLogger {public ErrorLogger(int level){this.level = level;}@Overrideprotected void write(String message) {    System.out.println("Error Console::Logger: " + message);}
}

FileLogger.java

public class FileLogger extends AbstractLogger {public FileLogger(int level){this.level = level;}@Overrideprotected void write(String message) {    System.out.println("File::Logger: " + message);}
}

步骤 3
创建不同类型的记录器。赋予它们不同的错误级别,并在每个记录器中设置下一个记录器。每个记录器中的下一个记录器代表的是链的一部分。
ChainPatternDemo.java

public class ChainPatternDemo {private static AbstractLogger getChainOfLoggers(){AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);errorLogger.setNextLogger(fileLogger);fileLogger.setNextLogger(consoleLogger);return errorLogger;  }public static void main(String[] args) {AbstractLogger loggerChain = getChainOfLoggers();loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");loggerChain.logMessage(AbstractLogger.DEBUG, "This is a debug level information.");loggerChain.logMessage(AbstractLogger.ERROR, "This is an error information.");}
}

执行程序,输出结果:

Standard Console::Logger: This is an information.
File::Logger: This is a debug level information.
Standard Console::Logger: This is a debug level information.
Error Console::Logger: This is an error information.
File::Logger: This is an error information.
Standard Console::Logger: This is an error information.

3.3.10. 访问者模式(Visitor Pattern)


在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。


介绍
意图:主要将数据结构与数据操作分离。

主要解决:稳定的数据结构和易变的操作耦合问题。

何时使用:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。

如何解决:在被访问的类里面加一个对外提供接待访问者的接口。

关键代码:在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。

应用实例:您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。

优点:

  1. 符合单一职责原则。
  2. 优秀的扩展性。
  3. 灵活性。

缺点:

  1. 具体元素对访问者公布细节,违反了迪米特原则。
  2. 具体元素变更比较困难。
  3. 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

使用场景:

  1. 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。


实现
我们将创建一个定义接受操作的 ComputerPart 接口。Keyboard、Mouse、Monitor 和 Computer 是实现了 ComputerPart 接口的实体类。我们将定义另一个接口 ComputerPartVisitor,它定义了访问者类的操作。Computer 使用实体访问者来执行相应的动作。

VisitorPatternDemo,我们的演示类使用 Computer、ComputerPartVisitor 类来演示访问者模式的用法。

访问者模式的 UML 图:

步骤 1
定义一个表示元素的接口。
ComputerPart.java

public interface ComputerPart {public void accept(ComputerPartVisitor computerPartVisitor);
}

步骤 2
创建扩展了上述类的实体类。
Keyboard.java

public class Keyboard  implements ComputerPart {@Overridepublic void accept(ComputerPartVisitor computerPartVisitor) {computerPartVisitor.visit(this);}
}

Monitor.java

public class Monitor  implements ComputerPart {@Overridepublic void accept(ComputerPartVisitor computerPartVisitor) {computerPartVisitor.visit(this);}
}

Mouse.java

public class Mouse  implements ComputerPart {@Overridepublic void accept(ComputerPartVisitor computerPartVisitor) {computerPartVisitor.visit(this);}
}

Computer.java

public class Computer implements ComputerPart {ComputerPart[] parts;public Computer(){parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};      } @Overridepublic void accept(ComputerPartVisitor computerPartVisitor) {for (int i = 0; i < parts.length; i++) {parts[i].accept(computerPartVisitor);}computerPartVisitor.visit(this);}
}

步骤 3
定义一个表示访问者的接口。
ComputerPartVisitor.java

public interface ComputerPartVisitor {public void visit(Computer computer);public void visit(Mouse mouse);public void visit(Keyboard keyboard);public void visit(Monitor monitor);
}

步骤 4
创建实现了上述类的实体访问者。
ComputerPartDisplayVisitor.java

public class ComputerPartDisplayVisitor implements ComputerPartVisitor {@Overridepublic void visit(Computer computer) {System.out.println("Displaying Computer.");}@Overridepublic void visit(Mouse mouse) {System.out.println("Displaying Mouse.");}@Overridepublic void visit(Keyboard keyboard) {System.out.println("Displaying Keyboard.");}@Overridepublic void visit(Monitor monitor) {System.out.println("Displaying Monitor.");}
}

步骤 5
使用 ComputerPartDisplayVisitor 来显示 Computer 的组成部分。
VisitorPatternDemo.java

public class VisitorPatternDemo {public static void main(String[] args) {ComputerPart computer = new Computer();computer.accept(new ComputerPartDisplayVisitor());}
}

执行程序,输出结果:

Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.

3.3.11. 状态模式(State Pattern)


在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。

在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。


介绍
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。

何时使用:代码中包含大量与对象状态有关的条件语句。

如何解决:将各种具体的状态类抽象出来。

关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。

应用实例:

  1. 打篮球的时候运动员可以有正常状态、不正常状态和超常状态。
  2. 曾侯乙编钟中,‘钟是抽象接口’,'钟A’等是具体状态,'曾侯乙编钟’是具体环境(Context)。

优点:

  1. 封装了转换规则。
  2. 枚举可能的状态,在枚举状态之前需要确定状态种类。
  3. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  4. 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  5. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点:

  1. 状态模式的使用必然会增加系统类和对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  3. 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

使用场景:

  1. 行为随状态改变而改变的场景。
  2. 条件、分支语句的代替者。

注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。


实现
我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。

StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。

状态模式的 UML 图:

步骤 1
创建一个接口。
State.java

public interface State {public void doAction(Context context);
}

步骤 2
创建实现接口的实体类。
StartState.java

public class StartState implements State {public void doAction(Context context) {System.out.println("Player is in start state");context.setState(this); }public String toString(){return "Start State";}
}

StopState.java

public class StopState implements State {public void doAction(Context context) {System.out.println("Player is in stop state");context.setState(this); }public String toString(){return "Stop State";}
}

步骤 3
创建 Context 类。
Context.java

public class Context {private State state;public Context(){state = null;}public void setState(State state){this.state = state;     }public State getState(){return state;}
}

步骤 4
使用 Context 来查看当状态 State 改变时的行为变化。
StatePatternDemo.java

public class StatePatternDemo {public static void main(String[] args) {Context context = new Context();StartState startState = new StartState();startState.doAction(context);System.out.println(context.getState().toString());StopState stopState = new StopState();stopState.doAction(context);System.out.println(context.getState().toString());}
}

执行程序,输出结果:

Player is in start state
Start State
Player is in stop state
Stop State

4. 完结,撒花✌✌✌

Java设计模式大全相关推荐

  1. java设计模式面试题大全含答案

    java设计模式面试题大全含答案 1.23种经典设计模式都有哪些,如何分类? 2.j2ee常用的设计模式?说明工厂模式. 3.Spring 框架中都用到了哪些设计模式? 4.<java面试宝典& ...

  2. java设计模式面试,深入分析

    正文 一些看到我文章的朋友,问我怎么零基础Java"逆袭",又怎么学好Java. 以下是我的一些经验总结吧: 1.制定好一下系统的学习规划,每天定量,学完什么知识点就掌握,能自己应 ...

  3. 【Java设计模式】组合模式

    转自:  https://blog.csdn.net/qq_42322103/article/details/95457321 漫谈网站优化提速: https://blog.csdn.net/mete ...

  4. 外国程序员整理的Java资料大全

    2019独角兽企业重金招聘Python工程师标准>>> 外国程序员整理的Java资料大全 博客分类: java 构建 这里搜集了用来构建应用程序的工具. Apache Maven:M ...

  5. Java资源大全中文版

    Java资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理.awesome-java 就是 akullpp 发起维护的 Java 资源列表,内容 ...

  6. Java资源大全中文版-Awesome - java

    本文来自GitHub 上 Awesome - java 系列的资源整理. awesome-java是 akullpp 发起维护的 Java 资源列表,内容包括:构建工具.数据库.框架.模板.安全.代码 ...

  7. Java资源大全中文版(转)

    Java资源大全中文版(Awesome最新版) Awesome系列的Java资源整理.awesome-java 就是akullpp发起维护的Java资源列表,内容包括:构建工具.数据库.框架.模板.安 ...

  8. Java 超全超强大的Java网站大全

    1. The Source for Java Technology Collaboration(JAVA开发的官方网站)  http://www.java.net/ - 外文 2. jGuru: FA ...

  9. java备忘录模式应用场景_图解Java设计模式之备忘录模式

    图解Java设计模式之备忘录模式 游戏角色状态恢复问题 游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态. ...

最新文章

  1. java超出gc开销限制_超出了GC开销限制– Java堆分析
  2. 赛门铁克运维注意事项
  3. r语言 不同长度 list 转 dataframe_解决R效率问题—parallel多线程启动
  4. linux 终端最大化命令,11个让你吃惊的Linux终端命令
  5. SQL Server中的T-SQL RegEx命令
  6. 20200209:匹配子序列的单词数(leetcode792)
  7. Android5.1 bootchart在Mac使用说明(OK)
  8. 安装ADB驱动 ADB相关问题
  9. Android电话本实现
  10. 微型计算机偏移地址,请高手讲解一下在微机原理里面,偏移地址,段地址,实体地址之间的关系!配合图解更好,谢谢!...
  11. Nagios下载安装配置
  12. java怎么输出无损高清音乐_MP3转换器如何将无损FLAC音频转换成MP3音乐
  13. 复杂UI卡顿问题没想到还能这么优化?
  14. 商务周刊封面:别了,摩托罗拉
  15. 积分墙、广告等违规应用如何在安卓市场上线
  16. d3d纹理过滤器配置
  17. 如何完整的修改一个数据库的名称
  18. linux如何配浏览器证书,部署国密SSL证书,如何兼容国际主流浏览器?
  19. ENVI中使用水体指数法NDWI提取水体
  20. 使用第三方GitLab进行登录认证

热门文章

  1. EXCEL如何使用查找函数vlookup
  2. 红黑树详解,对插入旋转独到理解
  3. socket编程(在线自动聊天工具) --Python3
  4. 计算机主机不通电,电脑主机不通电无法开机怎么解决
  5. ECCV 2022 | 新方案: 先剪枝再蒸馏
  6. Mysql访问日志记录
  7. 科学计算机算e的连续复利,自然对数e的证明和货币基金连续复利的真相
  8. 关于无法显示特殊汉字的问题
  9. 利用闲置的树莓派4B搭建一个NAS(二)
  10. 对接顺丰(丰桥)查询物流信息