设计模式
设计模式是为了解决在软件开发过程中遇到的某些问题而形成的思想。同一场景有多种设计模式可以应用,不同的模式有各自的优缺点,开发者可以基于自身需求选择合适的设计模式,去解决相应的工程难题。
良好的软件设计和架构,可以让代码具备良好的可读性、可维护性、可扩展性、可复用性,让整个系统具备较强的鲁棒性和性能,减少屎山代码出现的概率。
创建型模式
创建型设计模式提供了各种对象创建机制,增加了现有代码的灵活性和重用性。
工厂方法模式(Factory Method):为超类提供了一个创建对象的接口,但允许子类改变将被创建的对象的类型。
优点:
- 良好的封装性。将产品的实例化封装执行,避免被修改,这样的产品具备良好的一致性。
- 良好的扩展性。增加产品时,同步增加一个工厂子类,不会违反开闭原则。
- 标准的解耦合框架。使用者只需要知道自己要什么产品即可,不用去管产品具体的特性等等,降低了模块间的耦合。
缺点:
- 代码量大。每加一个产品,都要加一个工厂子类,代码会显得臃肿。
- 不利于扩展复杂的产品结构。
抽象工厂模式(Abstract Factory):允许您在不指定具体类的情况下生成一组相关对象的模式,相当于升级版的工厂模式。
优点:
- 具体类分离。具体产品类在具体工厂的实现中进行了分离和归类。
- 易于更换产品族。当客户想要改变整个产品族时,只需要切换具体工厂即可。
- 利于产品一致性。当产品族的各个产品需要在一起执行时,抽象工厂可以确保客户只操作同系列产品,而不会进行跨品牌的组合。
缺点:
- 不利于添加新种类产品。每加一个新的种类,如多一个项链类型的产品,那每一个具体工厂都要进行代码的扩展,且破坏了原先规定的结构,违反了开闭原则。
建造者模式(Builder):允许您逐步构建复杂对象的模式。该模式允许您使用相同的构建代码生成不同类型和表示的对象。
优点:
- 封装性好。有效地封装了建造过程(主要业务逻辑),使得系统整体的稳定性得到了一定保证。
- 解耦。产品本身和建造过程解耦,相同的建造过程可以创建出不同的产品。
- 产品建造过程精细化。该模式注重产品创建的整个过程,将复杂的步骤拆解得到多个相对简单的步骤,使得系统流程更清晰,且对细节的把控更精准。
- 易于扩展。如果有新产品需求,只需要添加一个建造者类即可,不需要改动之前的代码,符合开闭原则。
缺点:
- 产品的组成部分和构建过程要一致,限制了产品的多样性。
- 若产品内部有结构上的变化,则整个系统都要进行大改,增加了后期维护成本。
原型模式(Prototype):允许您复制现有对象而不使您的代码依赖于它们的类的模式。
优点:
- 便捷、简洁、高效。不需要考虑对象的复杂程度,只需要复制即可。
- 无需初始化。可动态地获取当前原型的状态,并在当前基础上进行拷贝。
- 允许动态增加或减少产品类。
缺点:
- 每个类都需要配备一个clone函数,若对已有的类进行改造,需要修改其源码,违背了开闭原则。
单例模式(Singleton):单例模式是一种创建型的软件设计模式,在工程项目中非常常见。通过单例模式的设计,使得创建的类在当前进程中只有一个实例,并提供一个全局性的访问点,这样可以规避因频繁创建对象而导致的内存飙升情况。
结构型设计模式
结构型设计模式解释了如何将对象和类组合成更大的结构,同时保持这些结构的灵活性和高效性。
适配器模式(Adapter):允许具有不兼容接口的对象进行协作的模式。
优点:
- 良好封装性。接口内的内容对使用者而言是透明的,即看不见,这确保了内部功能具备较好的封装性,不易被改动。
- 解耦。不匹配的两方在适配器的作用下可以做到解耦,不需要修改任何一方原有代码逻辑。
- 良好复用性。适配的两方不需要做任何修改,业务的实现可以通过适配器来完成,不同的业务可以使用不同的适配器。
- 良好扩展性。若要增加业务场景,只需要增加适配器类,来满足业务即可。
缺点:
- 不利于维护。因为业务的实现基于适配器完成,适配器中代码的复杂程度会越来越高,不熟悉业务或者底层逻辑的人难以短时间内接手维护。
- 系统结构易混乱。当业务量快速增加时,适配器类的数量也会快速增加,没有良好的系统架构布局,最终会使得整个系统臃肿且危险。
桥接模式(Bridge):将一个大类或一组紧密相关的类分割成两个独立的层次结构(抽象和实现),使它们可以相互独立地进行开发的模式。
优点:
- 扩展性好。抽象与实现分离,扩展起来更便捷,可以获得更多样式的目标。
- 解耦。不同抽象间的耦合程度低。
- 满足设计模式要求的合成复用原则和开闭原则。
- 封装性好。具体实现细节对客户而言是透明不可见的。
缺点:
- 使用场景有限制。只有系统有两个以上独立变化维度时才适用。
复合模式(Composite):将对象组合成树状结构,然后以与单个对象相同的方式处理这些结构的模式。
优点:
- 层次鲜明。凸显“部分-整体”的层次结构。
- 一致性。对叶子对象(单)和容器对象(组合)的操作具备良好一致性。
- 节点自由度高。在结构中按需自由添加节点。
缺点:
- 设计更抽象。
- 应用场景限制。
装饰器模式(Decorator):将具有特定行为的对象放置在包含这些行为的特殊包装对象中,从而使您可以向对象附加新的行为的模式。
优点:
- 灵活性好。相比较继承,装饰模式扩展对象功能更加灵活。
- 扩展性好。不同装饰组合,可以创造出各式各样的对象,且避免了类爆炸。
- 满足设计模式要求的开闭原则和合成复用原则。
- 透明性好。客户端针对抽象操作,对具体实现的内容不可见。
缺点:
- 复杂性高。装饰模式的设计往往具备较高复杂度,对开发者的水平要求高。
外观模式(Facade):为库、框架或任何其他复杂的类集合提供简化的接口的模式。
优点:
- 简洁易使用。为复杂的模块和系统提供了一个简单的接口,简易化操作。
- 保证了子系统独立性。子系统间独立性良好,彼此间一般不受影响,如何使用由门面决定。
- 保证了系统稳定性。当直接使用子系统,可能会出现无法预知的异常时,门面模式可通过高层接口规范子系统接口的调用,且有效阻隔子系统和客户端间的交互,进而增强系统鲁棒性。
- 隐秘性好。门面将子系统的具体细节都封装了起来。
缺点:
- 不符合开闭原则。添加新系统要对门面进行修改。
- 对开发者要求高。开发者需要了解子系统间的业务逻辑关系,这样才能确保封装的高层接口是有效且稳定的。
享元模式(Flyweight):通过在多个对象之间共享状态的公共部分,而不是在每个对象中保留所有数据,可以将更多对象适应可用的内存量的模式。
优点:
- 减少资源浪费。共享资源极大程度降低了系统的资源消耗。
- 提高系统运行效率。当资源过度使用时,系统效率会大受影响。
缺点:
- 维护共享对象,需要额外开销。
- 系统复杂度提高。运行享元,除了内外状态,还有线程方面都要充分考虑。
代理模式(Proxy):提供另一个对象的替代或占位符的模式。代理控制对原始对象的访问,允许您在请求传递到原始对象之前或之后执行某些操作。
优点:
- 职责清晰。真实对象专注于自身业务逻辑,不用考虑其他非本职内容,交给代理完成。
- 高拓展性。真实对象的改变不影响代理。
- 解耦。将客户端与真实对象分离,降低系统耦合度。
- 提高性能。虚拟代理可以减少系统资源的消耗。
- 高安全性和稳定性。代理能很好地控制访问,提高程序安全。
缺点:
- 增加系统复杂度。代理的职责往往较冗杂。
- 请求速度降低。客户端与真实对象中加入代理,一定程度上会降低整个系统流程的运行效率。
行为型设计模式
行为型设计模式关注算法和对象之间责任的分配。
责任链模式(Chain of Responsibility):允许你将请求沿着一条处理器链传递。每个处理器在接收到请求后决定是处理该请求还是将其传递给链中的下一个处理器。
优点:
- 请求者和接收者松耦合。请求者只需要发送请求,不关心由谁处理怎么处理;接收者只需要处理自己该处理的,剩下的交给责任链上的其他职责处理。
- 比较灵活。责任链上各个职责对象,可以灵活排序或组合,以应对不同场景。
缺点:
- 性能易受影响。当责任链过长时,对请求的处理效率不够高。
- 不一定确保请求完整处理。每个职责只对自身部分负责,有可能请求走完整个责任链,也没有完全处理。
命令模式(Command):将请求转换为独立的对象,该对象包含有关请求的所有信息。通过这种转换,您可以将请求作为方法参数传递、延迟或排队请求的执行,并支持可撤销的操作。
迭代器模式(Iterator):让您遍历集合的元素,而不暴露其底层表示(如列表、堆栈、树等)。
优点:
- 符合单一职责原则。将遍历行为抽离成单独的类。
- 符合开闭原则。添加新集合或者新迭代器,不改变原有代码。
- 便于扩展多种遍历行为。
- 访问数据又不暴露内部。
缺点:
- 若对聚合对象只需要进行简单的遍历行为,那使用迭代器模式有些大材小用。
- 系统复杂性提高,类数量较多。
中介者模式(Mediator):减少对象之间混乱的依赖关系。该模式限制了对象之间的直接通信,并强制它们只能通过中介者对象进行协作。
备忘录模式(Memento):在不透露其实现细节的情况下,保存和恢复对象的先前状态。
优点:
- 良好封装性。发起人对象中的内部状态被保存在备忘录中,也只能由自己读取,对其他对象起到了屏蔽作用。
- 提供了状态恢复机制。类似于游戏存档读档。
- 简化了发起人职责。发起人状态的存储和获取,被分离出去了。
缺点:
- 资源消耗较大,对发起人对象不同内部状态的存储,会导致开销增加。
观察者模式(Observer):定义订阅机制,以通知多个观察对象有关它们所观察的对象发生的任何事件。
状态模式(State):当对象的内部状态发生变化时,允许对象改变其行为。它看起来就像对象改变了它的类。
策略模式(Strategy):定义一族算法,将每个算法都封装在独立的类中,并使它们的对象可互换使用。
模板方法模式(Template Method):在超类中定义算法的骨架,但允许子类在不改变其结构的情况下覆盖特定的步骤。
优点:
- 良好复用性。父类中公共部分可以多次使用,具备好的环境适应性。
- 良好扩展性。子类对父类模板的具体实现作扩展。
- 符合开闭原则。基于模板扩展功能,不需要改动原有代码。
缺点:
- 类个数增加。基于模板的每个实现,都要定义一个子类,容易使代码量膨胀。
- 若父类模板有改动,则子类均要同步更改。
访问者模式(Visitor):将算法与其操作的对象分离开来。
优点:
- 良好扩展性。扩展对元素的操作,只需要添加访问者。
- 满足单一职责原则。相关的操作封装为一个访问者,使得访问者职责单一。
- 解耦。数据结构自身和作用于它的操作解耦合。
缺点:
- 不易增加元素类。每增加一个元素类,访问者的接口和实现都要进行变化。
- 违背了依赖倒置原则。访问者依赖的是具体元素而不是抽象元素。
- 破坏封装。访问者可以获取被访问元素的细节。