什么是设计模式
设计模式:在软件设计中给定上下文中常见问题的通用的、可重用的解决方案。
设计模式分类
1. 创建型模式——Creational patterns
关注对象创建的过程
1.1 工厂方法模式
定义用于创建对象的接口,但让子类决定要实例化哪个类。工厂方法允许类将实例化推迟到子类。
应用场景:当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。
优点:无需将特定于应用程序的类绑定到代码中。代码仅处理接口,所以它可兼容其他子类。
缺点:增加代码量,需要额外增加一个Creator类及其子类。
2. 结构型模式——Structural patterns
处理类或对象的组合
2.1 适配器模式
将某个类/接口转换为client期望的其他形式
应用场景:需要在新系统中重用一个不兼容的老组件
如上图所示,为了实现Shape的display方法,对先前已存在的类—LegacyRectangle中的diaplay方法进行了重用
优点:实现了对已有类的大限度复用,避免“重新造轮子”。
缺点:适配器模式会引入额外的类和代码,这可能会增加系统的复杂性。此外,适配器模式有时会被用来掩盖设计上的问题,而不是解决它们。例如,适配器可能被用来连接不兼容的接口,但这可能是由于设计不良或缺乏整体架构考虑造成的。使用适配器模式可能会使得根本问题被忽视。
2.2 装饰器模式
实现子类特性的任意组合
应用场景:想要对子类实现多个特性的堆叠
上图为一个应用实例,通过逐层调用装饰器进行包装,实现特性的组合
优点:
- 动态扩展对象功能:装饰器模式允许在运行时动态地添加功能,而无需修改对象的类。这使得可以根据需要灵活地增加或移除功能。
- 遵循单一职责原则:每个装饰器类都专注于一个特定的功能扩展。这使得每个类的职责更加单一和明确,易于维护和理解。
- 替代继承:通过组合而不是继承来扩展对象的功能,避免了类爆炸(class explosion)问题。继承会导致大量的子类,而装饰器模式则通过不同装饰器的组合来实现相同的效果。
- 灵活性和可组合性:多个装饰器可以组合使用,以创建复杂的功能扩展。这种组合方式提供了极大的灵活性,允许以多种方式排列和组合装饰器。
- 透明性:客户端可以透明地使用装饰器,而无需知道对象被装饰了。装饰器模式对客户端是透明的,客户端代码无需修改即可使用增强功能的对象。
缺点:
- 增加代码复杂性:虽然装饰器模式提供了灵活性,但也增加了系统的复杂性。尤其是在装饰器链较长时,调试和排查问题可能变得困难。
- 较多的小类:由于每个具体装饰器都是一个独立的小类,这可能导致系统中类的数量增加,从而增加维护和管理的难度。
3. 行为类模式——Behavioral patterns
描述类或对象交互和分配责任的方式。
3.1 策略模式
对于特定的任务存在不同的算法,客户端可以在运行时根据动态上下文在算法之间切换。
应用场景:为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例
如上图所示,在使用ShoppingCart
中的pay方法时,可以根据需要传入算法策略的类型,从而实现不同的操作
优点:
- 开闭原则:策略模式遵循开闭原则(OCP),允许你在不修改现有代码的情况下引入新的策略。新策略的添加不会影响到现有的策略类和上下文类。
- 消除条件判断:策略模式通过使用多态消除了在客户端代码中使用条件判断来选择算法的需求。客户端代码不需要通过条件语句来决定使用哪种算法,而是通过策略接口调用对应的算法。
- 提高代码的灵活性和可维护性:由于策略模式将算法封装在独立的类中,算法的实现可以独立于其上下文类进行修改。这使得代码更易于理解、维护和扩展。
缺点:
- 增加对象数量:策略模式会引入大量的策略类,如果策略的数量很多,类的数量也会显著增加。这可能导致代码库变得复杂,管理起来更困难。
- 客户端必须了解不同的策略:客户端必须知道不同策略之间的区别,并且需要了解如何选择合适的策略。这增加了客户端代码的复杂性。
3.2 模板模式
做事情的步骤一样,但具体方法不同
应用场景:共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现
优点:实现了代码的复用,减少冗余代码量
缺点:基类中定义的算法骨架是固定的,子类只能修改其中的部分步骤。如果需要修改算法的整体结构,必须修改基类,这可能违背开闭原则(OCP)。
3.3 迭代器模式
客户端希望遍历被放入容器/集合类的一组ADT对象,无需关心容器的具体类型
应用场景:让自己的集合类实现Iterable接口,并实现自己的独特Iterator
迭代器(hasNext, next, remove),允许客户端利用这
个迭代器进行显式或隐式的迭代遍历
以下是代码示例
public class Pair<E> implements Iterable<E> {
private final E first, second;
public Pair(E f, E s) { first = f; second = s; }
public Iterator<E> iterator() {
return new PairIterator();
}
private class PairIterator implements Iterator<E> {
private boolean seenFirst = false, seenSecond = false;
public boolean hasNext() { return !seenSecond; }
public E next() {
if (!seenFirst) { seenFirst = true; return first; }
if (!seenSecond) { seenSecond = true; return second; }
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
3.4 Visitor模式
将数据和作用于数据上的某种特定操作分离开来
应用场景:为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下通过delegation接入ADT
优点:
- 易于增加新的操作:通过添加新的访问者类,可以在不修改对象结构的情况下定义新的操作。这使得系统更易于扩展,符合开放/封闭原则(OCP)。
- 集中相关行为:访问者模式将相关的行为集中在一个访问者类中,而不是分散在对象类中。这使得行为更容易理解和维护。
缺点:
违反单一职责原则:访问者模式将多个不相关的操作集中到访问者类中,可能违反单一职责原则。每个访问者类通常实现多个方法,这些方法可能具有不同的目的和逻辑。