echo

任生命穿梭 时间的角落

0%

设计模式

创建型模式

对象怎么来

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

工厂模式

  工厂模式(Factory Pattern)是Java 中最常用的设计模式之一。提供了一种创建对象大的最佳方式。我们在创建对象时不会对客户端暴露创建逻辑,通过一个共同的接口来指向新创建的对象。

 

介绍

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

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

  何时使用:不同条件下创建不同实例时。

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

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

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

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

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

  注意事项:复杂对象生成适合使用工厂模式,简单对象直接new。

实现

  我们将创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactoryFactoryPatternDemo*,我们的演示类使用 *ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。

img

 1、创建一个Shape接口

1
2
3
public interface Shape {
void draw();
}

  2、创建 Rectangle、Circle和Square 实体类实现Shape接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Rectangle implements Shape {

@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Circle::draw()");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Square::draw()");
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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("square")){
return new Square();
}else if(shapeType.equalsIgnoreCase("rectangle")){
return new Rectangle();
}else{
return null;
}
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FactoryPatternDemo {
public static void main(String[] args) {
ShapeFactory sf = new ShapeFactory();

Shape s1 = sf.getShape("circle");
if(s1 != null)s1.draw();

Shape s2 = sf.getShape("rectangle");
if(s2 != null)s2.draw();

Shape s3 = sf.getShape("square");
if(s3 != null)s3.draw();

}
}

  输出:

  Circle::draw()
  Rectangle::draw()
  Square::draw()

  在已知获取对象的类型下,我们可以将Shape 对象强制转换为 Circle、Rectangle和Square对象,来使用特定实体类的特有方法。

参考:https://www.runoob.com/design-pattern/factory-pattern.html

抽象工厂模式

  抽象工厂模式(Abstract Factory Pattrn)是围绕一个超级工厂创建其他工厂。该超级工厂称为其他工厂的工厂。提供了一种创建对象的最佳方式。

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

 

介绍

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

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

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

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

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

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

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

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

  使用场景 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。

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

实现

  我们将创建 ShapeColor 接口和实现这些接口的实体类。下一步是创建抽象工厂类 AbstractFactory*。接着定义工厂类 *ShapeFactoryColorFactory,这两个工厂类都是扩展了 *AbstractFactory。然后创建一个工厂创造器/生成器类 FactoryProducer*AbstractFactoryPatternDemo*,我们的演示类使用 *FactoryProducer 来获取 AbstractFactory 对象。它将向 AbstractFactory 传递形状信息 ShapeCIRCLE / RECTANGLE / SQUARE*),以便获取它所需对象的类型。同时它还向 *AbstractFactory 传递颜色信息 ColorRED / GREEN / BLUE),以便获取它所需对象的类型。

img

 1、新建Shape,Color两个接口

1
2
3
4
5
6
public interface Shape {
void draw();
}
public interface Color {
void fill();
}

  2、为这两个接口分别添加几个实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Rectangle implements Shape {

@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}

public class Circle implements Shape {

@Override
public void draw() {
System.out.println("Circle::draw()");
}

}

public class Square implements Shape {

@Override
public void draw() {
System.out.println("Square::draw()");
}
}
public class Blue implements Color {
@Override
public void fill() {
System.out.println("Blue::fill()");
}
}

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

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

  3、为 Color 和 Shape 对象创建抽象类来获取工厂。

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

  4、创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ShapeFactory extends AbstractFactory{
@Override
public Color getColor(String color) {
return null;
}

//使用getShape方法获取形状对象。
@Override
public Shape getShape(String shape) {
if(shape == null){
return null;
}
if(shape.equalsIgnoreCase("circle")){
return new Circle();
}else if(shape.equalsIgnoreCase("square")){
return new Square();
}else if(shape.equalsIgnoreCase("rectangle")){
return new Rectangle();
}
return null;
}

}
public class ColorFactory extends AbstractFactory {

@Override
public Shape getShape(String shapeType){
return null;
}

@Override
public 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;
}
}

  5、创建一个工厂创造器/生成器类,通过传递形状或颜色信息来获取工厂(工厂的工厂)。

1
2
3
4
5
6
7
8
9
10
11
public class FactoryProducer {

public static AbstractFactory getFactory(String choice){
if(choice.equalsIgnoreCase("color")){
return new ColorFactory();
}else if(choice.equalsIgnoreCase("shape")){
return new ShapeFactory();
}
return null;
}
}

  6、使用 FactoryProducer 来获取 AbstractFactory,通过传递类型信息来获取实体类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class AbstractFactoryDemo {
public static void main(String[] args) {

//获取形状工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory("shape");

if(shapeFactory != null){
Shape s1 = shapeFactory.getShape("circle");
if(s1 != null)s1.draw();

Shape s2 = shapeFactory.getShape("rectangle");
if(s2 != null)s2.draw();

Shape s3 = shapeFactory.getShape("square");
if(s3 != null)s3.draw();
}

//获取颜色工厂
AbstractFactory colorFactory = FactoryProducer.getFactory("color");

if(colorFactory != null){
Color c1 = colorFactory.getColor("red");
if(c1 != null)c1.fill();

Color c2 = colorFactory.getColor("blue");
if(c2 != null)c2.fill();

Color c3 = colorFactory.getColor("green");
if(c3 != null)c3.fill();
}

}
}

   输出: 

  Circle::draw()
  Rectangle::draw()
  Square::draw()
  Red::fill()
  Blue::fill()
  Green::fill()

