我们已经回顾了单例对象的使用,但您可能还没有意识到这种策略是一种设计模式,而且是最常用的设计模式之一。

事实上,这些模式有很多,可以根据其特定用途进行分类。

花样分类

图案类型 应用
创意的 解决对象创建问题的类型
结构性的 让我们在架构中构建正确且可扩展的类层次结构的模式
行为的 这组模式促进了程序中对象之间的安全和方便的交互。

通常,模式的特征在于它解决的问题。让我们看一下我们在使用 Java 时最常遇到的几种模式:

图案 目的
单例 我们已经熟悉这种模式——我们用它来创建和访问一个不能有多个实例的类。
迭代器 这个我们也很熟悉。我们知道这种模式可以让我们在不暴露其内部表示的情况下迭代一个集合对象。它与集合一起使用。
适配器 此模式连接不兼容的对象,以便它们可以协同工作。我认为适配器模式的名称可以帮助您准确地想象它的作用。这是现实生活中的一个简单示例:墙上插座的 USB 适配器。
模板法

一种解决集成问题并允许您更改算法步骤而不更改算法结构的行为编程模式。

想象一下,我们有一个装配步骤序列形式的汽车装配算法:

底盘 -> 车身 -> 发动机 -> 车厢内饰

如果我们放入一个加固的框架,一个更强大的引擎,或者一个带有额外照明的内部空间,我们不必改变算法,抽象序列保持不变。

装潢师 此模式为对象创建包装器以赋予它们有用的功能。我们将把它作为本文的一部分。

在 Java.io 中,以下类实现了模式:

图案 它在java.io中的使用位置
适配器
模板法
装潢师

装饰者模式

假设我们正在描述一个家居设计模型。

通常,该方法如下所示:

最初,我们可以选择几种类型的房屋。最低配置是一层有屋顶。然后我们使用各种装饰器来改变额外的参数,这自然会影响房子的价格。

我们创建一个抽象的 House 类:

public abstract class House {
	String info;

	public String getInfo() {
    	return info;
	}

	public abstract int getPrice();
}

这里我们有2种方法:

  • getInfo()返回有关我们房子的名称和特征的信息;
  • getPrice()返回当前房屋配置的价格。

我们还有标准的 House 实现——砖木结构:

public class BrickHouse extends House {

	public BrickHouse() {
    	info = "Brick House";
	}

	@Override
	public int getPrice() {
    	return 20_000;
	}
}

public class WoodenHouse extends House {

	public WoodenHouse() {
    	info = "Wooden House";
	}

	@Override
	public int getPrice() {
    	return 25_000;
	}
}

这两个类都继承了House类并覆盖了它的价格方法,为标准房屋设置了自定义价格。我们在构造函数中设置名称。

接下来,我们需要编写装饰器类。这些类也将继承House类。为此,我们创建了一个抽象装饰器类。

这就是我们将放置用于更改对象的附加逻辑的地方。最初,没有额外的逻辑,抽象类是空的。

abstract class HouseDecorator extends House {
}

接下来,我们创建装饰器实现。我们将创建几个类,让我们为房子添加额外的功能:

public class SecondFloor extends HouseDecorator {
	House house;

	public SecondFloor(House house) {
    	this.house = house;
	}

	@Override
	public int getPrice() {
    	return house.getPrice() + 20_000;
	}

	@Override
	public String getInfo() {
    	return house.getInfo() + " + second floor";
	}
}
为我们的房子增加二楼的装饰师

装饰器构造函数接受我们将“装饰”的房子,即添加修改。我们重写了getPrice()getInfo()方法,根据旧房子返回更新后的新房子的信息。

public class Garage extends HouseDecorator {

	House house;
	public Garage(House house) {
    	this.house = house;
	}

	@Override
	public int getPrice() {
    	return house.getPrice() + 5_000;
	}

	@Override
	public String getInfo() {
    	return house.getInfo() + " + garage";
	}
}
为我们的房子增加车库的装饰师

现在我们可以用装饰器更新我们的房子。为此,我们需要创建一个房子:

House brickHouse = new BrickHouse();

接下来,我们设置我们的房子变量等于一个新的装饰器,传入我们的房子:

brickHouse = new SecondFloor(brickHouse);

我们的房子变量现在是二楼的房子。

让我们看一下涉及装饰器的用例:

示例代码 输出
House brickHouse = new BrickHouse();

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

砖房

20000

House brickHouse = new BrickHouse();

  brickHouse = new SecondFloor(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

砖房+二楼

40000

House brickHouse = new BrickHouse();


  brickHouse = new SecondFloor(brickHouse);
  brickHouse = new Garage(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

砖房+二层+车库

45000

House woodenHouse = new SecondFloor(new Garage(new WoodenHouse()));

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

木屋+车库+二楼

50000

House woodenHouse = new WoodenHouse();

  House woodenHouseWithGarage = new Garage(woodenHouse);

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

  System.out.println(woodenHouseWithGarage.getInfo());
  System.out.println(woodenHouseWithGarage.getPrice());

木房子

25000

木屋+车库

30000

此示例说明了使用装饰器升级对象的好处。所以我们没有改变木房子对象本身,而是在旧对象的基础上创建了一个新对象。这里我们可以看出利与弊:我们每次都在内存中创建一个新对象,增加了内存消耗。

看看我们程序的 UML 图:

装饰器具有超级简单的实现并动态更改对象,升级它们。装饰器可以通过它们的构造函数来识别,构造函数将与当前类相同的抽象类型或接口的对象作为参数。在 Java 中,这种模式被广泛用于 I/O 类中。

例如,正如我们已经注意到的,java.io.InputStreamOutputStreamReaderWriter的所有子类都有一个接受相同类对象的构造函数。