常用设计模式介绍
创建型模式 (Creational Patterns)
这类模式主要关注对象的创建过程,旨在将对象的创建与使用分离,从而降低系统的耦合度。 它对类的实例化过程进行了抽象,使得程序在需要创建哪些对象时更加灵活。
1. 单例模式 (Singleton Pattern)
- 实现思路: 保证一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。通常的做法是将构造函数私有化,以防止外部通过
new创建实例,然后在类的内部创建一个静态实例,并提供一个静态方法供外界获取该实例。 - 使用场景:
- 需要频繁实例化然后销毁的对象,例如数据库连接池、线程池。
- 创建对象时耗时过多或耗费资源过多,但又经常用到的对象。
- 有状态的工具类对象,例如日志记录器、网站计数器。
- 需要全局共享信息的场景,如应用程序的配置对象。
- 优点:
- 只有一个实例,减少了内存开销和系统性能消耗。
- 避免了对资源的多重占用。
- 提供了对唯一实例的全局访问点,方便管理。
- 缺点:
- 扩展困难,因为没有接口,不能继承。
- 对测试不利,在并行开发环境中,如果单例没有完成,那么不能进行测试。
- 与单一职责原则有冲突,一个类应该只关心内部逻辑,而不应关心自己在外面是如何被实例化的。
2. 工厂方法模式 (Factory Method Pattern)
- 实现思路: 定义一个用于创建对象的接口(工厂接口),但让实现该接口的子类(具体工厂)来决定实例化哪一个类。客户端代码只与工厂接口和产品接口交互,无需关心具体的产品创建过程。
- 使用场景:
- 当一个类不知道它所需要的对象的类时。
- 当一个类希望由它的子类来指定它所创建的对象时。
- 将创建对象的任务委托给多个帮助子类中的某一个,客户端在使用时无需关心使用哪个子类。
- 优点:
- 用户只需要关心所需产品对应的工厂,无需关心创建细节。
- 遵循“开闭原则”,当增加新产品时,只需增加一个具体产品类和对应的具体工厂类,无需修改原有工厂代码。
- 典型的解耦框架,符合松耦合思想。
- 缺点:
- 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度。
3. 抽象工厂模式 (Abstract Factory Pattern)
- 实现思路: 提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。它处理的是“产品族”的创建问题。例如,一个工厂可以创建一套皮肤(按钮、文本框等),换一个工厂就可以创建另一套完全不同风格的皮肤。
- 使用场景:
- 一个系统要独立于它的产品的创建、组合和表示时。
- 系统中有多于一个的产品族,而每次只使用其中某一产品族。
- 属于同一个产品族的产品将在一起使用。
- 优点:
- 将具体产品的创建过程与客户端分离,客户端通过抽象接口来操作,无需关心具体实现。
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
- 缺点:
- 扩展新的产品等级结构(即增加新的产品种类)非常困难,因为需要修改抽象工厂和所有具体工厂的接口。
4. 建造者模式 (Builder Pattern)
- 实现思路: 将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。 它通过一步一步地创建复杂对象,允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
- 使用场景:
- 当一个对象的构建过程非常复杂,包含多个部分,且这些部分的构建顺序会影响最终结果时。
- 当需要创建的对象具有许多可选参数或配置时,可以避免出现大量的构造函数重载。
- 需要生成的对象内部属性本身相互依赖的场景。
- 优点:
- 封装性好,构建和表示分离,客户端不必知道产品内部组成的细节。
- 扩展性好,具体的建造者类之间相互独立,容易扩展。
- 可以更好地控制对象的构建过程,因为是在一步一步地完成创建。
- 缺点:
- 会产生多余的 Builder 对象以及 Director 对象,消耗内存。
- 如果产品内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得庞大。
结构型模式 (Structural Patterns)
这类模式关注如何将类或对象组合在一起形成更大的结构,同时保持结构的灵活性和效率。 它分为类结构型模式(使用继承)和对象结构型模式(使用组合/聚合)。
1. 适配器模式 (Adapter Pattern)
- 实现思路: 将一个类的接口转换成客户端希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 就像一个电源转接头,让不同标准的插头可以在插座上使用。
- 使用场景:
- 系统需要使用现有的类,而此类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类协同工作。
- 需要一个统一的输出接口,而输入端的类型不可预知。
- 优点:
- 可以让任何两个没有关联的类一起运行,提高了类的复用性。
- 增加了类的透明度。
- 灵活性好,不会破坏原有系统。
- 缺点:
- 过多地使用适配器,会让系统非常零乱,不易整体进行把握。
2. 装饰器模式 (Decorator Pattern)
- 实现思路: 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。 它允许向一个现有的对象添加新的功能,同时又不改变其结构。
- 使用场景:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
- 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时。
- 优点:
- 比继承更灵活,可以在运行时动态地增删功能。
- 可以将核心职责和装饰功能分离开,简化了高层代码。
- 缺点:
- 会产生许多细小的对象,增加了系统的复杂度。
3. 代理模式 (Proxy Pattern)
- 实现思路: 为其他对象提供一种代理以控制对这个对象的访问。 代理对象和目标对象通常实现相同的接口,客户端与代理对象交互,代理对象内部再与目标对象交互。
- 使用场景:
- 远程代理:为一个对象在不同的地址空间提供局部代表。
- 虚拟代理:根据需要创建开销很大的对象,例如显示一张大图,可以先用代理显示缩略图,真正需要时再加载原图。
- 安全代理:用来控制真实对象访问时的权限。
- 智能指引:当调用真实的对象时,代理处理另外一些事。
- 优点:
- 职责清晰,代理模式可以作为客户端和目标对象之间的中介,起到保护目标对象的作用。
- 可以扩展目标对象的功能。
- 高扩展性、智能化。
- 缺点:
- 由于在客户端和真实主题之间增加了代理对象,可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些实现会非常复杂。
4. 外观模式 (Facade Pattern)
- 实现思路: 为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。 它隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。
- 使用场景:
- 当你要为一个复杂子系统提供一个简单接口时。
- 客户端与多个子系统之间存在大量的依赖关系,希望通过一个统一入口降低耦合。
- 在层次化结构中,可以用外观模式定义每一层的入口。
- 优点:
- 减少了客户端与子系统之间的耦合,让子系统内部的模块更容易维护和扩展。
- 客户端无须关心子系统内部的实现,只需要和外观对象交互即可。
- 缺点:
- 不符合开闭原则,如果需要修改外观类,可能需要改动大量客户端代码。
- 外观类可能会变成一个包含所有功能的“上帝对象”。
行为型模式 (Behavioral Patterns)
这类模式特别关注对象之间的通信,涉及到算法和对象间职责的分配。 它们描述了在不同的对象之间划分责任和算法的抽象化。
1. 策略模式 (Strategy Pattern)
- 实现思路: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。该模式使得算法可独立于使用它的客户而变化。
- 使用场景:
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
- 优点:
- 算法可以自由切换,并且易于扩展。
- 避免使用多重条件判断。
- 每个策略都是独立的,符合单一职责原则。
- 缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个。
- 策略类会增多,所有策略类都需要对外暴露。
2. 观察者模式 (Observer Pattern)
- 实现思路: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。它包括“主题”(Subject)和“观察者”(Observer)两个核心角色。
- 使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变。
- 一个对象必须通知其他对象,而它又不能假定其他对象是谁。
- 优点:
- 在观察者和被观察者之间建立了一套触发机制,实现了松耦合。
- 支持广播通信。
- 缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果观察者和主题之间有循环依赖,可能导致系统崩溃。
3. 命令模式 (Command Pattern)
- 实现思路: 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
- 使用场景:
- 系统需要将请求的调用者和接收者解耦,使得调用者和接收者不直接交互。
- 需要将请求排队、记录日志或支持撤销/重做操作。
- 优点:
- 降低了系统的耦合度。
- 新的命令可以很容易地加入到系统中,符合开闭原则。
- 可以比较容易地设计一个命令队列或宏命令(组合命令)。
- 缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
4. 状态模式 (State Pattern)
- 实现思路: 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。 它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。
- 使用场景:
- 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
- 代码中包含大量与对象状态有关的条件语句。
- 优点:
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态。
- 使得状态转换变得明确,减少了对象间的相互依赖。
- 缺点:
- 会导致系统里有大量的状态类,增加了系统的复杂性。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
5. 责任链模式 (Chain of Responsibility Pattern)
- 实现思路: 为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
- 使用场景:
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可动态指定一组对象处理请求。
- 优点:
- 降低耦合度,请求者无需知道是哪个对象处理了他的请求。
- 可简化对象的相互连接,增强了给对象指派职责的灵活性。
- 增加新的请求处理类很方便。
- 缺点:
- 不能保证请求一定被接收。
- 对链的性能有影响,而且调试时不容易观察运行时的特征。