Question

@FCPS 在这个对话中, 我们聊聊设计模式。我想知道工厂模式有哪些有点,它设计之初是为了解决什么问题?

我想的是解决直接用构造方法导致参数太多的问题,还有没有别的

Answer

Searching………………工厂模式(Factory Pattern)是一种创建型设计模式,它解决了多种问题,并带来了若干优点。以下是工厂模式的设计初衷及其优点的详细解释。

工厂模式的设计初衷

  1. 减少代码与类之间的耦合度: 工厂模式通过引入一个中间层(即工厂类)来创建实例,这样做可以降低调用者与具体产品类之间的耦合度,提高系统的灵活性和扩展性。
  2. 支持代码的封装性和可维护性: 将对象的创建过程封装在工厂中,可以使对象创建的细节对调用者隐藏,从而使代码更易于维护和理解。
  3. 提供一种替代直接实例化对象的方法: 在一些场合下,直接实例化对象会带来一些问题,如需要实例化的对象类型依赖于外部条件,或者对象创建过程较为复杂等情况。工厂模式提供了一个创建对象的接口,允许系统在不直接指定对象类型的情况下创建对象。
  4. 控制产品的生产: 工厂可以决定何时创建产品,控制产品的生产过程和质量,便于实现权限控制和资源重用等功能。

工厂模式的优点

  1. 降低耦合度: 工厂模式有助于减少调用代码与对象创建逻辑之间的直接依赖,从而降低系统的耦合度。
  2. 增强代码的扩展性: 当需要引入新的产品类时,无需修改已有代码,只需添加一个新的具体产品类和相应的工厂类即可,遵循开闭原则。
  3. 增强代码的灵活性: 工厂模式可以在运行时更改创建逻辑,动态地决定创建哪一种产品实例。
  4. 隐藏创建逻辑: 用户不需要知道具体的创建细节,只需通过工厂即可得到所需的对象,降低了使用产品的复杂度。
  5. 支持产品的规范性和安全性: 工厂模式使产品的创建过程标准化,同时也更易于加入权限控制等安全性控制机制。

总之,工厂模式不仅可以解决由直接使用构造方法可能带来的参数过多等问题,还提供了代码组织和管理上的多方面优势,如降低耦合度、提升系统的扩展性与灵活性等。

Question

一定要引入工厂类才算工厂模式吗?只在一个类里写一个静态方法,比如 getInstance(…) 算用到工厂模式了吗

Answer

Searching…不一定要引入专门的工厂类才能算作使用了工厂模式。在一个类中通过静态方法创建实例的做法,实际上也是工厂模式的一种体现,尽管它可能不那么显而易见。这种方法有时被称为“静态工厂方法”模式,它是工厂方法模式的一个特殊情况。

静态工厂方法模式

静态工厂方法模式使用一个类的静态方法来创建和返回实例,而不是直接使用new关键字。这种方法的优点包括:

  1. 控制实例化过程: 静态工厂方法可以包含额外的逻辑来决定何时或如何创建实例,甚至可以返回一个已经存在的实例(例如实现单例模式)。
  2. 返回类型的灵活性: 静态工厂方法的返回类型可以是其创建的对象的任意超类型,这为接口返回类型提供了灵活性,可以有助于增强API的抽象性和灵活性。
  3. 参数化实例的创建: 通过接受不同的参数,静态工厂方法可以根据不同的需求返回不同类型的实例,增加了方法的可用性。

静态工厂方法模式的一个典型例子是Java标准库中的valueOf方法,如Integer.valueOf(int)

例子

假设我们有一个配置类,我们希望这个类是单例的,并且可以根据不同的配置文件创建实例。以下是使用静态工厂方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Configuration {
private static Configuration instance;

private Configuration() {
// 私有构造器,阻止外部实例化
}

public static Configuration getInstance(String configFile) {
if (instance == null) {
instance = new Configuration();
// 假设这里有一些根据configFile读取和设置配置的逻辑
}
return instance;
}
}

在这个例子中,getInstance方法是一个静态工厂方法。它根据传入的配置文件名参数创建或返回配置类的实例。这既实现了单例模式,又保留了静态工厂方法的优点,比如灵活性和控制实例化过程的能力。

综上所述,即使不引入专门的工厂类,通过在类内部提供静态工厂方法也是工厂模式的一种有效使用方式。

Question