参考:https://www.runoob.com/design-pattern/abstract-factory-pattern.html

单例模式

  单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  注意:1)单例类中只能有一个实例

     2)单例类必须自己创建自己的唯一实例

     3)单例类必须给所有其他对象提供这一实例

介绍

  意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  主要解决:一个全局使用的类频繁地创建与销毁。

  何时使用:当您想控制实例数目,节省系统资源的时候。

  如何解决:判断系统是否已经有这个单例,如果有则返回,没有则创建。

  关键代码:构造函数是私有的。

  应用实例:一个班级只有一个班主任。

  优点:1)在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。2)避免对资源的多重占用(比如写文件操作)。

  缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

  应用场景:1)要求生产唯一序列号。2)WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。3)创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

实现

  我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo*,我们的演示类使用 *SingleObject 类来获取 SingleObject 对象。

img

 1、创建一个Singleton 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SingleObject {

private static SingleObject instance = new SingleObject();

private SingleObject(){}

public static SingleObject getInstance(){
return instance;
}

public void showMessage(){
System.out.println("hello world!");
}
}

  2、从 Singleton 类获取唯一的对象。

1
2
3
4
5
6
7
8
9
10
public class SingletonPatternDemo {

public static void main(String[] args) {
//不合法,构造函数不可见
//SingleObject so = new SingleObject();

SingleObject so = SingleObject.getInstance();
so.showMessage();
}
}

  输出:hello world!

单例模式的几种实现方式

  1、懒汉式不加锁,线程不安全。可能会有多个进程进入到 if 语句内,创建多个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* 懒加载,线程不安全。
* */
public class SingletonLazyUnlocked {
private static SingletonLazyUnlocked instance;

private SingletonLazyUnlocked(){}

public static SingletonLazyUnlocked getInstance(){
if(instance != null){
instance = new SingletonLazyUnlocked();
}
return instance;
}
}

  2、懒汉式加锁,线程安全。加synchronized影响效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* 懒加载,线程安全。
* */
public class SingletonLazyLocked {
private static SingletonLazyLocked instance;

private SingletonLazyLocked(){}

public static synchronized SingletonLazyLocked getInstance(){
if(instance != null){
instance = new SingletonLazyLocked();
}
return instance;
}
}

  3、饿汉式,线程安全。类加载时创建实例,容易产生垃圾对象。

1
2
3
4
5
6
7
8
9
10
11
12
/*
* 非懒加载,线程安全。
* */
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();

private SingletonHungry(){}

public static SingletonHungry getInstance(){
return instance;
}
}

  4、双重校验锁(Double-Checked Locking)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* 懒加载,线程安全
* */
public class SingletonDCL {
private volatile static SingletonDCL instance;

private SingletonDCL(){}

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

}

  第一次校验:如果创建了实例再去执行getInstance()方法不会去竞争锁,直接返回实例。

  第二次校验:防止二次创建实例。如果没有第二个判空语句,在实例还没有创建的情况下,A 和 B 两个线程都要获得单例的对象。A ,B 都进入了第一个判空语句内,然后争夺锁,A获得了锁,实例化了一个对象,然后释放了锁,B获得了锁,由于没有第二个判空语句B 又创建了一次对象。

  volatile 关键字的作用:防止指令重排优化。保证变量在多线程运行时的可见性,无法保证原子性。

  5、静态内部类

  对静态域使用延迟初始化利用类加载机制来保证初始化 instance 时只有一个线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* 静态内部类懒加载
* 懒加载,线程安全
* */
public class SingletonRegister {
private static class SingletonHolder{
private static final SingletonRegister INSTANCE = new SingletonRegister();
}

private SingletonRegister(){}

public static final SingletonRegister getInstance(){
return SingletonHolder.INSTANCE;
}
}

  

  6、枚举

  实现单例的最佳方法。更简洁,自动支持序列化机制防止多次实例化。

1
2
3
4
5
6
public enum SingletonEnum {
INSTANCE;
public void method(){

}
}

参考:https://www.runoob.com/design-pattern/singleton-pattern.html

建造者模式

  建造者模式(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 类,带有 ItemArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilderBuilderPatternDemo*,我们的演示类使用 *MealBuilder 来创建一个 Meal。

img

 1、创建一个表示食物条目和食物包装的接口

1
2
3
4
5
6
7
8
public interface Item {
public String name();
public Packing packing();
public float price();
}
public interface Packing {
public String pack(); //返回包装的名字
}

  2、创建实现 Packing 接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* 包装纸类
* */
public class Wrapper implements Packing {
@Override
public String pack() {
return "Wrapper";
}
}
/*
* 杯子类
* */
public class Bottle implements Packing {
@Override
public String pack() {
return "Bottle";
}
}

  3、创建实现 Item 接口的抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* 汉堡抽象类
* 汉堡都是用包装纸包装,价格不同。
* */
public abstract class Burger implements Item{

@Override
public Packing packing() {
return new Wrapper();
}

@Override
public abstract float price();
}
/*
* 冷饮抽象类,所有冷饮都是用杯子装,价格不同。
* */
public abstract class ColdDrink implements Item{

@Override
public Packing packing() {
return new Bottle();
}

@Override
public abstract float price();
}

  4、创建扩展了 Burger 和 ColdDrink 的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class VegBurger extends Burger {

@Override
public float price() {
return 25.0f;
}

@Override
public String name() {
return "Veg Burger";
}
}
public class ChickenBurger extends Burger {

@Override
public float price() {
return 50.5f;
}

@Override
public String name() {
return "Chicken Burger";
}
}
public class Coke extends ColdDrink {

@Override
public float price() {
return 30.0f;
}

@Override
public String name() {
return "Coke";
}
}
public class Pepsi extends ColdDrink {

@Override
public float price() {
return 35.0f;
}

@Override
public String name() {
return "Pepsi";
}
}

  5、创建一个 Meal 类,带有上面定义的 Item 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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、BuilderPatternDemo 使用 MealBuilder 来演示建造者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BuilderPatterDemo {
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("Non-Veg meal");
nonVegMeal.showItems();
System.out.println("Total Cost: "+nonVegMeal.getCost());
}
}

  输出:

