什么是设计模式?请简述其作用。

设计模式其实是在软件开发过程中经过经验积累和验证总结得出的一套通用代码设计方案,它们帮助开发者避免重复发明轮子,确保设计的一致性和可维护性,提高代码的可读性和可扩展性。

如果熟悉了设计模式,当遇到类似的场景,我们可以快速地参考设计模式实现代码。不仅可以加速我们的编码速度,也提升了代码的可扩展性、可重用性与可维护性!

作用

  1. 帮助我们快速解决常见问题:设计模式提供了解决特定软件设计问题的通用方法,拿来套上即用,例如单例模式、代理模式、责任链模式等等。
  2. 提升代码可扩展性:设计模式通常考虑了软件的扩展性,将不同的功能和功能变化分离开来实现,使得未来添加新功能更加容易。
  3. 提高代码可重用性:设计模式本身就是经验的总结,按照设计模式的思路,很多代码封装的很好,便于复用,减少重复工作。
  4. 提升代码可维护性:通过使用设计模式,使得代码结构更加清晰,易于理解和维护。
  5. 简化沟通成本:如果大家都熟悉设计模式,其实设计模式就是一种通用语言,通过设计就能明白其实现含义,有助于开发者之间更有效地沟通设计意图。
  6. 提供最佳实践:它们是经验的总结,可以指导开发者避免常见陷阱,采用最佳实践。

工厂模式和抽象工厂模式有什么区别?

工厂模式

工厂方法模式定义了一个创建对象的接口,一个具体的工厂类负责生产一种产品,如果需要添加新的产品,仅需新增对应的具体工厂类而不需要修改原有的代码实现。

工厂方法的目的是使得创建对象和使用对象是分离的,并且客户端总是引用抽象工厂和抽象产品。工厂方法是指定义工厂接口和产品接口,但如何创建实际工厂和实际产品被推迟到子类实现,从而使调用方只和抽象工厂与抽象产品打交道。

我们来看个例子,假设有一个产品 Product,以及它的具体实现 ConcreteProduct,我们使用工厂方法模式来创建 ConcreteProduct:

// 产品接口
public interface Product {
void use();
}

// 具体产品实现
public class ConcreteProduct implements Product {
@Override
public void use() {
System.out.println("Using ConcreteProduct");
}
}

// 工厂接口
public abstract class Factory {
public abstract Product createProduct();
}

// 具体工厂实现
public class ConcreteFactory extends Factory {
@Override
public Product createProduct() {
return new ConcreteProduct();
}
}

// 使用工厂方法创建产品
public class Client {
public static void main(String[] args) {
Factory factory = new ConcreteFactory();
Product product = factory.createProduct();
product.use();
}
}

抽象工厂模式

而抽象工厂提供一个创建一系列相关或相互依赖对象的接口,简单来说不是仅生产一个产品,而是一个系列产品。听起来可能有点抽象,我们还是看一下例子。

假设我们有两个产品 ProductA 和 ProductB,以及它们的具体实现 ConcreteProductA1、ConcreteProductA2、ConcreteProductB1、ConcreteProductB2,我们使用抽象工厂模式来创建这些相关的产品:

// 抽象产品A
public interface ProductA {
void use();
}

// 具体产品A1
public class ConcreteProductA1 implements ProductA {
@Override
public void use() {
System.out.println("Using ConcreteProductA1");
}
}

// 具体产品A2
public class ConcreteProductA2 implements ProductA {
@Override
public void use() {
System.out.println("Using ConcreteProductA2");
}
}

// 抽象产品B
public interface ProductB {
void eat();
}

// 具体产品B1
public class ConcreteProductB1 implements ProductB {
@Override
public void eat() {
System.out.println("Eating ConcreteProductB1");
}
}

// 具体产品B2
public class ConcreteProductB2 implements ProductB {
@Override
public void eat() {
System.out.println("Eating ConcreteProductB2");
}
}

// 抽象工厂
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}

// 具体工厂1
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}

@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}