给我写一个引入工厂类来实现工厂模式的例子

Answer

Searching…为了更好地演示引入工厂类来实现工厂模式的例子,让我们构建一个简单的例子。假设我们有一个软件,需要根据不同的文件类型(如文本文件、图像文件等)来处理不同的文件。我们可以为每种文件类型创建一个处理器,并使用工厂模式来根据文件类型的不同选择合适的处理器。

步骤 1:定义产品接口

首先,我们定义一个FileProcessor接口,它将是所有文件处理器的基类。

1
2
3
public interface FileProcessor {
void processFile();
}

步骤 2:创建具体产品

接下来,我们为不同的文件类型创建具体的处理器类。这些类实现了FileProcessor接口。

1
2
3
4
5
public class TextFileProcessor implements FileProcessor {
public void processFile() {
System.out.println("处理文本文件");
}
}
1
2
3
4
5
public class ImageFileProcessor implements FileProcessor {
public void processFile() {
System.out.println("处理图像文件");
}
}

步骤 3:创建工厂类

然后,我们创建一个工厂类FileProcessorFactory,用于根据文件类型创建合适的文件处理器实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FileProcessorFactory {
public static FileProcessor getFileProcessor(String fileType) {
if (fileType == null) {
return null;
}
if (fileType.equalsIgnoreCase("TEXT")) {
return new TextFileProcessor();
} else if (fileType.equalsIgnoreCase("IMAGE")) {
return new ImageFileProcessor();
}
return null;
}
}

步骤 4:使用工厂类

最后,我们可以使用FileProcessorFactory来根据文件类型获取并使用合适的文件处理器。

1
2
3
4
5
6
7
8
9
public class FactoryPatternDemo {
public static void main(String[] args) {
FileProcessor fileProcessor1 = FileProcessorFactory.getFileProcessor("TEXT");
fileProcessor1.processFile();

FileProcessor fileProcessor2 = FileProcessorFactory.getFileProcessor("IMAGE");
fileProcessor2.processFile();
}
}

总结

在这个例子中,FileProcessorFactory是一个工厂类,它根据输入的文件类型动态创建并返回合适的文件处理器对象。这样,我们就成功地使用了工厂模式来解耦客户端代码和具体产品类,提高了系统的灵活性和可扩展性。

Question

讲讲什么是抽象工厂模式

Answer

Searching…抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它允许你生产系列相关或相互依赖的对象,而无需指定它们具体的类。这个模式是工厂方法模式的一种扩展,其提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

抽象工厂模式的核心组成

  1. 抽象工厂(Abstract Factory): 提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
  2. 具体工厂(Concrete Factory): 实现抽象工厂的操作,生产具体的产品。
  3. 抽象产品(Abstract Product): 为一类产品对象声明一个接口。
  4. 具体产品(Concrete Product): 抽象产品的具体子类。具体工厂负责创建具体的产品对象。
  5. 客户端(Client): 只需使用抽象工厂和抽象产品提供的接口就可以创建一系列相关或依赖的对象。

抽象工厂模式的目的

抽象工厂模式的目的是提供一种封装一组具有共同目标的单个工厂的方式,使其创建的对象具有某种共同点。它支持增加新的工厂和产品,而不影响原有系统,提高了系统的灵活性和可扩展性。

抽象工厂模式的优点

  • 隔离具体类的生成: 客户端不需要知道它使用的具体产品的类名,只需要知道相应的工厂即可。
  • 增加新的具体工厂和产品族很方便: 无需改变现有代码,符合开闭原则。
  • 提高系统的灵活性: 换用不同的产品族或产品组合可以提供不同行为的实例。
  • 促进了高内聚低耦合的设计思想。

示例

假设有一个家具店,需要生产并销售不同风格(如现代和维多利亚风格)的家具,包括椅子和桌子。每种风格的家具制作细节不同,因此需要使用抽象工厂模式来处理这个问题。

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
// 定义椅子和桌子的抽象接口
interface Chair {
void describe();
}

interface Table {
void describe();
}

// 定义具体产品
class ModernChair implements Chair {
public void describe() {
System.out.println("This is a modern chair.");
}
}

class VictorianChair implements Chair {
public void describe() {
System.out.println("This is a Victorian chair.");
}
}

class ModernTable implements Table {
public void describe() {
System.out.println("This is a modern table.");
}
}

class VictorianTable implements Table {
public void describe() {
System.out.println("This is a Victorian table.");
}
}