img

参考:https://www.runoob.com/design-pattern/builder-pattern.html

原型模式

  原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种模式实现了一个原型接口,该接口用于创建当前对象的克隆。当创建对象的代价比较大时,则采用这种模式。

介绍

  意图:用原型实例制定创建对象的种类,并且通过拷贝这些原型创建新的对象。

  主要解决:在运行期建立和删除原型。

  何时使用:1、当一个系统应独立于它的产品创建,构成和表示时。2、当要实例化的类是在运行时刻指定时,例如,通过动态加载。3、为了避免创建一个与产品类层次平行的工厂类层次时。4、当一个类的实例只能有几个不同状态中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便。

  如何解决:利用已有的一个原型对象,快速地生成和原型对象一样地实例。

  关键代码:1、实现克隆操作,在JAVA 中继承 Cloneable ,重写Clone()。2、原型模式同样用于隔离类对象地使用者和具体类型(易变类)之间地耦合关系,它同样要求这些“易变类”拥有稳定的接口。

  应用实例:1、细胞分裂。2、JAVA 中的Object clone() 方法。

  优点:1、性能提高。2、逃避构造函数的约束。

  缺点:1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。

  使用场景 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

  注意事项与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。

  

实现

  我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache*,该类把 shape 对象存储在一个 *Hashtable 中,并在请求的时候返回它们的克隆。

PrototypePatternDemo*,我们的演示类使用 *ShapeCache 类来获取 Shape 对象。

img

 1、创建一个实现了 Cloneable 接口的抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract class Shape implements Cloneable {
private String id;
protected String type;

public abstract void draw();

public String getId() {
return id;
}

public String getType() {
return type;
}

public void setId(String id) {
this.id = id;
}

public Object clone(){
Object cloned = null;
try {
cloned = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return cloned;
}

}

  2、创建继承了上面抽象类的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Square extends Shape {

public Square(){
type = "Square";
}

@Override
public void draw() {
System.out.println("Square::draw()");
}
}
public class Rectangle extends Shape {

public Rectangle(){
type = "Rectangle";
}

@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}
public class Circle extends Shape {

public Circle(){
type = "Circle";
}

@Override
public void draw() {
System.out.println("Circle::draw()");
}
}

  3、创建一个类,从数据库中获取实体类,并把它们存储在一个 ConcurrentHashMap 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.concurrent.ConcurrentHashMap;

public class ShapeCache {

private static ConcurrentHashMap<String,Shape> shapeMap = new ConcurrentHashMap<>();

public static Shape getShape(String shapeId){
Shape cachedShape = shapeMap.get(shapeId);
return (Shape)cachedShape.clone();
}

//对每种形状进行数据库查询,并创建该形状。
//假设创建三种形状。
public static void loadCache(){
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);

Rectangle rectangle = new Rectangle();
rectangle.setId("2");
shapeMap.put(rectangle.getId(),rectangle);

Square square = new Square();
square.setId("3");
shapeMap.put(square.getId(),square);
}
}

  4、PrototypePatternDemo 使用 ShapeCache 类来获取存储在 ConcurrentHashMap 中的形状的克隆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PrototypePatternDemo {

public static void main(String[] args) {
ShapeCache.loadCache();

Shape clonedShape = (Shape)ShapeCache.getShape("1");
System.out.println("Shape: "+clonedShape.getType());

Shape clonedShape2 = (Shape)ShapeCache.getShape("2");
System.out.println("Shape: "+clonedShape2.getType());

Shape clonedShape3 = (Shape)ShapeCache.getShape("3");
System.out.println("Shape: "+clonedShape3.getType());
}
}

  输出:

img

参考:https://www.runoob.com/design-pattern/prototype-pattern.html

结构型模式

对象和谁有关

这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

适配器模式

  适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。例如,读卡器是内存卡和电脑之间的适配器。

介绍

意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

主要解决:主要解决在软件系统中,常常要将一些”现存的对象”放到新的环境中,而新环境要求的接口是现对象不能满足的。

何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)

如何解决:继承或依赖(推荐)。

关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。

应用实例: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。

优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。

缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

实现

我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。

我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。

我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter*,并使用 *AdvancedMediaPlayer 对象来播放所需的格式。

AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo*,我们的演示类使用 *AudioPlayer 类来播放各种格式。

img

 1、为媒体播放器和更高级的媒体播放器创建接口

