创建型模式
1.简单工厂模式
简单工厂模式目的是封装对象的创建过程,将对象的创建和使用分离。
简单工厂模式的主要角色有三个:
- 工厂(Factory):负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
- 抽象产品(Product):是所创建的所有对象的父类,负责描述所有实例所共有的公共接口。
- 具体产品(Concrete Product):是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
- 工厂
- 抽象产品类
-
具体产品类
-
产品的创建
简单工厂模式的优点是:
- 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
- 客户端无需知道所创建的具体产品类的类名,只需要知道参数即可。
- 很容易添加新产品。
但简单工厂模式也有缺点:
- 工厂类集中了所有产品的创建逻辑,如果产品种类非常多,会使得工厂类变得非常臃肿。
- 系统拓展困难,一旦添加新产品就不得不修改工厂类的逻辑,在产品类型较多时,可能会出现工厂逻辑过于复杂,不利于系统的扩展和维护。
- 违背"开闭原则"。当添加新产品时,需要修改工厂类,违背了"开闭原则"。
总之,简单工厂模式适用于产品种类相对较少,产品的创建逻辑不复杂的情况。如果产品种类很多,创建逻辑复杂,就不太合适使用简单工厂模式了。在这种情况下,可以考虑使用工厂方法模式或者抽象工厂模式。
2.工厂方法模式
工厂方法模式是简单工厂模式的进一步抽象和推广。它属于创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
工厂方法模式的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(Concrete Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
- 抽象产品类(Shape)
- 具体产品类(Line, Circle, Rectangle)
- 抽象工厂类(Factory)
- 具体工厂类(LineFactory, CircleFactory, RectFactory)
- 测试
工厂方法模式的优点是:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
工厂方法模式的缺点是:
- 类的个数容易过多,增加复杂度。
- 增加了系统的抽象性和理解难度。
- 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。
总之,工厂方法模式适合这样的场景:当需要创建的对象较少时,不会造成工厂方法过于复杂。同时,工厂方法模式可以很好地解耦客户端代码和具体产品,很好地满足"开闭原则",在增加新产品时不需要修改已有代码。
3.抽象工厂模式
抽象工厂模式是工厂方法模式的升级版本。它提供了一种方式,可以将一组具有同一主题的单独的工厂封装起来。在正常使用中,客户端程序需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一主题的具体对象。
抽象工厂模式的主要角色如下:
- 抽象工厂(Abstract Factory):声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
- 具体工厂(Concrete Factory):实现抽象工厂声明的一组方法,每个具体工厂对应一个特定的产品族,只创建此产品族中的产品。
- 抽象产品(Abstract Product):为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法。
- 具体产品(Concrete Product):定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。
- 抽象产品类
- 具体产品类
- 抽象工厂类
- 具体工厂类
- 测试
抽象工厂模式的优点是:
- 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
- 增加新的产品族很方便,无须修改已有系统,符合"开闭原则"。
抽象工厂模式的缺点是:
- 增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对"开闭原则"的支持呈现倾斜性。
- 开闭原则的倾斜性(新产品族的增加很容易,新产品等级的增加很难)。
- 产品族扩展非常困难,需要修改抽象工厂,所有的具体工厂,以及客户端的代码。
总之,抽象工厂模式适用于这样的场景:当需要创建的对象是一个产品族,而不是单一的产品,并且这个产品族中的产品是相关的,或者说必须一起使用的。同时,抽象工厂模式可以保证客户端始终只使用同一个产品族中的对象,不会因为错误地使用不同产品族中的对象而导致程序出错。
4.建造者模式
建造者模式是一种创建型设计模式,它允许你分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。
建造者模式的主要意图是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式包含以下主要角色:
- Builder(抽象建造者):定义创建Product对象的各个部件的抽象接口。
- ConcreteBuilder(具体建造者):实现Builder接口,构造和装配各个部件。
- Director(指挥者):构建一个使用Builder接口的对象。它定义了一个构建过程的顺序,即按照什么样的顺序来执行各个构建步骤。
- Product(产品):表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程。
- 产品类
- 抽象建造者
- 具体建造者
- 指挥者
- 测试
注意:
在建造者模式中,我们通常只有一个产品类,但这个产品可以有不同的内部组成或表示。这些不同的内部组成或表示是在具体建造者类中定义的。
在我给出的例子中,产品类是House,它代表了一个房子。但是,不同类型的房子(如木屋和石屋)有不同的内部组成(墙、门、屋顶等的材料不同)。这些不同的内部组成是在具体建造者类(WoodenHouseBuilder和StoneHouseBuilder)中定义的。
用户(或客户端代码)并不直接使用具体建造者类,而是通过指挥者(Director)来创建具体的产品。指挥者定义了产品的构建过程(顺序),但是它不知道产品的具体内部组成。它只知道抽象建造者(HouseBuilder)定义的接口。
所以,当用户想要创建一个具体的产品(如木屋)时,它需要:
- 创建一个具体的建造者(如WoodenHouseBuilder)。
- 将这个具体建造者传递给指挥者。
- 告诉指挥者去构建产品。
- 从指挥者那里获取构建好的产品。
通过这种方式,同样的构建过程(由指挥者定义)可以创建出不同的产品(由具体建造者定义)。这就是建造者模式的核心思想。
建造者模式的核心思想:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式适用于以下场景:
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
建造者模式的主要优点是:
- 它让你可以分步骤创建对象,并且可以改变过程以创建不同的对象表示。
- 它将复杂对象的构造代码和表示代码分离开来,提高了代码的可读性和维护性。
不过,建造者模式也有一些缺点:
- 它会增加代码的复杂性,因为你需要创建多个不同的建造者类。
- 客户端需要知道建造者的存在,并且需要知道如何使用建造者来创建对象。
5.单例模式
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。换句话说,单例模式确保一个类在整个系统中只有一个实例,而且这个实例易于被访问。
单例模式通常用于以下场景:
- 当一个类在整个系统中只应该有一个实例,而且这个实例需要易于被访问时。例如,一个系统只应该有一个文件系统或一个窗口管理器。
- 当这个唯一的实例应该通过子类化可扩展时,并且客户端可以在不修改代码的情况下使用扩展的实例时。
- 懒汉式
这种实现方式被称为"懒汉式"单例,因为它直到第一次被使用时才创建实例。
- 饿汉式
在这个实现中,m_instance在类定义外初始化,所以它会在程序开始时就被创建。
单例模式有以下优点:
- 确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。
- 当一个类只应该有一个实例时,单例模式可以严格控制客户端如何以及何时访问它。
- 当这个唯一的实例应该通过子类化可扩展时,单例模式可以方便地让客户端在不修改代码的情况下使用扩展的实例。
但单例模式也有一些缺点:
- 违反了单一职责原则。单例类既要负责创建自己的对象,又要负责管理单例的生命周期。
- 可能掩盖不良设计,例如当组件的各个部分知道太多彼此的信息时。
- 在多线程环境中需要特殊处理,以避免多个线程同时创建单例对象。
- 可能难以单元测试。许多测试框架依赖于继承来创建模拟对象,但单例的构造函数是私有的,无法继承。
单例模式可以严格控制客户端如何以及何时访问它。
3. 当这个唯一的实例应该通过子类化可扩展时,单例模式可以方便地让客户端在不修改代码的情况下使用扩展的实例。
但单例模式也有一些缺点:
- 违反了单一职责原则。单例类既要负责创建自己的对象,又要负责管理单例的生命周期。
- 可能掩盖不良设计,例如当组件的各个部分知道太多彼此的信息时。
- 在多线程环境中需要特殊处理,以避免多个线程同时创建单例对象。
- 可能难以单元测试。许多测试框架依赖于继承来创建模拟对象,但单例的构造函数是私有的,无法继承。