// 定义抽象工厂接口
interface FurnitureFactory {
Chair createChair();
Table createTable();
}

// 定义具体工厂
class ModernFurnitureFactory implements FurnitureFactory {
public Chair createChair() {
return new ModernChair();
}

public Table createTable() {
return new ModernTable();
}
}

class VictorianFurnitureFactory implements FurnitureFactory {
public Chair createChair() {
return new VictorianChair();
}

public Table createTable() {
return new VictorianTable();
}
}

// 客户端代码
public class Client {
public static void main(String[] args) {
FurnitureFactory factory = new ModernFurnitureFactory();

Chair chair = factory.createChair();
chair.describe();

Table table = factory.createTable();
table.describe();
}
}

在这个例子中,FurnitureFactory是抽象工厂,定义了创建家具的接口。ModernFurnitureFactoryVictorianFurnitureFactory是具体的工厂,分别生产现代风格和维多利亚风格的家具。通过切换不同的工厂,可以很方便地生产不同风格的家具,从而展示了抽象工厂模式的灵活性和扩展性。

Question

能不能用塔防游戏的场景举个抽象工厂的例子,比如炮塔,敌方单位,传统炮塔,雷击炮塔,之类的

Answer

Searching…在塔防游戏的场景中使用抽象工厂模式,可以非常方便地管理和扩展游戏中的各种单位和建筑,比如炮塔和敌方单位。游戏中可能有不同类型的炮塔(如传统炮塔、雷击炮塔等)以及不同类型的敌方单位(如普通敌人、装甲敌人等)。我们可以为每种类型的炮塔和敌方单位创建不同的工厂,然后使用抽象工厂模式来统一管理这些工厂。

步骤 1: 定义抽象产品

首先,我们定义两个抽象产品接口:TowerEnemy

1
2
3
4
5
6
7
interface Tower {
void attack();
}

interface Enemy {
void move();
}

步骤 2: 创建具体产品

然后,为每种类型的炮塔和敌方单位创建具体的实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class TraditionalTower implements Tower {
public void attack() {
System.out.println("传统炮塔进行攻击");
}
}

class LightningTower implements Tower {
public void attack() {
System.out.println("雷击炮塔进行攻击,造成范围伤害");
}
}

class NormalEnemy implements Enemy {
public void move() {
System.out.println("普通敌人向前移动");
}
}

class ArmoredEnemy implements Enemy {
public void move() {
System.out.println("装甲敌人缓慢向前移动");
}
}

步骤 3: 定义抽象工厂

接着,定义一个抽象工厂接口,包含创建炮塔和敌方单位的方法。

1
2
3
4
interface GameElementFactory {
Tower createTower();
Enemy createEnemy();
}

步骤 4: 创建具体工厂

为不同游戏关卡或地图创建具体的工厂类,每个工厂类可以生产一种类型的炮塔和敌人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Level1Factory implements GameElementFactory {
public Tower createTower() {
return new TraditionalTower();
}

public Enemy createEnemy() {
return new NormalEnemy();
}
}

class Level2Factory implements GameElementFactory {
public Tower createTower() {
return new LightningTower();
}

public Enemy createEnemy() {
return new ArmoredEnemy();
}
}

步骤 5: 客户端使用

最后,在游戏逻辑中,根据当前的游戏级别或场景,选择对应的工厂来创建炮塔和敌人。

1
2
3
4
5
6
7
8
9
10
11
public class Game {
public static void main(String[] args) {
GameElementFactory factory = new Level1Factory(); // 或者 Level2Factory 根据实际场景选择

Tower tower = factory.createTower();
Enemy enemy = factory.createEnemy();

tower.attack();
enemy.move();
}
}

通过使用抽象工厂模式,我们可以容易地为游戏添加新的炮塔和敌方单位类型,只需要新增具体的产品类和对应的工厂类即可。这种方式使得游戏的扩展性大大增强,同时也保持了代码的整洁和可维护性。

Question

什么是原型模式

Answer

Searching…原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制现有的对象来创建新的对象,而无需知道创建的细节。简单来说,原型模式就是对象的克隆。

原型模式的核心

原型模式的核心是实现一个可以克隆自身的接口。在Java中,这通常通过实现Cloneable接口并重写Object类的clone()方法来实现。

使用场景