1
2
3
4
5
6
7
8
9
public interface MediaPlayer {
public void play(String audioType,String fileName);

}
public interface AdvancedMediaPlayer {

public void playVlc(String fileName);
public void playMp4(String fileName);
}

  2、创建实现了 AdvancedMediaPlayer 接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Mp4Player implements AdvancedMediaPlayer {

@Override
public void playVlc(String fileName) {

}

@Override
public void playMp4(String fileName) {
System.out.println("Playing Mp4 file. name: "+fileName);
}
}
public class VlcPlayer implements AdvancedMediaPlayer {

@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. name: "+fileName);
}

@Override
public void playMp4(String fileName) {

}
}

  3、创建实现了 MediaPlayer 接口的适配器类(可以播放视频)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MediaAdapter implements MediaPlayer {

AdvancedMediaPlayer advancedMusicPlayer;

public MediaAdapter(String audioType){
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer = new VlcPlayer();
} else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer = new Mp4Player();
}
}

@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer.playVlc(fileName);
}else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer.playMp4(fileName);
}
}
}

  4、创建实现了 MediaPlayer 接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AudioPlayer implements MediaPlayer {

private MediaAdapter mediaAdapter; //添加一个媒体MediaAdapter 使得AudioPlayer 支持播放视频。

@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("mp3")){
System.out.println("Playing mp3 file. name: "+fileName);
}

else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")){
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType,fileName);
}else{
System.out.println("Invalid media. "+audioType+" format not supported");
}
}
}

  5、使用 AudioPlayer 来播放不同类型的音频格式。

1
2
3
4
5
6
7
8
9
10
11
public class AdapterPatternDemo {

public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();

audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}

  输出:

img

参考:https://www.runoob.com/design-pattern/adapter-pattern.html

桥接模式

桥接(Bridge)是用于把抽象化与实现化解耦,使二者可以独立变化。它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

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

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

介绍

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

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

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

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

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

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

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

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

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

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

实现

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

img

1、创建桥接接口

1
2
3
public interface DrawAPI {
public void draw(int radius, int x, int y);
}

2、创建实现了 DrawAPI 接口的实体桥接实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BlueCircle implements DrawAPI {

@Override
public void draw(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: blue, radius: "+radius+", x: "+x+", y: "+y+"]");
}
}
public class RedCircle implements DrawAPI {

@Override
public void draw(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: "+radius+", x: "+x+", y: "+y+"]");
}
}

3、使用 DrawAPI 接口创建抽象类 Shape

1
2
3
4
5
6
7
8
public abstract class Shape {
protected DrawAPI drawAPI;

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

4、创建继承了 Shape 的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Circle extends Shape {

private int x, y, radius;

public Circle(int radius, int x, int y, DrawAPI drawAPI){
super(drawAPI);
this.radius = radius;
this.x = x;
this.y = y;
}

public void draw(){
drawAPI.draw(radius,x,y);
}
}

5、使用 Shape 和 DrawAPI 类画出不同的圆

1
2
3
4
5
6
7
8
9
public class BridgePatternDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(100,100,10,new RedCircle());
Shape blueCircle = new Circle(100,100,10,new BlueCircle());

redCircle.draw();
blueCircle.draw();
}
}

输出:

img

我们将Circle的抽象和具体的实现分离开,DrawAPI 作为桥接接口,具体的实现类实现 DrawAPI 的接口,当需要画各种类型的圆时通过 DrawAPI 来实现。对比传统的 RedCircle 继承 Circle 类 的方法比较起来,当继承 Circle 类的实体类多起来之后更加容易维护。

参考:https://www.runoob.com/design-pattern/bridge-pattern.html

过滤器模式

过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种结构型模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把他们连接起来,它结合多个标准来获得单一标准。

实现

我们将创建一个 Person 对象、Cirteria 接口 和实现了该接口的实体类,来过滤 Person 对象的列表。CriteriaPatternDemo,我们的演示类使用 Criteria 对象,基于各种标准和它们的结合来过滤对象的列表。

img

1、创建一个 Person 类,在该类上应用标准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Person {

private String name;
private String gender;
private String maritalStatus;

public Person(String name, String gender, String maritalStatus) {
this.name = name;
this.gender = gender;
this.maritalStatus = maritalStatus;
}

public String getName() {
return name;
}

public String getGender() {
return gender;
}

public String getMaritalStatus() {
return maritalStatus;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", maritalStatus='" + maritalStatus + '\'' +
'}';
}
}

2、为标准(Criteria)创建一个接口

1
2
3
4
5
6
import java.util.List;

public interface Criteria {

public List<Person> meetCriteria(List<Person> persons);
}

3、创建实现了 Criteria 接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import java.util.ArrayList;
import java.util.List;

public class CriteriaMale implements Criteria {

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> malePersons = new ArrayList<>();
for (Person person : persons) {
if(person.getGender().equalsIgnoreCase("MALE")){
malePersons.add(person);
}
}
return malePersons;
}

}
import java.util.ArrayList;
import java.util.List;

public class CriteriaFemale implements Criteria {

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> femalePersons = new ArrayList<>();
for (Person person : persons) {
if(person.getGender().equalsIgnoreCase("FEMALE")){
femalePersons.add(person);
}
}
return femalePersons;
}
}
import java.util.ArrayList;
import java.util.List;