// 具体工厂2
public class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2();
}

@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}

// 使用抽象工厂创建产品
public class Client {
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
ProductA productA1 = factory1.createProductA();
ProductB productB1 = factory1.createProductB();
productA1.use();
productB1.eat();

AbstractFactory factory2 = new ConcreteFactory2();
ProductA productA2 = factory2.createProductA();
ProductB productB2 = factory2.createProductB();
productA2.use();
productB2.eat();
}
}

代码虽然很多,但是看下来应该很清晰,抽象工厂就是打包式的创建系列对象,等于帮我们搭配好了,屏蔽关联对象的一些创建细节。

总结一下。

工厂模式:

  • 关注创建单一产品对象。
  • 使用子类来决定创建哪个具体产品。
  • 扩展性较好,新增产品时只需增加新的工厂子类。
  • 包含一个抽象产品接口,多个具体产品实现这个接口。
  • 有一个抽象工厂接口和多个具体工厂实现类,每个具体工厂负责创建一种具体产品。

抽象工厂模式:

  • 关注创建一系列相关或相互依赖的产品对象。
  • 提供一个接口,用于创建多个产品族的对象。
  • 增加新的产品族较为容易,但增加新产品类型较为困难。(比如要加个 createProductC,所有实现具体工厂的代码都得改)
  • 包含多个抽象产品接口,每个接口代表一类相关的产品。
  • 有一个抽象工厂接口,这个接口中包含多个创建不同抽象产品的方法。
  • 多个具体工厂实现类,每个具体工厂实现类负责创建一整套相关的具体产品,即实现抽象工厂接口中的所有创建方法,以提供一个完整的产品系列。

什么是模板方法模式?一般用在什么场景?

模板方法模式,它在一个抽象类中定义了一个算法(业务逻辑)的骨架,具体步骤的实现由子类提供,它通过将算法的不变部分放在抽象类中,可变部分放在子类中,达到代码复用和扩展的目的。

复用:所有子类可以直接复用父类提供的模板方法,即上面提到的不变的部分。 扩展:子类可以通过模板定义的一些扩展点就行不同的定制化实现。

abstract class AbstractClass {
// 模板方法
public final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
hook();
}

// 基本操作(抽象方法)
protected abstract void primitiveOperation1();
protected abstract void primitiveOperation2();

// 钩子方法(可选的操作,提供默认实现)
protected void hook() {}
}


class ConcreteClassA extends AbstractClass {
@Override
protected void primitiveOperation1() {
System.out.println("ConcreteClassA: primitiveOperation1");
}

@Override
protected void primitiveOperation2() {
System.out.println("ConcreteClassA: primitiveOperation2");
}
}

class ConcreteClassB extends AbstractClass {
@Override
protected void primitiveOperation1() {
System.out.println("ConcreteClassB: primitiveOperation1");
}

@Override
protected void primitiveOperation2() {
System.out.println("ConcreteClassB: primitiveOperation2");
}

@Override
protected void hook() {
System.out.println("ConcreteClassB: hook");
}
}

上述代码定义了模板方法 templateMethod,内部定义了业务处理的逻辑,按序执行 primitiveOperation1()、primitiveOperation2() 和 hook()。

子类只需要关心几个方法的内部实现,不需要关心模板方法的内部执行顺序,这就是我们所说的将通用的算法步骤放在抽象类中,不同的实现细节放在子类中,在多个子类中共享相同的代码,而不需要在每个子类中重复实现相同的逻辑。

在 Java 中有很多应用场景,例如 JdbcTemplate 就是使用了模板方法来处理数据库的操作。

再比如 HttpServlet 类的 service 方法也用了模板方法,doGet、doPost 等方法都是需要子类实现的。

优点

  1. 提高复用性
  2. 提高扩展性
  3. 符合开闭原则

缺点

  1. 类数目增加
  2. 增加了系统实现的复杂度
  3. 继承关系自身的缺点,如果父类添加新的抽象方法,所有子类都要改一遍。