当直接创建对象的成本较高时,比如对象的创建需要消耗较多的资源(时间、CPU资源等),或对象的构造函数复杂时,可以使用原型模式进行优化。使用原型模式可以:

  • 避免构造函数的约束。
  • 避开复杂的对象创建过程。
  • 提高应用程序的性能。

原型模式的优点

  • 性能提高: 通过对象克隆避免了构造函数的调用,可以比直接新建对象性能更佳。
  • 逃避构造过程的限制: 克隆过程并不通过常规的构造函数,有时构造函数可能有访问权限的限制,或者构造过程较为复杂。

示例代码

假设我们有一个图形界面的Rectangle类,需要快速创建一个与之完全相同的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Rectangle implements Cloneable {
private int width;
private int height;

public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}

public void display() {
System.out.println("Rectangle [Width: " + width + ", Height: " + height + "]");
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

客户端使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class PrototypeDemo {
public static void main(String[] args) {
try {
Rectangle rect1 = new Rectangle(3, 6);
Rectangle rect2 = (Rectangle) rect1.clone();

rect1.display();
rect2.display();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

在这个例子中,Rectangle类实现了Cloneable接口,并重写了clone()方法来允许对象自我复制。客户端代码创建了一个Rectangle对象的副本,而无需知道对象构造和初始化的细节。

注意事项

实现原型模式时,需要注意深拷贝与浅拷贝的区别。浅拷贝只复制对象的基本数据类型和引用,不复制引用指向的对象;而深拷贝则会连同引用指向的对象一起复制。在使用clone()方法时,默认是浅拷贝,如果需要实现深拷贝,需要在clone()方法中手动复制对象的引用类型成员。

Question

举个单例模式的代码例子

Answer

Searching…单例模式(Singleton Pattern)是一种确保一个类只有一个实例,并提供一个全局访问点的创建型设计模式。这个模式是用来避免对资源的多重占用,比如写操作时对文件的共享使用。

实现单例模式的步骤如下:

  1. 将该类的构造函数设为私有,防止外部通过new关键字创建对象实例。
  2. 在类内部创建一个该类的实例。
  3. 提供一个公共的静态方法,返回这个类内部创建的实例。

懒汉式单例示例

懒汉式单例模式指的是在真正需要使用对象实例时才会创建该实例。

1
2
3
4
5
6
7
8
9
10
11
12
public class LazySingleton {
private static LazySingleton instance;

private LazySingleton() {}

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

注意: 在多线程环境下,为了防止多个线程同时访问getInstance方法时可能会创建多个实例的问题,需要将getInstance方法设置为同步(synchronized)。

饿汉式单例示例

饿汉式单例模式指的是在类加载时就完成了实例的初始化,因此类加载较慢,但获取对象的速度快。

1
2
3
4
5
6
7
8
9
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();

private EagerSingleton() {}

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

这种方式基于classloader机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance明显没有达到懒加载的效果。

Question

构造器 builder 模式,你能不能讲一讲,举一个例子。可以用塔防游戏的塔防来举例子。

Answer

Searching…构造器模式(Builder Pattern)是一种创建型设计模式,它允许你分步骤构造复杂对象,而不将不同构造过程集中在一个构造函数中,以避免构造函数参数列表过长,以及增加构造过程的灵活性和清晰度。

构造器(Builder)模式的优点

  • 分离复杂对象的构造和表示: 允许用户通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
  • 更好的控制构造过程: 构建过程被分步骤执行,这意味着构造过程可以更加精细和控制。
  • 提供清晰的构造API: 相对于将所有参数放在同一个构造函数中,使用Builder模式可以使得参数设置更加直观和方便理解。

使用场景

当一个类的构造函数参数过多,导致构造函数变得难以管理,或者当一个类有多种表现形式和创建过程时,可以使用Builder模式。

塔防游戏的塔防构造例子

假设在一个塔防游戏中,我们需要创建不同类型的塔(如箭塔、炮塔、魔法塔等),并且每种类型的塔都有不同的属性(如攻击力、攻击范围、攻击速度等)。我们可以使用Builder模式来构造这些塔。

定义塔的类和它的Builder接口:

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 Tower {
private String type;
private int attackPower;
private int range;
private float attackSpeed;

private Tower(TowerBuilder builder) {
this.type = builder.type;
this.attackPower = builder.attackPower;
this.range = builder.range;
this.attackSpeed = builder.attackSpeed;
}

// 塔的Builder接口
public static class TowerBuilder {
private String type;
private int attackPower;
private int range;
private float attackSpeed;

public TowerBuilder setType(String type) {
this.type = type;
return this;
}

public TowerBuilder setAttackPower(int attackPower) {
this.attackPower = attackPower;
return this;
}

public TowerBuilder setRange(int range) {
this.range = range;
return this;
}

public TowerBuilder setAttackSpeed(float attackSpeed) {
this.attackSpeed = attackSpeed;
return this;
}

public Tower build() {
return new Tower(this);
}
}
}

使用该Builder来创建不同种类的塔:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TowerDefenseGame {
public static void main(String[] args) {
Tower arrowTower = new Tower.TowerBuilder()
.setType("箭塔")
.setAttackPower(50)
.setRange(10)
.setAttackSpeed(1.5f)
.build();

Tower cannonTower = new Tower.TowerBuilder()
.setType("炮塔")
.setAttackPower(100)
.setRange(8)
.setAttackSpeed(2.0f)
.build();

// 现在你有了两种类型的塔,且具有不同的属性
System.out.println("已创建箭塔和炮塔");
}
}

通过使用Builder模式,我们能够以清晰、灵活的方式构造出不同属性的塔,而无需记念复杂的构造函数参数列表,同时还能轻松地添加新的属性和类型的塔。

Question

我看这个构造者类是写在炮塔类的内部的一个内部类,实际上构造者模式一定要写在类的内部作为内部类吗?

Answer

Searching…在构造者模式中,构造者(Builder)不一定要作为内部类来实现。构造者可以是内部类,也可以是与产品类相分离的独立类。选择使用哪种方式取决于具体的设计需求和偏好。

使用内部类的优势:

  • 封装性好。 将Builder作为内部类可以更好地封装构造过程,而外部类的用户无需获知构造的细节。
  • 访问方便。 内部类可以访问外部类的私有成员,这使得编写Builder的构造过程更加方便,特别是当构造过程需要访问外部类的私有属性时。

使用独立类的情况:

  • 解耦合。 当构造器(或Builder)的实现可能在未来变得非常复杂,或者需要在多个类之间共享构造逻辑时,将Builder作为一个独立的类实现可以更好地解耦。
  • 重用性。 如果构造逻辑需要在不同的上下文中重用,独立的Builder类可以更容易地被重用。

示例:独立的Builder类

假设我们有一个Tower类,此时我们将TowerBuilder实现为一个独立的类而不是内部类。

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
// 塔的类
public class Tower {
private String type;
private int attackPower;
private int range;
private float attackSpeed;

public Tower(TowerBuilder builder) {
this.type = builder.getType();
this.attackPower = builder.getAttackPower();
this.range = builder.getRange();
this.attackSpeed = builder.getAttackSpeed();
}

// Getter方法略...
}

// 独立的Builder类
public class TowerBuilder {
private String type;
private int attackPower;
private int range;
private float attackSpeed;

public TowerBuilder setType(String type) {
this.type = type;
return this;
}

public TowerBuilder setAttackPower(int attackPower) {
this.attackPower = attackPower;
return this;
}

public TowerBuilder setRange(int range) {
this.range = range;
return this;
}

public TowerBuilder setAttackSpeed(float attackSpeed) {
this.attackSpeed = attackSpeed;
return this;
}

// Getter方法略...

public Tower build() {
return new Tower(this);
}
}

无论选择哪种实现方式,关键是要使得构造过程清晰、灵活且易于维护。选择是否将Builder作为内部类,主要取决于具体的应用场景和设计需求。

Question

什么是适配器模式

Answer

Searching…适配器模式(Adapter Pattern),也称为包装器(Wrapper),是一种结构型设计模式。它允许将一个类的接口转换成客户端期望的另外一个接口,使原本接口不兼容的类可以一起工作。适配器模式主要用于解决新旧系统或新旧接口之间的兼容问题,而无需修改原有的代码。

适配器模式的角色

  • 目标(Target)接口: 当前系统业务所期待的接口,它可以是具体的或抽象的类,也可以是接口。
  • 需要适配的类(Adaptee): 需要适配的类或适配者类。
  • 适配器(Adapter): 适配器类是一个转换器,通过继承或引用适配者的对象,将适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

适配器模式的两种实现方式

  1. 类适配器模式: 通过继承来实现适配器功能。
  2. 对象适配器模式: 通过组合来实现适配器功能。

示例:塔防游戏中的适配器模式

假设在一个塔防游戏中,有一个旧的敌人接口OldEnemy和一个新的敌人接口NewEnemy。现在我们有一些旧的敌人类,它们实现了OldEnemy接口,但是系统是按NewEnemy接口来设计的。我们需要一个适配器让旧的敌人类能够在新的系统中工作。

定义NewEnemy接口和OldEnemy接口:

1
2
3
4
5
6
7
8
9
10
11
// 新的敌人接口
interface NewEnemy {
void move();
void attack();
}

// 旧的敌人接口
interface OldEnemy {
void goForward();
void fireWeapon();
}

定义一个旧的敌人实现:

1
2
3
4
5
6
7
8
9
10
// 旧的敌人实现类
class OldEnemyRobot implements OldEnemy {
public void goForward() {
System.out.println("OldEnemyRobot going Forward");
}

public void fireWeapon() {
System.out.println("OldEnemyRobot firing weapon");
}
}

实现适配器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 适配器类
class EnemyAdapter implements NewEnemy {
OldEnemy oldEnemy;

public EnemyAdapter(OldEnemy oldEnemy) {
this.oldEnemy = oldEnemy;
}

@Override
public void move() {
oldEnemy.goForward();
}

@Override
public void attack() {
oldEnemy.fireWeapon();
}
}

在这个例子中,EnemyAdapter类实现了新的敌人接口,但它实际上内部使用了旧的敌人实现。这样,即使是旧的敌人实现,也可以在期望使用新敌人接口的系统中工作,而无需改动旧的敌人类的代码。

使用适配器:

1
2
3
4
5
6
7
8
9
10
public class TowerDefenseGame {
public static void main(String[] args) {
OldEnemy oldEnemyRobot = new OldEnemyRobot();
NewEnemy adaptedEnemy = new EnemyAdapter(oldEnemyRobot);

// 现在可以使用新敌人接口操作旧的敌人实例了
adaptedEnemy.move();
adaptedEnemy.attack();
}
}

适配器模式通过引入一个中间层(即适配器类),使得原本因接口不兼容而不能一起工作的类可以一起工作,增强了系统的灵活性和可扩展性。

Question

上面这个是类的,能不能举一个对象适配器模式

Answer

Searching…当然可以。对象适配器模式与类适配器模式的主要区别在于,对象适配器模式使用组合来连接适配者类的接口与目标接口,而不是继承。这种方式提供了更好的灵活性,因为它可以适配多个适配者的类而不是仅一个。

对象适配器模式示例

在塔防游戏场景中,让我们继续使用之前的NewEnemyOldEnemy接口,以及OldEnemyRobot类的示例。我们将创建一个对象适配器使得OldEnemyRobot能够像NewEnemy那样工作。

定义NewEnemy接口和OldEnemy接口:

1
2
3
4
5
6
7
8
9
10
11
// 新的敌人接口
interface NewEnemy {
void move();
void attack();
}

// 旧的敌人接口
interface OldEnemy {
void goForward();
void fireWeapon();
}

定义一个旧的敌人实现类OldEnemyRobot

1
2
3
4
5
6
7
8
9
10
// 旧的敌人实现类
class OldEnemyRobot implements OldEnemy {
public void goForward() {
System.out.println("OldEnemyRobot going Forward");
}

public void fireWeapon() {
System.out.println("OldEnemyRobot firing weapon");
}
}

实现一个对象适配器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 对象适配器类
class EnemyRobotAdapter implements NewEnemy {
private OldEnemy oldEnemyRobot;

public EnemyRobotAdapter(OldEnemy oldEnemyRobot) {
this.oldEnemyRobot = oldEnemyRobot;
}

@Override
public void move() {
oldEnemyRobot.goForward();
}

@Override
public void attack() {
oldEnemyRobot.fireWeapon();
}
}

这里,EnemyRobotAdapter类不再继承OldEnemyRobot,而是通过构造函数接收一个OldEnemy实例,并将其存储在一个私有成员变量中。然后,EnemyRobotAdapter类实现了NewEnemy接口,通过调用其内部oldEnemyRobot的方法来满足接口要求,从而使老式的机器人敌人可以像新的敌人一样被使用。

使用适配器的示例代码可以是这样的:

1
2
3
4
5
6
7
8
9
10
public class TowerDefenseGame {
public static void main(String[] args) {
OldEnemy oldEnemyRobot = new OldEnemyRobot();
NewEnemy adaptedRobotEnemy = new EnemyRobotAdapter(oldEnemyRobot);

// 使用适配器后,旧的机器人敌人就能够像新的敌人一样移动和攻击了
adaptedRobotEnemy.move();
adaptedRobotEnemy.attack();
}
}

在这个例子中,EnemyRobotAdapter是一个对象适配器,它适配了OldEnemyRobot使其可以按照NewEnemy接口进行操作。与类适配器不同,对象适配器模式允许一个适配器与多个不同的适配者工作,只要适配者遵循相同的接口。这增加了代码的重用性和灵活性。

Question

能不能再介绍一下继承模式的类适配器?

Answer

Searching…当然可以。类适配器模式通过继承(扩展)需要适配的类(Adaptee)和目标接口(Target)来实现适配功能。这种方式直接继承了适配者类,因此可以在适配器中重写适配者的方法以适配目标接口。

类适配器模式的特点:

  1. 单继承限制: 由于Java等语言不支持多重类继承,类适配器模式使用继承的方式实现适配,因此每个适配器只能适配一个适配者类。
  2. 覆盖行为: 适配器类可以重写适配者类的方法,提供更加符合目标接口期望行为的实现。
  3. 直接访问适配者: 适配器可以直接访问适配者类的所有公共方法和属性。

类适配器模式示例:

假设我们有一个现有的旧式打印机接口OldPrinter和一个新的打印接口NewPrinter,我们的目标是使旧式打印机能够适配到新的打印系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 旧的打印机接口
interface OldPrinter {
void printString();
}

// 适配者类
class SpecificPrinter implements OldPrinter{
@Override
public void printString() {
System.out.println("Print from SpecificPrinter");
}
}

// 新的打印接口
interface NewPrinter {
void print();
}

实现类适配器:

1
2
3
4
5
6
7
8
// 通过继承SpecificPrinter并实现NewPrinter接口来创建适配器
class PrinterAdapter extends SpecificPrinter implements NewPrinter {
@Override
public void print() {
// 调用适配者类SpecificPrinter的方法
printString();
}
}

使用适配器:

1
2
3
4
5
6
public class ClassAdapterDemo {
public static void main(String[] args) {
NewPrinter newPrinter = new PrinterAdapter();
newPrinter.print(); // 将输出: Print from SpecificPrinter
}
}

这个例子中,PrinterAdapter通过继承SpecificPrinter类,并实现NewPrinter接口,达到了适配器的目的。这样一来,尽管SpecificPrinter本身是按照OldPrinter的方式实现的,但现在可以在期望使用NewPrinter接口的地方使用它。

通过类适配器模式,我们可以在不修改旧式打印机类SpecificPrinter的前提下,使其适合新的打印系统。这种方式非常直接且易于实现,是解决接口不兼容问题的一种有效方法。

Question

介绍一下桥接模式

Answer

Searching…桥接模式(Bridge Pattern)是一种结构型设计模式,用于将抽象部分与其实现部分分离,使它们都可以独立地变化。通过使用组合的方式,桥接模式能够连接抽象与实现的界面,进而让这两者的变化更加独立。

桥接模式的核心组成

  1. 抽象类(Abstraction): 定义抽象类的接口,并维护一个指向实现部分(Implementor)的引用。
  2. 扩展抽象类(Refined Abstraction): 扩展由抽象类定义的接口。
  3. 实现类接口(Implementor): 定义实现类的接口,这个接口并不一定要与抽象类的接口完全一致,事实上两者可以完全不同。一般来说,实现类接口提供基本操作,而抽象类则定义基于这些基本操作的较高层次的操作。
  4. 具体实现类(Concrete Implementor): 实现Implementor接口的具体类。

桥接模式的目的

目的在于使得抽象与实现可以独立扩展,不从而互相影响,实现系统可能有多角度分类,每一种分类都可能变化。

桥接模式的优势

  • 分离抽象与实现部分: 提高了系统的可扩展性。
  • 提高系统的灵活性: 抽象部分和实现部分可以独立地进行变化,而不会相互影响。
  • 符合开闭原则: 桥接模式通过组合的方式建立一个抽象和实现的联系,而非继承。

桥接模式示例:塔防游戏的武器与塔

假设你正在开发一个塔防游戏,游戏中的每个塔可以使用不同的武器。使用桥接模式,我们可以将“塔”和“武器”的实现分离,让它们可以独立变化。

具体实现

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
// 实现类接口(Implementor)
interface Weapon {
void attack();
}

// 具体实现类(Concrete Implementor)
class Cannon implements Weapon {
public void attack() {
System.out.println("Cannon fire!");
}
}

class Laser implements Weapon {
public void attack() {
System.out.println("Laser shoot!");
}
}

// 抽象类(Abstraction)
abstract class Tower {
protected Weapon weapon;

public Tower(Weapon weapon) {
this.weapon = weapon;
}

abstract void attack();
}

// 扩展抽象类(Refined Abstraction)
class DefenseTower extends Tower {
public DefenseTower(Weapon weapon) {
super(weapon);
}

public void attack() {
System.out.print("Defense Tower ");
weapon.attack();
}
}

// 客户端代码
public class TowerDefenseGame {
public static void main(String[] args) {
Tower tower1 = new DefenseTower(new Cannon());
Tower tower2 = new DefenseTower(new Laser());

tower1.attack(); // 输出:Defense Tower Cannon fire!
tower2.attack(); // 输出:Defense Tower Laser shoot!
}
}

在这个示例中,Tower类是“抽象类”(Abstraction),Weapon是“实现类接口”(Implementor),“Cannon”和“Laser”是具体的实现类(Concrete Implementor),DefenseTower是扩展了抽象类的“扩展抽象类”(Refined Abstraction)。这样,塔和武器可以独立地变化和扩展,而不会相互影响。

Question

再介绍一下组合模式吧(composite)

Answer

Searching…组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构来表示“整体/部分”的层次结构。通过这种模式,客户端可以统一对待个别对象和组合对象。

组合模式的核心组成

  1. 组件(Component): 定义了叶子和容器对象的共同接口,使得客户端代码可以统一对待叶子节点和容器节点。
  2. 叶节点(Leaf): 没有子节点的对象,实现或继承了组件接口。
  3. 容器(Composite): 容器对象包含一个或多个叶子对象和容器对象,实现或继承了组件接口,并可以添加或移除其中的子节点。

组合模式的目的

组合模式的主要目的是让叶节点和容器节点具有一致性,从而让客户端代码可以相同地对待单个对象和组合对象。

组合模式的优势

  • 简化客户端代码: 客户端代码可以一致地对待单个对象和组合对象,无需关心它们在内部的组合层次。
  • 更容易扩展: 可以更容易地添加新类型的组件,因为新增加的类型不会改变已有代码的运作方式。

示例:塔防游戏中的组合模式

假设在塔防游戏中,我们有不同类型的炮塔,同时也可以创建炮塔组合,将多个炮塔组合成一组进行统一管理和操作。

具体实现

定义组件接口:

1
2
3
interface TowerComponent {
void attack();
}

实现叶节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SingleTower implements TowerComponent {
private String name;
private int power;

public SingleTower(String name, int power) {
this.name = name;
this.power = power;
}

@Override
public void attack() {
System.out.println(name + " 发动攻击,攻击力 " + power);
}
}

实现容器:

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

class TowerComposite implements TowerComponent {
private List<TowerComponent> children = new ArrayList<>();

public void add(TowerComponent component) {
children.add(component);
}

public void remove(TowerComponent component) {
children.remove(component);
}

@Override
public void attack() {
for (TowerComponent child : children) {
child.attack();
}
}
}

客户端使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TowerDefenseGame {
public static void main(String[] args) {
// 创建单个炮塔
SingleTower tower1 = new SingleTower("箭塔", 50);
SingleTower tower2 = new SingleTower("炮塔", 100);

// 创建炮塔组合
TowerComposite composite = new TowerComposite();
composite.add(tower1);
composite.add(tower2);

// 单个炮塔攻击
tower1.attack();
tower2.attack();

// 炮塔组合发动攻击
composite.attack();
}
}

在这个示例中,使用组合模式允许客户端统一对待单个炮塔和炮塔组合。客户端可以通过相同的接口调用attack方法,而不必关心是对单个炮塔还是炮塔组合发起的操作。这样大大简化了客户端代码的复杂度,并提高了代码的可扩展性和灵活性。

Question

很好,再来介绍一下装饰模式