public class CriteriaSingle implements Criteria {

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> singlePersons = new ArrayList<>();
for (Person person : persons) {
if(person.getMaritalStatus().equalsIgnoreCase("SINGLE")){
singlePersons.add(person);
}
}
return singlePersons;
}
}
import java.util.List;

public class AndCriteria implements Criteria {

private Criteria criteria;
private Criteria otherCriteria;

public AndCriteria(Criteria criteria, Criteria otherCriteria) {
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> firstCriteriaPersons = criteria.meetCriteria(persons);
return otherCriteria.meetCriteria(firstCriteriaPersons);
}
}
import java.util.List;

public class OrCriteria implements Criteria {
private Criteria criteria;
private Criteria otherCriteria;

public OrCriteria(Criteria criteria, Criteria otherCriteria) {
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> firstCriteriaItems = criteria.meetCriteria(persons);
List<Person> otherCriteriaItems = otherCriteria.meetCriteria(persons);

for (Person person : otherCriteriaItems) {
if(! firstCriteriaItems.contains(person)){
firstCriteriaItems.add(person);
}
}
return firstCriteriaItems;
}
}

4、使用不同的标准(Criteria)和它们的结合来过滤 Person 对象的列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class CriteriaPatternDemo {
public static void main(String[] args) {
List<Person> persons = new ArrayList<>();
persons.add(new Person("Robert","Male", "Single"));
persons.add(new Person("John","Male", "Married"));
persons.add(new Person("Laura","Female", "Married"));
persons.add(new Person("Diana","Female", "Single"));
persons.add(new Person("Mike","Male", "Single"));
persons.add(new Person("Bobby","Male", "Single"));

Criteria male = new CriteriaMale();
Criteria female = new CriteriaFemale();
Criteria single = new CriteriaSingle();
Criteria singleMale = new AndCriteria(single,male);
Criteria singleOrFemale = new OrCriteria(single,female);

System.out.println("Males: ");
printPersons(male.meetCriteria(persons));

System.out.println("Females: ");
printPersons(female.meetCriteria(persons));

System.out.println("Single Males: ");
printPersons(singleMale.meetCriteria(persons));

System.out.println("Single Or Females: ");
printPersons(singleOrFemale.meetCriteria(persons));
}

public static void printPersons(List<Person> persons){
for (Person person : persons) {
System.out.println(person);
}
}
}

输出:

img

我们为 Person 建造了多个 Criteria 并利用它们进行过滤。我们还可以将这些标准进行逻辑操作,使用多个标准来对对象进行过滤。

参考:https://www.runoob.com/design-pattern/filter-pattern.html

组合模式

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

介绍

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

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

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

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

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

应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作数、操作符和另一个操作数。 2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。

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

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

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

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

实现

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

1、创建 Employee 类,该类带有 Employee 对象的列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 salary) {
this.name = name;
this.dept = dept;
this.salary = salary;
this.subordinates = new ArrayList<>();
}

public void add(Employee employee){
subordinates.add(employee);
}

public void remove(Employee employee){
subordinates.remove(employee);
}

public List<Employee> getSubordinates() {
return subordinates;
}

@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", dept='" + dept + '\'' +
", salary=" + salary +
'}';
}
}

2、使用 Employee 类来创建和打印员工的层次结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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+"\n");

for (Employee headEmployee : CEO.getSubordinates()) {
System.out.println(headEmployee);

for (Employee e : headEmployee.getSubordinates()) {
System.out.println(e);
}
       System.out.println("");
} } }

输出:

CEO 直接管理销售主管和市场主管,销售主管和市场主管又分别管理自己手下的员工。

参考:https://www.runoob.com/design-pattern/composite-pattern.html

装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

介绍

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:在不想增加很多子类的情况下扩展类。

如何解决:将具体功能职责划分,同时继承装饰者模式。

关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。

应用实例: 1、孙悟空有 72 变,当他变成”庙宇”后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:多层装饰比较复杂。

使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。

注意事项:可代替继承。

实现

我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator*,并把 *Shape 对象作为它的实例变量。

RedShapeDecorator 是实现了 ShapeDecorator 的实体类。

DecoratorPatternDemo*,我们的演示类使用 *RedShapeDecorator 来装饰 Shape 对象。

img

1、创建一个接口

1
2
3
public interface Shape {
void draw();
}

2、创建实现接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Rectangle implements Shape {

@Override
public void draw() {
System.out.println("Shape: Rectangle");
}
}
public class Circle implements Shape {

@Override
public void draw() {
System.out.println("Shape: Circle");
}
}

3、创建实现了 Shape 接口的抽象装饰类

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class ShapeDecorator implements Shape{
protected Shape shapeDecorator;

public ShapeDecorator(Shape shapeDecorator) {
this.shapeDecorator = shapeDecorator;
}

@Override
public void draw() {
shapeDecorator.draw();
}
}

4、创建扩展了 ShapeDecorator 类的实体装饰类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RedShapeDecorator extends ShapeDecorator {

public RedShapeDecorator(Shape shapeDecorator) {
super(shapeDecorator);
}

@Override
public void draw() {
super.draw();
setRedBorder(shapeDecorator);
}

private void setRedBorder(Shape decoratedShape){
System.out.println("Board Color: Red");
}
}

5、使用 RedShapeDecorator 来装饰 Shape 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DecoratorPatternDemo {

public static void main(String[] args) {
Shape circle = new Circle();
ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());

System.out.println("Circle with normal boarder");
circle.draw();

System.out.println("\nCircle of red boarder");
redCircle.draw();

System.out.println("\nRectangle of red boarder");
redRectangle.draw();
}
}

