文章目录
1.简单工厂模式(Simple Factory)
1.1.定义
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
理解:简单工厂模式它属于工厂模式的一种,用于创建对象而无需指定具体的类。在简单工厂模式中,一个工厂类负责创建多个不同类型的对象,而客户端只需要知道所需对象的名称或标识即可。
关键角色和概念:
1. 工厂类(Creator):负责创建对象的类,它包含了实际创建对象的逻辑,客户端通过调用工厂类的静态方法或实例化工厂对象来获取所需的产品。
2. 产品类(Product):被创建的对象类,具体产品由工厂类根据客户端的请求创建。
3. 客户端(Client):调用工厂类创建对象的类,通过工厂类获取所需的产品对象。
简单工厂模式的主要优点是客户端不需要知道具体产品的类名,只需知道产品的名称或标识即可获取所需对象,降低了客户端与具体产品类的耦合性。但同时,如果需要添加新的产品类型,仍然需要修改工厂类的代码,可能违反了开放-封闭原则。
这种模式通常适用于工厂类负责创建的对象较少,不会频繁变动的场景。
1.2.结构
简单工厂模式包含如下角色:
- Factory:工厂角色:负责实现创建所有实例的内部逻辑
- Product:抽象产品角色:是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
- ConcreteProduct:具体产品角色:是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
1.3.时序图
1.4.代码实现
场景1:假设有一个简单的图形绘制程序,支持绘制圆形和矩形。
using System; // 产品接口 interface IShape { void Draw(); } // 具体产品:圆形 class Circle : IShape { public void Draw() { Console.WriteLine("绘制圆形"); } } // 具体产品:矩形 class Rectangle : IShape { public void Draw() { Console.WriteLine("绘制矩形"); } } // 简单工厂类 class ShapeFactory { // 工厂方法,根据传入的类型创建相应的产品对象 public static IShape CreateShape(string shapeType) { switch (shapeType.ToLower()) { case "circle": return new Circle(); case "rectangle": return new Rectangle(); default: throw new ArgumentException("不支持的图形类型"); } } } // 客户端 class Client { static void Main() { // 客户端通过工厂类获取产品对象 IShape circle = ShapeFactory.CreateShape("circle"); IShape rectangle = ShapeFactory.CreateShape("rectangle"); // 客户端使用产品对象 circle.Draw(); rectangle.Draw(); } }
例子中,我们定义了一个IShape
接口作为产品的抽象,然后有两个具体产品类Circle
和Rectangle
实现了这个接口。ShapeFactory
作为简单工厂类,包含了一个静态方法CreateShape
,根据传入的类型字符串创建相应的产品对象。
客户端通过调用工厂方法获取产品对象,并使用这些对象进行相应的操作。这样,客户端不需要直接知道具体产品的类名,只需通过工厂类获取所需对象即可。
场景2:假设有一个简单的餐厅点餐系统,支持点餐并根据菜品种类生成不同的菜单。
using System; using System.Collections.Generic; // 产品接口 interface IFood { void Cook(); } // 具体产品:中式菜 class ChineseFood : IFood { public void Cook() { Console.WriteLine("中式菜已做好"); } } // 具体产品:西式菜 class WesternFood : IFood { public void Cook() { Console.WriteLine("西式菜已做好"); } } // 简单工厂类 class FoodFactory { // 工厂方法,根据传入的类型创建相应的产品对象 public IFood CreateFood(string foodType) { switch (foodType.ToLower()) { case "chinese": return new ChineseFood(); case "western": return new WesternFood(); default: throw new ArgumentException("不支持的菜系类型"); } } } // 客户端 class Client { static void Main() { Console.WriteLine("请输入菜系类型(chinese/western):"); string foodType = Console.ReadLine(); try { // 创建简单工厂对象 FoodFactory factory = new FoodFactory(); // 客户端通过工厂类获取产品对象 IFood food = factory.CreateFood(foodType); // 客户端使用产品对象 food.Cook(); } catch (ArgumentException ex) { Console.WriteLine(ex.Message); } } }
例子中,我们定义了一个IFood
接口作为产品的抽象,然后有两个具体产品类ChineseFood
和WesternFood
实现了这个接口。FoodFactory
作为简单工厂类,包含了一个实例方法CreateFood
,根据传入的菜系类型字符串创建相应的产品对象。
客户端通过输入菜系类型,创建简单工厂对象,然后调用工厂方法获取相应的产品对象,并使用这个对象进行烹饪操作。
1.5.优缺点
优点:
1. 封装对象的创建过程:简单工厂模式将对象的创建过程封装在一个工厂类中,客户端只需关心产品接口和工厂类,而无需关心具体产品的实现。这提供了一种简单的方法来创建对象,隐藏了对象的实例化细节。
2. 解耦客户端和具体产品类:客户端通过工厂类获取产品对象,而不直接依赖具体产品类。这使得客户端代码与具体产品的类名解耦,提高了系统的灵活性和可维护性。
3. 单一责任原则:工厂类负责创建对象,遵循了单一责任原则,使得系统中的各个类更加清晰、易于理解和修改。
缺点:
1. 扩展性受限:新增产品时,需要修改工厂类的创建方法,违反了开闭原则。如果系统中的产品类不断增加,可能导致工厂类变得庞大,维护困难。
2. 不符合开闭原则:当需要新增产品时,需要修改工厂类的代码,这违背了开闭原则,即对扩展开放,对修改关闭。
3. 违反了依赖倒置原则:客户端依赖具体的工厂类,而不是依赖抽象。如果需要切换工厂类,客户端代码也需要修改。
简单工厂模式适用于产品种类相对稳定且变化不频繁的情况,如果系统中的产品类经常变化,可能需要考虑使用其他创建型模式,如工厂方法模式或抽象工厂模式,以更好地符合开闭原则。
1.6.使用场景
- 工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
- 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。
1.7.总结
- 创建型模式对类的实例化过程进行了抽象,能够将对象的创建与对象的使用过程分离。
- 简单工厂模式又称为静态工厂方法模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
- 简单工厂模式包含三个角色:工厂角色负责实现创建所有实例的内部逻辑;抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口;具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
- 简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
- 简单工厂模式最大的优点在于实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责,但是其最大的缺点在于工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法代码将会非常复杂。
- 简单工厂模式适用情况包括:工厂类负责创建的对象比较少;客户端只知道传入工厂类的参数,对于如何创建对象不关心。
2.工厂方法模式(Factory Method)
2.1.定义
工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
理解:工厂方法模式它定义了一个用于创建对象的接口,但是将实际的实例化过程延迟到子类中。这样,客户端代码在创建对象时不直接指定它们的类,而是通过调用一个工厂方法来创建对象。这种方式允许一个类的实例化延迟到其子类,从而在运行时选择具体实现类,而不是在编译时进行固定选择。
关键角色包括:
1. 抽象产品(Abstract Product):定义了产品的接口,具体产品类必须实现这个接口。
2. 具体产品(Concrete Product):是抽象产品的具体实现,是最终要创建的对象。
3. 抽象工厂(Abstract Factory):声明了创建产品的抽象方法,是一个接口或者抽象类。
4. 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品的实例。
在工厂方法模式中,客户端通过调用工厂方法来创建产品,而具体工厂类决定了到底实例化哪个具体产品类。这样的设计使得系统更容易扩展,因为可以新增具体产品类和对应的工厂类,而不需要修改已有的代码。
2.2.结构
工厂方法模式包含如下角色:
- Product:抽象产品
- ConcreteProduct:具体产品
- Factory:抽象工厂
- ConcreteFactory:具体工厂
2.3.时序图
2.4.代码实现
场景1:假设有一个电脑制造公司,他们生产不同类型的电脑,包括台式电脑(Desktop)和笔记本电脑(Laptop)。为了满足不同客户的需求,该公司使用工厂方法模式来创建这些电脑。
在这个场景中:
Computer
是抽象产品,定义了电脑的基本接口。Desktop
和Laptop
是具体产品,分别表示台式电脑和笔记本电脑。ComputerFactory
是抽象工厂,声明了创建电脑的抽象方法。DesktopFactory
和LaptopFactory
是具体工厂,分别负责创建台式电脑和笔记本电脑。
using System; // 抽象产品 interface Computer { void DisplayInfo(); } // 具体产品:台式电脑 class Desktop : Computer { public void DisplayInfo() { Console.WriteLine("这是一台台式电脑。"); } } // 具体产品:笔记本电脑 class Laptop : Computer { public void DisplayInfo() { Console.WriteLine("这是一台笔记本电脑。"); } } // 抽象工厂 interface ComputerFactory { Computer CreateComputer(); } // 具体工厂:台式电脑工厂 class DesktopFactory : ComputerFactory { public Computer CreateComputer() { return new Desktop(); } } // 具体工厂:笔记本电脑工厂 class LaptopFactory : ComputerFactory { public Computer CreateComputer() { return new Laptop(); } } // 客户端代码 class Client { static void Main() { // 使用台式电脑工厂创建台式电脑 ComputerFactory desktopFactory = new DesktopFactory(); Computer desktopComputer = desktopFactory.CreateComputer(); desktopComputer.DisplayInfo(); // 使用笔记本电脑工厂创建笔记本电脑 ComputerFactory laptopFactory = new LaptopFactory(); Computer laptopComputer = laptopFactory.CreateComputer(); laptopComputer.DisplayInfo(); } }
示例中,客户端通过选择不同的具体工厂(DesktopFactory
或 LaptopFactory
),可以创建不同类型的电脑(Desktop
或 Laptop
)。通过工厂方法模式,可以方便地扩展产品和工厂,而不需要修改客户端代码。
场景2:场景描述: 假设有一个在线购物平台,该平台销售不同种类的商品,包括电子产品(Electronic)和服装(Clothing)。为了处理不同种类商品的订单,该平台使用工厂方法模式来创建订单。
在这个场景中:
Product
是抽象产品,定义了商品的基本接口。Electronic
和Clothing
是具体产品,分别表示电子产品和服装。OrderFactory
是抽象工厂,声明了创建订单的抽象方法。ElectronicOrderFactory
和ClothingOrderFactory
是具体工厂,分别负责创建电子产品订单和服装订单。
using System; // 抽象产品 interface Product { void DisplayInfo(); } // 具体产品:电子产品 class Electronic : Product { public void DisplayInfo() { Console.WriteLine("这是一件电子产品。"); } } // 具体产品:服装 class Clothing : Product { public void DisplayInfo() { Console.WriteLine("这是一件服装。"); } } // 抽象工厂 interface OrderFactory { Product CreateProduct(); } // 具体工厂:电子产品订单工厂 class ElectronicOrderFactory : OrderFactory { public Product CreateProduct() { return new Electronic(); } } // 具体工厂:服装订单工厂 class ClothingOrderFactory : OrderFactory { public Product CreateProduct() { return new Clothing(); } } // 客户端代码 class Client { static void Main() { // 使用电子产品订单工厂创建电子产品订单 OrderFactory electronicFactory = new ElectronicOrderFactory(); Product electronicProduct = electronicFactory.CreateProduct(); electronicProduct.DisplayInfo(); // 使用服装订单工厂创建服装订单 OrderFactory clothingFactory = new ClothingOrderFactory(); Product clothingProduct = clothingFactory.CreateProduct(); clothingProduct.DisplayInfo(); } }
示例中,客户端通过选择不同的具体工厂(ElectronicOrderFactory
或 ClothingOrderFactory
),可以创建不同类型的商品订单(Electronic
或 Clothing
)。通过工厂方法模式,可以方便地扩展商品和订单,而不需要修改客户端代码。
2.5.优缺点
优点:
1. 封装对象创建过程:工厂方法模式将对象的创建过程封装在工厂类中,使得客户端无需关心具体产品的实例化过程。这降低了系统的耦合性。
2. 符合开闭原则:工厂方法模式支持新产品的添加,无需修改已有的代码。通过添加新的具体产品类和相应的工厂类,系统可以轻松扩展。
3. 更好的代码组织结构:工厂方法模式提供了一种良好的代码组织结构,各个具体工厂类负责创建对应的具体产品,使得代码结构清晰,易于理解和维护。
4. 符合单一职责原则:每个具体工厂类负责创建一个具体产品,符合单一职责原则,使得每个类的职责更加清晰明确。
缺点:
1. 类的数量增加:随着具体产品类和工厂类的增加,系统中的类的数量会迅速增加。这可能会使得类的管理和维护变得复杂,增加系统的复杂性。
2. 不适用于复杂对象的创建:如果创建的对象比较复杂,包含多个部分或需要特定的配置参数,工厂方法模式可能无法提供足够的灵活性,而需要使用其他创建型模式,如抽象工厂模式。
3. 客户端需要知道具体产品类:客户端在使用工厂方法创建对象时,通常需要知道具体产品的类名,这与封装对象创建过程的初衷略有违背。客户端与具体产品之间仍然存在一定的耦合。
工厂方法模式在某些场景下是非常有用的,特别是在需要实现产品簇的情况下,它提供了一种良好的扩展机制。然而,在选择设计模式时,需根据具体的需求和系统特点综合考虑各种因素。
2.6.使用场景
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
2.7.总结
- 工厂方法模式又称为工厂模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
- 工厂方法模式包含四个角色:抽象产品是定义产品的接口,是工厂方法模式所创建对象的超类型,即产品对象的共同父类或接口;具体产品实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,它们之间往往一一对应;抽象工厂中声明了工厂方法,用于返回一个产品,它是工厂方法模式的核心,任何在模式中创建对象的工厂类都必须实现该接口;具体工厂是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例。
- 工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
- 工厂方法模式的主要优点是增加新的产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;其缺点在于增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。
- 工厂方法模式适用情况包括:一个类不知道它所需要的对象的类;一个类通过其子类来指定创建哪个对象;将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定。
3.抽象工厂模式(Abstract Factory)
3.1.定义
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
理解:抽象工厂模式是一种创建型设计模式,旨在提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。该模式属于对象创建型模式,强调创建一整个产品簇,而不是单个产品。
关键角色包括:
1. 抽象产品(Abstract Product):定义了一组相关或相互依赖的产品的接口,通常是一组抽象类或接口。
2. 具体产品(Concrete Product):实现了抽象产品接口的具体类,属于某个产品簇的具体实现。
3. 抽象工厂(Abstract Factory):声明了一组创建抽象产品的方法,形成一个产品簇的抽象。它是一个接口或者抽象类。
4. 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建一个产品簇中的具体产品。每个具体工厂对应一个产品簇。
5. 产品簇(Product Family):由一组相关或相互依赖的抽象产品组成,满足一定的业务需求。
抽象工厂模式的核心思想是通过工厂接口创建一整个产品簇,使得客户端能够交互使用相关的产品而不关心具体的实现类。这提供了一种灵活的方式,允许系统在运行时选择不同的工厂来创建不同的产品簇,以满足特定的需求或变化。
这个模式对于需要满足产品簇的变化,并且希望客户端能够独立于具体产品类的情况下使用产品的系统非常有用。通过抽象工厂模式,系统可以更容易地适应新的产品簇或变化,同时保持代码的稳定性。
3.2.结构
抽象工厂模式包含如下角色:
- AbstractFactory:抽象工厂
- ConcreteFactory:具体工厂
- AbstractProduct:抽象产品
- Product:具体产品
3.3.时序图
3.4.代码实现
场景:假设有一个汽车制造公司,该公司生产不同类型的汽车,包括小型车(Compact)和豪华车(Luxury)。每种类型的汽车都有对应的零部件,例如发动机和轮胎。为了确保汽车零部件的兼容性,该公司使用抽象工厂模式来创建不同类型的汽车和对应的零部件。
在这个场景中:
Car
是抽象产品,定义了汽车的接口。CompactCar
和LuxuryCar
是具体产品,分别表示小型车和豪华车。Engine
和Tire
是抽象产品,定义了汽车零部件的接口。CompactEngine
、LuxuryEngine
、CompactTire
和LuxuryTire
是具体产品,分别表示小型车和豪华车的发动机和轮胎。CarFactory
是抽象工厂,声明了创建汽车和零部件的抽象方法。CompactCarFactory
和LuxuryCarFactory
是具体工厂,分别负责创建小型车和豪华车的汽车和零部件。
using System; // 抽象产品:汽车 interface Car { void DisplayInfo(); } // 具体产品:小型车 class CompactCar : Car { public void DisplayInfo() { Console.WriteLine("这是一辆小型车。"); } } // 具体产品:豪华车 class LuxuryCar : Car { public void DisplayInfo() { Console.WriteLine("这是一辆豪华车。"); } } // 抽象产品:发动机 interface Engine { void Start(); } // 具体产品:小型车发动机 class CompactEngine : Engine { public void Start() { Console.WriteLine("小型车发动机启动。"); } } // 具体产品:豪华车发动机 class LuxuryEngine : Engine { public void Start() { Console.WriteLine("豪华车发动机启动。"); } } // 抽象产品:轮胎 interface Tire { void Inflate(); } // 具体产品:小型车轮胎 class CompactTire : Tire { public void Inflate() { Console.WriteLine("小型车轮胎充气。"); } } // 具体产品:豪华车轮胎 class LuxuryTire : Tire { public void Inflate() { Console.WriteLine("豪华车轮胎充气。"); } } // 抽象工厂 interface CarFactory { Car CreateCar(); Engine CreateEngine(); Tire CreateTire(); } // 具体工厂:小型车工厂 class CompactCarFactory : CarFactory { public Car CreateCar() { return new CompactCar(); } public Engine CreateEngine() { return new CompactEngine(); } public Tire CreateTire() { return new CompactTire(); } } // 具体工厂:豪华车工厂 class LuxuryCarFactory : CarFactory { public Car CreateCar() { return new LuxuryCar(); } public Engine CreateEngine() { return new LuxuryEngine(); } public Tire CreateTire() { return new LuxuryTire(); } } // 客户端代码 class Client { static void Main() { // 使用小型车工厂创建小型车和对应的零部件 CarFactory compactFactory = new CompactCarFactory(); Car compactCar = compactFactory.CreateCar(); Engine compactEngine = compactFactory.CreateEngine(); Tire compactTire = compactFactory.CreateTire(); Console.WriteLine("生产小型车:"); compactCar.DisplayInfo(); compactEngine.Start(); compactTire.Inflate(); Console.WriteLine(); // 使用豪华车工厂创建豪华车和对应的零部件 CarFactory luxuryFactory = new LuxuryCarFactory(); Car luxuryCar = luxuryFactory.CreateCar(); Engine luxuryEngine = luxuryFactory.CreateEngine(); Tire luxuryTire = luxuryFactory.CreateTire(); Console.WriteLine("生产豪华车:"); luxuryCar.DisplayInfo(); luxuryEngine.Start(); luxuryTire.Inflate(); } }
示例中,CompactCarFactory
和 LuxuryCarFactory
分别负责创建小型车和豪华车的汽车和零部件。通过选择不同的具体工厂,可以创建不同类型的汽车和对应的零部件,确保它们的兼容性。
3.5.优缺点
优点:
1. 产品簇的一致性:抽象工厂模式确保所创建的产品簇是相互关联、一致的。这意味着无论何时创建一个产品,都能保证它与其他产品协同工作。
2. 易于替换产品系列:客户端使用抽象工厂创建产品,而不是直接实例化具体产品类。这使得在不修改客户端代码的情况下,可以轻松切换不同的产品系列。
3. 符合开闭原则:抽象工厂模式支持新产品的添加,无需修改已有的代码。通过添加新的抽象产品和相应的工厂类,系统可以轻松扩展。
4. 降低客户端与具体类的耦合: 客户端代码仅与抽象工厂和抽象产品接口交互,不涉及具体类。这降低了客户端与具体实现的耦合度,使系统更加灵活。
缺点:
1. 产品簇的扩展复杂:如果需要添加新的产品簇,即新的抽象产品和抽象工厂,系统的扩展会变得复杂。需要修改所有的具体工厂类以支持新的产品簇,这可能导致代码的变动较大。
2. 单一产品簇的切换困难:抽象工厂模式设计的初衷是创建一整个产品簇,如果只需要切换某一个具体产品,可能需要引入额外的复杂性,不太适合单一产品簇的切换。
3. 不够灵活:在产品的组合方面,抽象工厂模式可能不够灵活。如果产品簇中的某些产品组合不常变化,但需要独立变化其中一个,可能需要考虑其他创建型模式,如建造者模式。
抽象工厂模式适用于需要创建一系列相关对象的情况,特别是在涉及产品簇的情况下。然而,在使用这种模式时,需要仔细权衡系统的灵活性和复杂性,根据具体情况选择合适的设计方案。
3.6.使用场景
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
- 系统中有多于一个的产品族,而每次只使用其中某一产品族。
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
- 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
3.7.总结
- 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
- 抽象工厂模式包含四个角色:抽象工厂用于声明生成抽象产品的方法;具体工厂实现了抽象工厂声明的生成抽象产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中;抽象产品为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法;具体产品定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。
- 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。
- 抽象工厂模式的主要优点是隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;主要缺点在于增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。
- 抽象工厂模式适用情况包括:一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节;系统中有多于一个的产品族,而每次只使用其中某一产品族;属于同一个产品族的产品将在一起使用;系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
4.建造者模式(Builder)
4.1.定义
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式。
理解:建造者模式(Builder Pattern)是一种创建型设计模式,它允许你创建一个复杂对象的表示,而无需直接实例化其具体组件。该模式提供一种将构建过程和表示分离的方法,使得同样的构建过程可以创建不同的表示。
关键角色包括:
1. 产品(Product):表示被构建的复杂对象。该对象通常包含多个部分,而且这些部分之间可能存在复杂的关联关系。
2. 抽象建造者(Builder):定义了创建产品各个部分的抽象接口。具体的建造者将实现这个接口以构建和装配产品的各个部分。
3. 具体建造者(Concrete Builder):实现了抽象建造者接口,负责构建和装配产品的具体部分。通常还提供一个用于获取构建后产品的方法。
4. 指导者(Director):负责使用建造者接口构建产品的对象。指导者通常不直接创建产品,而是通过调用建造者的方法来构建产品。
5. 客户端(Client):调用指导者来构建产品,或者直接使用具体建造者来自定义构建过程。
建造者模式的关键思想在于将构建过程抽象出来,使得构建算法和具体的表示相互独立。
4.2.结构
建造者模式包含如下角色:
- Builder:抽象建造者
- ConcreteBuilder:具体建造者
- Director:指挥者
- Product:产品角色
4.3.时序图
4.4.代码实现
场景:一个快餐店提供不同类型的套餐,包括汉堡(Burger)、饮料(Drink)和甜点(Dessert)。每种套餐都有不同的组成部分,例如汉堡的类型、饮料的大小和甜点的种类。为了创建不同类型的套餐,该快餐店使用建造者模式。
在这个场景中:
Meal
是产品类,表示套餐,包含汉堡、饮料和甜点。MealBuilder
是抽象建造者接口,定义了构建套餐各个部分的抽象方法。Burger
、Drink
和Dessert
是产品部件,表示汉堡、饮料和甜点。VeggieBurger
、Coke
和IceCream
是具体产品部件,分别表示素食汉堡、可乐和冰淇淋。MealDirector
是指导者,负责使用建造者接口构建产品。Program
是客户端代码,负责调用指导者构建具体类型的套餐。
using System; // 产品部件:汉堡 class Burger { public string Type { get; set; } public void DisplayInfo() { Console.WriteLine($"汉堡: {Type}"); } } // 产品部件:饮料 class Drink { public string Size { get; set; } public void DisplayInfo() { Console.WriteLine($"饮料: {Size}"); } } // 产品部件:甜点 class Dessert { public string Flavor { get; set; } public void DisplayInfo() { Console.WriteLine($"甜点: {Flavor}"); } } // 抽象建造者接口 interface MealBuilder { void BuildBurger(); void BuildDrink(); void BuildDessert(); Meal GetMeal(); } // 具体建造者:套餐A建造者 class MealBuilderA : MealBuilder { private Meal meal = new Meal(); public void BuildBurger() { meal.Burger = new VeggieBurger(); } public void BuildDrink() { meal.Drink = new Coke(); } public void BuildDessert() { meal.Dessert = new IceCream(); } public Meal GetMeal() { return meal; } } // 具体建造者:套餐B建造者 class MealBuilderB : MealBuilder { private Meal meal = new Meal(); public void BuildBurger() { meal.Burger = new ChickenBurger(); } public void BuildDrink() { meal.Drink = new Pepsi(); } public void BuildDessert() { meal.Dessert = new Cake(); } public Meal GetMeal() { return meal; } } // 具体产品部件:素食汉堡 class VeggieBurger : Burger { public VeggieBurger() { Type = "素食汉堡"; } } // 具体产品部件:鸡肉汉堡 class ChickenBurger : Burger { public ChickenBurger() { Type = "鸡肉汉堡"; } } // 具体产品部件:可乐 class Coke : Drink { public Coke() { Size = "中杯"; } } // 具体产品部件:百事可乐 class Pepsi : Drink { public Pepsi() { Size = "大杯"; } } // 具体产品部件:冰淇淋 class IceCream : Dessert { public IceCream() { Flavor = "香草味"; } } // 具体产品部件:蛋糕 class Cake : Dessert { public Cake() { Flavor = "巧克力味"; } } // 产品类:套餐 class Meal { public Burger Burger { get; set; } public Drink Drink { get; set; } public Dessert Dessert { get; set; } public void DisplayInfo() { Console.WriteLine("套餐详情:"); Burger.DisplayInfo(); Drink.DisplayInfo(); Dessert.DisplayInfo(); } } // 指导者:负责使用建造者接口构建产品 class MealDirector { public void Construct(MealBuilder builder) { builder.BuildBurger(); builder.BuildDrink(); builder.BuildDessert(); } } // 客户端代码 class Program { static void Main() { // 创建指导者 MealDirector director = new MealDirector(); // 创建套餐A MealBuilder builderA = new MealBuilderA(); director.Construct(builderA); Meal mealA = builderA.GetMeal(); Console.WriteLine("套餐A:"); mealA.DisplayInfo(); Console.WriteLine(); // 创建套餐B MealBuilder builderB = new MealBuilderB(); director.Construct(builderB); Meal mealB = builderB.GetMeal(); Console.WriteLine("套餐B:"); mealB.DisplayInfo(); } }
示例中,MealBuilderA
和 MealBuilderB
分别负责构建不同类型的套餐,每个套餐包括了不同类型的汉堡、饮料和甜点。MealDirector
负责使用建造者接口构建套餐。客户端代码通过创建具体的建造者对象,并调用指导者的构造方法,可以创建不同类型的套餐。这种方式使得构建过程与产品的表示相互独立,使得系统更加灵活。
4.5.优缺点
优点:
1. 构建过程与表示分离:建造者模式允许你将一个复杂对象的构建过程和它的表示分离。这使得你可以更灵活地控制对象的构建过程,同时可以使用相同的构建过程创建不同的表示。
2. 更好的可读性:由于建造者模式使用了逐步构建的方式,每一步都有明确的方法调用,提高了代码的可读性。客户端代码更易理解,不需要知道对象的内部构建细节。
3. 更好的控制复杂性:建造者模式有助于管理复杂对象的构建过程,避免了在一个类中拥有众多构造函数或者构造函数参数的问题。每个具体建造者负责一部分构建,降低了代码复杂性。
4. 支持变化:如果产品的构建过程需要变化,可以创建新的具体建造者,而无需修改客户端代码。这符合开闭原则,使得系统更易于扩展。
5. 适用于不同需求:通过定义不同的具体建造者,可以构建出不同的产品表示。这使得建造者模式非常适用于需要生成多种具有不同特征的对象的情况。
缺点:
1. 增加了代码量:使用建造者模式可能会增加代码量,因为需要创建多个具体建造者和产品类。在对象的结构相对简单的情况下,引入建造者模式可能显得繁琐。
2. 不适用于一步构建的情况:如果对象的构建过程可以在一步中完成,而且不涉及复杂的逻辑,那么引入建造者模式可能会显得过于复杂。
3. 不容易理解:对于一些简单的对象,引入建造者模式可能会使代码过于复杂,不容易理解。在这种情况下,更简单的创建型模式可能更为合适。
建造者模式适用于需要创建复杂对象、并且希望灵活控制构建过程的情况。在设计阶段,需要权衡模式的优点和缺点,选择合适的设计方案。
4.6.使用场景
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
- 对象的创建过程独立于创建该对象的类。在建造者模式中引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类中。
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
4.7.总结
- 建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。
- 建造者模式包含如下四个角色:抽象建造者为创建一个产品对象的各个部件指定抽象接口;具体建造者实现了抽象建造者接口,实现各个部件的构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象;产品角色是被构建的复杂对象,包含多个组成部件;指挥者负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造
- 在建造者模式的结构中引入了一个指挥者类,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。
- 建造者模式的主要优点在于客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,符合“开闭原则”,还可以更加精细地控制产品的创建过程;其主要缺点在于由于建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,因此其使用范围受到一定的限制,如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
- 建造者模式适用情况包括:需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性;需要生成的产品对象的属性相互依赖,需要指定其生成顺序;对象的创建过程独立于创建该对象的类;隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同类型的产品。
5.原型模式(Prototype)
5.1.定义
原型模式是一种创建型设计模式,用于通过复制现有对象来创建新对象。该模式通过声明一个克隆方法在原型接口中,由具体的原型类实现,使得客户端代码可以使用克隆方法创建新的对象,而无需了解对象的具体类型和内部结构。原型模式适用于对象的创建成本较高,或者对象的创建过程复杂的情况。
理解:原型模式它允许通过复制现有对象来创建新对象,而无需从头开始重新构建。该模式通过声明一个克隆方法在原型接口中,由具体的原型类实现,使得客户端代码可以使用克隆方法创建新的对象,而无需了解对象的具体类型和内部结构。原型模式适用于对象的创建成本较高,或者对象的创建过程复杂的情况。
关键角色包括:
1. 原型接口(Prototype Interface):定义了克隆方法的接口,通常包含一个 `Clone` 方法,用于复制对象。
2. 具体原型类(Concrete Prototype):实现了原型接口的具体类。它负责实现克隆方法,以便在创建新对象时进行复制。
3. 客户端(Client):使用原型模式创建新对象的代码。客户端通过调用原型对象的克隆方法来获取新的对象,而无需了解对象的具体类型和内部实现。
原型模式的关键思想是通过克隆方法来创建新对象,从而避免了昂贵的对象创建过程。这种模式使得系统更加灵活,能够动态地添加或删除对象。
5.2.结构
原型模式包含如下角色:
- Prototype:抽象原型类
- ConcretePrototype:具体原型类
- Client:客户类
5.3.代码实现
场景:一个图形绘制应用程序,用户可以创建和编辑各种图形,如圆形、矩形等。每种图形都有不同的属性和方法,用户可以根据已有的图形创建新的图形。使用原型模式,可以通过克隆已有的图形对象来创建新的图形,而无需了解每种图形的具体实现。
在这个场景中:
Shape
是图形的抽象原型接口,声明了克隆方法。Circle
和Rectangle
是具体原型类,实现了Shape
接口的克隆方法,分别表示圆形和矩形。GraphicEditor
是图形编辑器,负责创建和编辑图形,使用原型模式创建新的图形。
using System; // 原型接口:图形 interface Shape { Shape Clone(); void Draw(); } // 具体原型类:圆形 class Circle : Shape { private int radius; public Circle(int radius) { this.radius = radius; } // 实现克隆方法 public Shape Clone() { // 使用成员wise复制 return new Circle(this.radius); } // 实现绘制方法 public void Draw() { Console.WriteLine($"绘制圆形,半径为 {radius}"); } } // 具体原型类:矩形 class Rectangle : Shape { private int width; private int height; public Rectangle(int width, int height) { this.width = width; this.height = height; } // 实现克隆方法 public Shape Clone() { // 使用成员wise复制 return new Rectangle(this.width, this.height); } // 实现绘制方法 public void Draw() { Console.WriteLine($"绘制矩形,宽度为 {width},高度为 {height}"); } } // 图形编辑器:负责创建和编辑图形 class GraphicEditor { // 创建新图形的方法 public Shape CreateShape(Shape prototype) { // 克隆原型图形 Shape newShape = prototype.Clone(); // 进行一些编辑操作,例如位置调整、颜色修改等 Console.WriteLine("编辑图形完成"); return newShape; } } // 客户端代码 class Program { static void Main() { // 创建图形编辑器 GraphicEditor editor = new GraphicEditor(); // 创建原型图形 Shape circlePrototype = new Circle(10); Shape rectanglePrototype = new Rectangle(5, 8); // 创建新图形并绘制 Shape newCircle = editor.CreateShape(circlePrototype); newCircle.Draw(); Shape newRectangle = editor.CreateShape(rectanglePrototype); newRectangle.Draw(); } }
示例中,Shape
接口是图形的原型接口,声明了克隆方法和绘制方法。Circle
和 Rectangle
分别是具体的原型类,实现了克隆方法和绘制方法。GraphicEditor
是图形编辑器,通过克隆方法创建新的图形,并进行一些编辑操作。在客户端代码中,我们创建了原型图形(圆形和矩形),然后通过图形编辑器创建新的图形,并绘制出来。这样,我们就实现了图形的动态创建和编辑。
5.4.优缺点
优点:
1. 减少对象创建成本:原型模式通过复制已有对象来创建新对象,避免了从头开始构建对象的成本,特别是在对象的创建过程比较昂贵时,能够显著提高性能。
2. 简化对象创建过程:使用原型模式,客户端代码无需了解对象的具体实现和构建过程,只需通过克隆方法即可创建新对象,使得客户端代码更为简洁。
3. 动态添加或删除对象:原型模式使得系统能够动态地添加或删除对象,而无需修改客户端代码。客户端可以根据需要复制现有对象,而不用关心具体对象的类型。
4. 避免重复初始化:对于那些需要频繁创建相似对象的情况,原型模式可以避免重复地执行初始化代码,提高了对象的创建效率。
缺点:
1. 破坏封装性:如果对象的构造过程涉及到复杂的初始化逻辑、依赖关系等,克隆可能无法完全复制对象的内部状态,破坏了对象的封装性。
2. 可能引入全局变量:如果滥用原型模式,将克隆的对象当作全局变量使用,可能导致代码难以理解和维护。每个对象的状态变化都会影响到其他对象。
3. 需要注意浅克隆和深克隆:原型模式中的克隆通常分为浅克隆和深克隆。浅克隆仅复制对象本身,而不复制引用的对象;深克隆会连同引用的对象一同复制。在使用时需要注意选择适当的克隆方式。
4. 可能引入风险:在使用原型模式时,需要确保克隆的对象是稳定的,不会引入潜在的风险。否则,可能会导致程序运行时的不确定性。
原型模式是一种有力的对象创建方式,特别适用于需要复制对象的场景。然而,在使用时需要谨慎考虑具体的应用场景和模式的适用性,以充分发挥其优点并避免潜在的缺点。
5.5.使用场景
- 对象创建成本较高:当对象的创建过程相对昂贵,包括复杂的初始化、数据库访问、文件操作等,通过克隆已有对象来创建新对象可以显著降低创建成本。
- 相似对象的创建:当需要创建大量相似但不完全相同的对象时,使用原型模式能够简化创建过程,提高性能。客户端可以通过克隆现有对象来创建新对象,而无需关心对象的具体实现。
- 避免构造函数复杂性:当对象的构造函数包含复杂的逻辑、依赖关系等,使用原型模式可以避免重复执行构造函数的复杂初始化代码。
- 动态配置对象:当需要动态地配置对象,根据用户输入或运行时条件创建不同的对象配置时,原型模式能够方便地通过克隆来生成新的配置,而不需要重新创建和配置对象。
- 动态添加或删除对象:当系统需要动态地添加或删除对象时,原型模式允许客户端通过克隆方法创建新对象,而无需修改客户端代码,从而保持系统的灵活性。
- 支持多态性的场景:当需要在运行时选择具体实现类,并希望系统能够动态地切换实现类时,原型模式可以通过克隆来创建不同的实现类的对象。
5.6.总结
- 一个对象的创建过程包括繁琐的准备工作或重量级的资源初始化,那么每次需要创建新对象时,都需要必须执行这些初始操作,这时就可以使用原型模式,通过复制旧对象来创建新对象,从而避免创建成本高的问题。
- 对象需要修改的属性较多,使用原型模式则可以在原始对象的基础上进行修改,减少代码量。
- 存在多个对象需要共享同一个数据源,可以使用原型模式基于已有的原始对象来进行克隆,避免了重复创建多个对象。
- 当对象的创建过程涉及多个线程时,需要注意线程安全性。原型模式可以用于在不同的线程之间共享原型对象,并在每个线程中创建对象的副本,确保线程安全性。
6.单例模式(Singleton)
6.1.定义
单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。
理解:单例模式其主要目的是确保一个类只有一个实例,并提供一个全局访问点以访问该实例。这种模式通常在需要对某个对象全局唯一性和可控性的情况下使用。
关键要点:
1. 唯一实例:单例模式确保一个类只有一个实例存在。该实例由类自身负责创建,并提供一个全局的访问点,使得其他代码可以获取到该实例。
2. 私有构造函数:单例类通常会将其构造函数设为私有,以防止外部直接实例化该类。通过私有构造函数,单例类可以控制实例的创建过程。
3. 静态成员和方法:单例模式通常通过静态成员变量和静态方法实现。静态成员变量用于保存唯一的实例,而静态方法用于获取该实例。
4. 懒加载或饿汉式:单例模式可以采用懒加载(Lazy Initialization)的方式,即在第一次访问时才创建实例;也可以采用饿汉式(Eager Initialization),即在类加载时就创建实例。懒加载可以节省资源,但可能引入线程安全问题,需要特别注意。
5. 线程安全性:当多个线程同时访问单例类时,需要确保实例的创建是线程安全的。可以采用加锁的方式来保证线程安全,也可以使用双重检查锁定等机制。
6. 全局访问点:单例模式提供一个全局的访问点,允许其他代码通过该访问点获取到单例实例。这有助于统一控制和管理某个类的唯一实例。
单例模式通常应用于需要在系统中全局唯一性、共享资源、或控制访问的场景。然而,在某些情况下,使用单例模式可能引入全局状态,需要谨慎使用,以避免破坏系统的可维护性和可测试性。
6.2.结构
单例模式包含如下角色:
- Singleton:单例
6.3.时序图
6.4.代码实现
场景1:一个日志记录器的情景,该日志记录器负责记录系统中的日志信息。由于日志记录是一个共享的、全局唯一的功能,我们希望确保系统中只有一个日志记录器实例。
在这个场景中:
Logger
类是单例类,负责记录系统日志信息。GetInstance
方法是获取单例实例的方法,确保只有一个实例存在。LogMessage
方法是模拟记录日志的功能。
using System; // 单例类:日志记录器 public class Logger { // 私有静态成员变量,用于保存唯一的实例 private static Logger instance; // 私有构造函数,防止外部直接实例化该类 private Logger() { } // 获取单例实例的方法 public static Logger GetInstance() { // 如果实例尚未创建,则创建一个新实例 if (instance == null) { instance = new Logger(); } // 返回唯一的实例 return instance; } // 记录日志的方法 public void LogMessage(string message) { Console.WriteLine($"记录日志: {message}"); } } // 客户端代码 class Program { static void Main() { // 获取日志记录器实例 Logger logger1 = Logger.GetInstance(); Logger logger2 = Logger.GetInstance(); // 验证两个实例是否相同 Console.WriteLine($"logger1 和 logger2 是否相同: {logger1 == logger2}"); // 记录日志 logger1.LogMessage("这是一条日志消息。"); logger2.LogMessage("另一条日志消息。"); } }
示例中,Logger
类是单例类,通过将构造函数设为私有,确保外部无法直接实例化。GetInstance
方法用于获取单例实例,确保只有一个实例存在。客户端代码通过调用 Logger.GetInstance()
获取日志记录器的实例,然后可以通过该实例调用 LogMessage
方法记录日志。由于单例模式的特性,logger1
和 logger2
实际上是同一个实例,确保了全局唯一性。
场景2:一个配置管理器的情境,该配置管理器负责加载系统配置信息,并提供全局访问点以便其他模块获取配置。
在这个场景中:
ConfigurationManager
类是单例类,负责加载和管理系统配置信息。GetInstance
方法是获取单例实例的方法,确保只有一个实例存在。LoadConfiguration
方法是模拟加载配置信息的功能。GetConfiguration
方法用于获取系统配置信息。
using System; // 单例类:配置管理器 public class ConfigurationManager { // 私有静态成员变量,用于保存唯一的实例 private static ConfigurationManager instance; // 配置信息 private string configuration; // 私有构造函数,防止外部直接实例化该类 private ConfigurationManager() { } // 获取单例实例的方法 public static ConfigurationManager GetInstance() { // 如果实例尚未创建,则创建一个新实例 if (instance == null) { instance = new ConfigurationManager(); } // 返回唯一的实例 return instance; } // 模拟加载配置信息的方法 public void LoadConfiguration(string config) { configuration = config; Console.WriteLine($"加载配置信息: {configuration}"); } // 获取系统配置信息的方法 public string GetConfiguration() { return configuration; } } // 客户端代码 class Program { static void Main() { // 获取配置管理器实例 ConfigurationManager configManager1 = ConfigurationManager.GetInstance(); ConfigurationManager configManager2 = ConfigurationManager.GetInstance(); // 验证两个实例是否相同 Console.WriteLine($"configManager1 和 configManager2 是否相同: {configManager1 == configManager2}"); // 模拟加载和获取配置信息 configManager1.LoadConfiguration("配置信息A"); string configInfo = configManager2.GetConfiguration(); Console.WriteLine($"获取配置信息: {configInfo}"); } }
示例中,ConfigurationManager
类是单例类,通过将构造函数设为私有,确保外部无法直接实例化。GetInstance
方法用于获取单例实例,确保只有一个实例存在。客户端代码通过调用 ConfigurationManager.GetInstance()
获取配置管理器的实例,然后可以通过该实例调用 LoadConfiguration
方法模拟加载配置信息,以及调用 GetConfiguration
方法获取系统配置信息。由于单例模式的特性,configManager1
和 configManager2
实际上是同一个实例,确保了全局唯一性。
6.5.优缺点
优点:
1. 全局唯一性:单例模式确保系统中只有一个实例存在,这对于需要全局唯一性的对象非常有用,如配置管理器、日志记录器等。
2. 全局访问点:单例模式提供一个全局的访问点,使得其他对象能够方便地获取单例实例。这简化了对象之间的通信。
3. 懒加载或饿汉式:单例模式可以采用懒加载(Lazy Initialization)的方式,在第一次访问时才创建实例,从而节省资源。或者采用饿汉式(Eager Initialization),在类加载时就创建实例,避免了多线程并发问题。
4. 避免资源浪费:由于单例模式只创建一个实例,可以避免重复创建相同的对象,从而节省系统资源。
5. 控制访问:单例模式允许通过一个特定的入口点访问唯一实例,从而在创建实例时对访问进行控制,例如在初始化时执行一些操作。
缺点:
1. 全局状态:单例模式引入了全局状态,可能对代码的可维护性和可测试性产生负面影响。因为单例实例在整个应用程序中可被访问,可能导致代码的耦合性增加。
2. 违反单一职责原则:单例模式既负责创建实例,又负责控制访问。这可能违反了单一职责原则,使得类的责任过于庞大。
3. 可能引入全局变量:单例模式可能被误用,变成全局变量。如果滥用单例模式,可能会导致代码难以理解和维护。
4. 不适用于多线程环境:在多线程环境中,懒加载方式可能引入竞态条件(Race Condition),需要额外的同步机制来保证线程安全。
5. 可能导致内存泄漏:如果实例被长时间持有,而没有被正确释放,可能导致内存泄漏。
在使用单例模式时,需要根据具体场景仔细权衡其优点和缺点,确保在系统设计中合理应用,以避免引入不必要的复杂性。
6.6.使用场景
- 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
- 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,使之成为多例模式
6.7.总结
- 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。
- 单例模式只包含一个单例角色:在单例类的内部实现只生成一个实例,同时它提供一个静态的工厂方法,让客户可以使用它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有。
- 单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法。该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。
- 单例模式的主要优点在于提供了对唯一实例的受控访问并可以节约系统资源;其主要缺点在于因为缺少抽象层而难以扩展,且单例类职责过重。
- 单例模式适用情况包括:系统只需要一个实例对象;客户调用类的单个实例只允许使用一个公共访问点。