输出

img

我们使用 RedShapeDecorator 装饰类来对 Shape 对象扩展红色的功能,而不是使用RedCircle 继承 Circle 这种方法,避免了子类膨胀。

参考:https://www.runoob.com/design-pattern/decorator-pattern.html

外观模式

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。它向现有的系统添加一个接口,来隐藏系统的复杂性。该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。

介绍

意图:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。

何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个”接待员”即可。 2、定义系统的入口。

如何解决:客户端不与系统耦合,外观类与系统耦合。

关键代码:在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。

应用实例: 1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。 2、JAVA 的三层开发模式。

优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。

缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。

注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口。

实现

我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。下一步是定义一个外观类 ShapeMaker

ShapeMaker 类使用实体类来代表用户对这些类的调用。FacadePatternDemo*,我们的演示类使用 *ShapeMaker 类来显示结果。

img

1、创建一个接口

1
2
3
public interface Shape {
void draw();
}

2、创建实现接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Cricle::draw()");
}
}
public class Rectangle implements Shape{
@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Square::draw()");
}
}

3、创建一个外观类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ShapeMaker {
private Shape circle;
private Shape rectangle;
private Shape square;

public ShapeMaker(){
circle = new Circle();
square = new Square();
rectangle = new Rectangle();
}

public void drawCircle(){
circle.draw();
}

public void drawRectangle(){
rectangle.draw();
}

public void drawSquare(){
square.draw();
}
}

4、使用该外观类画出各种类型的形状

1
2
3
4
5
6
7
8
9
public class FacadePatternDemo {
public static void main(String[] args) {
ShapeMaker shapeMaker = new ShapeMaker();

shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();
}
}

输出

img

我们通过 ShapeMaker 类屏蔽了底层的类的实现,仅仅调用接口就可画出各种形状。

参考:https://www.runoob.com/design-pattern/facade-pattern.html

享元模式

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。它减少对象数量从而改善应用所需的对象结构的方式。享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建对象。

介绍

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

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

何时使用: 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),以便获取它所需对象的颜色。

img

1、创建一个接口

1
2
3
public interface Shape {
void draw();
}

2、创建实现接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;
}

@Override
public void draw() {
System.out.println("circle::draw() [color: "+color+", x: "+x+", y: "+y+", radius: "+radius+"]");
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.HashMap;

public class ShapeFactory {
private static final HashMap<String,Shape> circleMap = new HashMap<>();

public static Shape getShape(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、使用该工厂,通过传递颜色信息来获取实体类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class FlyWeightDemo {

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.getShape(getRandomColor());
circle.setRadius(100);
circle.setX(getRandomX());
circle.setY(getRandomY());
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);
}
}

输出

img

我们重用了 key 为红色的圆。

代理模式

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。

优点: 1、职责清晰。 2、高扩展性。 3、智能化。

缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

实现

我们将创建一个 Image 接口和实现了 Image 接口的实体类。ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。

ProxyPatternDemo*,我们的演示类使用 *ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。

img

1、创建一个接口

1
2
3
public interface Image {
void display();
}

2、创建实现接口得实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class RealImage implements Image {
private String filename;

public RealImage(String filename){
this.filename = filename;
loadFromDisk(filename);
}

@Override
public void display() {
System.out.println("Displaying "+filename);
}

private void loadFromDisk(String filename){
System.out.println("Loading "+filename);
}
}
public class ProxyImage implements Image {
private RealImage realImage;
private String filename;

public ProxyImage(String filename) {
this.filename = filename;
}

@Override
public void display() {
if(realImage == null){
realImage = new RealImage(filename);
}
realImage.display();
}
}

3、当被请求时,使用 ProxyImage 来获取 RealImage 类的对象。

1
2
3
4
5
6
7
8
9
10
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test.jpg");

//从磁盘加载
image.display();
//不需要从磁盘加载
image.display();
}
}

输出:

img

参考:https://www.runoob.com/design-pattern/proxy-pattern.html

行为型模式

对象与对象在干嘛

这些设计模式特别关注对象之间的通信。

责任链模式

责任链模式(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。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

img

1、创建抽象的记录器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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、创建扩展了该记录器的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ConsoleLogger extends AbstractLogger {
public ConsoleLogger(int level){
this.level = level;
}

@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: "+message);
}
}
public class ErrorLogger extends AbstractLogger {

public ErrorLogger(int level){
this.level = level;
}

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

@Override
protected void write(String message) {
System.out.println("File::Logger: "+message);
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.");

}
}

输出

img

责任链 Error – File – Console

参考:https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html

命令模式

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

介绍

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

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

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

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

关键代码:定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口

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

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

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

使用场景:认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。

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

实现

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

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

img

1、创建一个命令接口

1
2
3
public interface Order {
void execute();
}

2、创建一个请求类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Stock {

private String name = "ABC";
private int quantity = 10;

public void buy(){
System.out.println("Stick [ Name: "+name+",Quantity: "+quantity+" ] bought");
}

public void sell(){
System.out.println("Stick [ Name: "+name+",Quantity: "+quantity+" ] sold");
}
}

3、创建实现了 Order 接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BuyStock implements Order{
private Stock abcStock;

public BuyStock(Stock abcStock){
this.abcStock = abcStock;
}

@Override
public void execute(){
abcStock.buy();
}
}
public class SellStock implements Order {
private Stock abcStock;

public SellStock(Stock abcStock) {
this.abcStock = abcStock;
}

@Override
public void execute() {
abcStock.sell();
}
}

4、创建命令调用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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类来接收并执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}
}

输出

img

参考:https://www.runoob.com/design-pattern/command-pattern.htm

解释器模式

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

介绍

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

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

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

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

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

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

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

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

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

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

实现

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

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

img

1、创建一个表达式接口

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

2、创建实现了上述接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class TerminalExpression implements Expression {

private String data;

public TerminalExpression(String data) {
this.data = data;
}

@Override
public boolean interpret(String context) {
if(context.contains(data)){
return true;
}
return false;
}
}
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;
}

@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
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;
}

@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}

3、InterpreterPatternDemo 使用 Expression 类来创建规则,并解析它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class InterpreterPatternDemo {
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert,john);
}

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"));
}
}

输出:

img

参考:https://www.runoob.com/design-pattern/interpreter-pattern.html

迭代器模式

迭代器模式(Iterator Pattern )用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

介绍

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

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

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

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

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

应用实例:JAVA 中的 iterator。

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

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

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

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

实现

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

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

img

1、创建接口:

1
2
3
4
5
6
7
public interface Iterator {
public boolean hasNext();
public Object next();
}
public interface Container {
public Iterator getIterator();
}

2、创建实现了 Container 接口的实体类。该类实现了 Iterator 接口的内部类 NameIterator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class NameRepository implements Container {
public String[] names = {"Robert","John","Julie","Lora"};

@Override
public Iterator getIterator() {
return new NameIterator();
}

private class NameIterator implements Iterator{

int index;

@Override
public boolean hasNext() {
if(index < names.length){
return true;
}
return false;
}

@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}

3、使用 NameRepository 来获取迭代器,并打印名字。

1
2
3
4
5
6
7
8
9
10
public class IteratorPatternDemo {
public static void main(String[] args) {
NameRepository nameRepository = new NameRepository();

for(Iterator iter = nameRepository.getIterator(); iter.hasNext();){
String name = (String)iter.next();
System.out.println("name: "+name);
}
}
}

输出

img

参考:https://www.runoob.com/design-pattern/iterator-pattern.html

中介者模式

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

介绍

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

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

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

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

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

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

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

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

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

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

实现

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

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

img

1、创建中介类

1
2
3
4
5
6
7
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类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class User {

private String name;

public User(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void sendMessage(String message){
ChatRoom.showMessage(this,message);
}
}

3、使用 User 对象来显示它们之间的通信

1
2
3
4
5
6
7
8
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");
}
}

输出

img

参考:https://www.runoob.com/design-pattern/mediator-pattern.html

备忘录模式

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

介绍

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

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

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

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

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

应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。

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

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

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

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

实现

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

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

img

1、创建 Memento 类

1
2
3
4
5
6
7
8
9
10
11
public class Memento {
private String state;

public Memento(String state) {
this.state = state;
}

public String getState() {
return state;
}
}

2、创建 Originator 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Originator {
private String state;

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public Memento saveStateToMemento(){
return new Memento(state);
}

public void getStateFromMemento(Memento memento){
state = memento.getState();
}
}

3、创建 CareTaker 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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());
}
}

输出

img

Memento 记录 Originator 对象的状态, CareTaker 对象负责保存 Memento 对象记录。

参考:https://www.runoob.com/design-pattern/memento-pattern.html

观察者模式

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

介绍

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

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

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

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

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

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

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

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

使用场景:

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

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

实现

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

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

img

1、创建 Subject 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.ArrayList;
import java.util.List;

public class Subject {
private List<Observer> observers = new ArrayList<Observer>();

private int state;

public void attach(Observer observer){
observers.add(observer);
}

public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}

public int getState() {
return state;
}

public void setState(int state) {
this.state = state;
notifyAllObservers();
}
}

2、创建 Observer 类

1
2
3
4
5
public abstract class Observer {

protected Subject subject;
public abstract void update();
}

3、创建实体观察者类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class BinaryObserver extends Observer {
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}

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

@Override
public void update() {
System.out.println("Hex String: "+Integer.toHexString(subject.getState()));
}
}
public class OctalObserver extends Observer {

public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}

@Override
public void update() {
System.out.println("Octal String: "+Integer.toOctalString(subject.getState()));
}
}

4、使用 Subject 和实体观察者对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();

new HexObserver(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);
}
}

输出

img

我们使用 BinaryObserver、OctalObserver 和 HexObserver 来“观察” Subject 对象的情况,当 Subject 的状态改变时就调用 notifyAllObservers()方法。

参考:https://www.runoob.com/design-pattern/state-pattern.html

状态模式

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。

介绍

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

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

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

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

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

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

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

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

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

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

实现

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

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

img

1、创建一个接口

1
2
3
4
public interface State {

public void doAction(Context context);
}

2、创建实现接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class StartState implements State {

@Override
public void doAction(Context context) {
System.out.println("Player is in start stage");
context.setState(this);
}

@Override
public String toString() {
return "Start State";
}
}
public class StopState implements State {

@Override
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}

@Override
public String toString() {
return "Stop State";
}
}

3、创建 Context 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Context {
private State state;

public Context() {
}

public void setState(State state) {
this.state = state;
}

public State getState() {
return state;
}
}

4、使用 Context 来查看当状态 State 改变时的行为变化

1
2
3
4
5
6
7
8
9
10
11
12
13
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());

StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState());
}
}

当状态 State 改变后,对象的toString方法也发生了变化。

输出

img

参考:https://www.runoob.com/design-pattern/state-pattern.html

策略模式

在策略模式(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 在它所配置或使用的策略改变时的行为变化。

img

1、创建一个接口

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

2、创建实现接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class OperationAdd implements Strategy {

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

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

@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}

3、创建Context类

1
2
3
4
5
6
7
8
9
10
11
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、使用Context来查看当它改变策略 Strategy 时的行为变化

1
2
3
4
5
6
7
8
9
10
11
12
13
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));

}
}

输出

img

参考:https://www.runoob.com/design-pattern/strategy-pattern.html

访问者模式

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

介绍

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

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

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

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

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

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

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

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

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

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

实现

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

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

img

1、定义一个表示元素的接口

1
2
3
4
public  interface ComputerPart {

public void accept(ComputerPartVisitor computerPartVisitor);
}

2、创建扩展了上述接口的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Keyboard implements ComputerPart {

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

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
public class Computer implements ComputerPart {
ComputerPart[] parts;

public Computer() {
parts = new ComputerPart[]{new Mouse(),new Keyboard(),new Monitor()};
}

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (ComputerPart part : parts) {
part.accept(computerPartVisitor);
}

computerPartVisitor.visit(this);
}
}

4、创建实现了上述类的实体访问者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {

@Override
public void visit(Computer computer) {
System.out.println("Displaying Computer.");
}

@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse");
}

@Override
public void visit(Monitor monitor) {
System.out.println("Displaying Monitor.");
}

@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}
}

5、使用 ComputerPartDisplayVisitor 来显示 Computer 的组成部分

1
2
3
4
5
6
7
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();

computer.accept(new ComputerPartDisplayVisitor());
}
}

输出

img

参考:https://www.runoob.com/design-pattern/visitor-pattern.html

模板模式

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

介绍

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

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

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

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

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

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

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

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

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

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

实现

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

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

img

1、创建一个抽象类,它的模板方法被设置为 final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class Game {

abstract void initialize();
abstract void startPlay();
abstract void endPlay();

public final void play(){
initialize();

startPlay();

endPlay();
}
}

2、创建扩展了上述类的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Cricket extends Game {

@Override
void initialize() {
System.out.println("Cricket Game Initialized! Start Playing.");
}

@Override
void startPlay() {
System.out.println("Cricket Game Started! Enjoy the game.");
}

@Override
void endPlay() {
System.out.println("Cricket Game Finished!");
}
}
public class Football extends Game {

@Override
void initialize() {
System.out.println("Football Game Initialized! Start Playing.");
}

@Override
void startPlay() {
System.out.println("Football Game Started! Enjoy the game.");
}

@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
}

3、使用 Game 的模板方法 play() 来演示游戏的定义方式

1
2
3
4
5
6
7
8
9
10
11
12
public class TemplatePatternDemo {

public static void main(String[] args) {
Game game = new Cricket();
game.play();

System.out.println();

game = new Football();
game.play();
}
}

输出

img

参考:https://www.runoob.com/design-pattern/template-pattern.html

其他模式

空对象模式

在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。在空对象模式中,我们创建要给指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。

实现

我们将创建一个定义操作(在这里,是客户的名称)的 AbstractCustomer 抽象类,和扩展了 AbstractCustomer 类的实体类。工厂类 CustomerFactory 基于客户传递的名字来返回 RealCustomerNullCustomer 对象。

NullPatternDemo*,我们的演示类使用 *CustomerFactory 来演示空对象模式的用法。

1、创建一个抽象类

1
2
3
4
5
public abstract class AbstractCustomer {
protected String name;
public abstract boolean isNil();
public abstract String getName();
}

2、创建扩展了上述类的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class RealCustomer extends AbstractCustomer{

public RealCustomer(String name) {
this.name = name;
}

@Override
public String getName() {
return name;
}

@Override
public boolean isNil() {
return false;
}
}
public class NullCustomer extends AbstractCustomer {

@Override
public String getName() {
return "Not Available in Customer Database";
}

@Override
public boolean isNil() {
return true;
}
}

3、创建 CustomerFactory 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CustomerFactory {

public static final String[] names = {"Rob", "Joe", "Julie"};

public static AbstractCustomer getCustomer(String name){
for (int i = 0; i < names.length; i++) {
if(names[i].equalsIgnoreCase(name)){
return new RealCustomer(name);
}
}

return new NullCustomer();
}
}

4、使用 CustomerFactory,基于客户传递的名字,来获取 RealCustomer 或 NullCustomer 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NullObjectPatternDemo {
public static void main(String[] args) {
AbstractCustomer customer1 = CustomerFactory.getCustomer("Rob");
AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob");
AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie");
AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura");

System.out.println("Customers");
System.out.println(customer1.getName());
System.out.println(customer2.getName());
System.out.println(customer3.getName());
System.out.println(customer4.getName());
}
}

输出

img

我们使用一个空对象代替了对象的判空处理。

参考:https://www.runoob.com/design-pattern/null-object-pattern.html