创建型模式关注对象的创建过程,旨在使对象的创建与使用分离,避免系统与具体类之间的紧密耦合。
结构型模式关注类和对象的组合,旨在通过组合接口和实现来实现新的功能。
行为型模式关注对象之间的通信和职责分配,旨在提高对象之间的通信效率和灵活性。
简单工厂模式,也称为静态工厂方法模式,并不属于GoF(Gang of Four)设计模式之一。它提供了一个创建对象的简单接口,通过传递不同的参数来决定创建哪种类的实例。
简单工厂模式包含以下角色:
UML 类图
+-------------------+
| Factory |
+-------------------+
| + CreateProduct() |
+-------------------+
|
v
+-------------------+
| Product |
+-------------------+
/ \
/ \
+--------+ +---------+
|ProductA| |ProductB |
+--------+ +---------+
// 产品接口
public interface IProduct
{
void Display();
}
// 具体产品A
public class ProductA : IProduct
{
public void Display()
{
Console.WriteLine("Product A");
}
}
// 具体产品B
public class ProductB : IProduct
{
public void Display()
{
Console.WriteLine("Product B");
}
}
// 简单工厂
public class SimpleFactory
{
public static IProduct CreateProduct(string type)
{
if (type == "A")
{
return new ProductA();
}
else if (type == "B")
{
return new ProductB();
}
else
{
throw new ArgumentException("Invalid type");
}
}
}
// 客户端代码
class Program
{
static void Main(string[] args)
{
IProduct product = SimpleFactory.CreateProduct("A");
product.Display();
}
}
工厂方法模式定义了一个用于创建对象的接口,但由子类决定实例化哪一个类。通过这种方式,工厂方法将对象的创建推迟到子类。
工厂方法模式包含以下角色:
Product
接口或继承 Product
抽象类的具体类。Product
类型的对象。也可以包含一些默认实现。UML 类图
+---------------------+
| Creator |
+---------------------+
| + FactoryMethod() |
+---------------------+
|
|
v
+---------------------+
| ConcreteCreator |
+---------------------+
| + FactoryMethod() |
+---------------------+
|
|
v
+---------------------+
| ConcreteProduct |
+---------------------+
| Implements Product |
+---------------------+
假设我们要创建不同类型的日志记录器(如控制台日志记录器和文件日志记录器),可以使用工厂方法模式。
// 产品接口
public interface ILogger
{
void Log(string message);
}
// 具体产品1:控制台日志记录器
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine("Console Logger: " + message);
}
}
// 具体产品2:文件日志记录器
public class FileLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine("File Logger: " + message);
}
}
// 抽象创建者
public abstract class LoggerFactory
{
public abstract ILogger CreateLogger();
public void WriteLog(string message)
{
var logger = CreateLogger();
logger.Log(message);
}
}
// 具体创建者1
public class ConsoleLoggerFactory : LoggerFactory
{
public override ILogger CreateLogger()
{
return new ConsoleLogger();
}
}
// 具体创建者2
public class FileLoggerFactory : LoggerFactory
{
public override ILogger CreateLogger()
{
return new FileLogger();
}
}
// 客户端代码
class Program
{
static void Main(string[] args)
{
LoggerFactory loggerFactory = new ConsoleLoggerFactory();
loggerFactory.WriteLog("This is a console log.");
loggerFactory = new FileLoggerFactory();
loggerFactory.WriteLog("This is a file log.");
}
}
抽象工厂模式提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。它适用于需要创建多个产品族的情况。
抽象工厂模式包含以下角色:
UML 类图
+-------------------------+
| AbstractFactory |
+-------------------------+
| + CreateProductA() |
| + CreateProductB() |
+-------------------------+
/|\
|
+-------------------------+
| ConcreteFactory1 |
+-------------------------+
| + CreateProductA() |
| + CreateProductB() |
+-------------------------+
|
+-------------------------+
| ConcreteFactory2 |
+-------------------------+
| + CreateProductA() |
| + CreateProductB() |
+-------------------------+
/|\
|
+----------------------------+ +----------------------------+
| AbstractProductA | | AbstractProductB |
+----------------------------+ +----------------------------+
| + OperationA() | | + OperationB() |
+----------------------------+ +----------------------------+
/|\ /|\
| |
+----------------------------+ +----------------------------+
| ProductA1 | | ProductB1 |
+----------------------------+ +----------------------------+
| + OperationA() | | + OperationB() |
+----------------------------+ +----------------------------+
|
+----------------------------+
| ProductA2 |
+----------------------------+
| + OperationA() |
+----------------------------+
假设我们要创建两种风格的家具:现代风格和维多利亚风格,每种风格包括椅子和沙发。我们可以使用抽象工厂模式来实现。
// 抽象产品A:椅子
public interface IChair
{
void SitOn();
}
// 抽象产品B:沙发
public interface ISofa
{
void LieOn();
}
// 具体产品A1:现代椅子
public class ModernChair : IChair
{
public void SitOn()
{
Console.WriteLine("Sitting on a modern chair.");
}
}
// 具体产品B1:现代沙发
public class ModernSofa : ISofa
{
public void LieOn()
{
Console.WriteLine("Lying on a modern sofa.");
}
}
// 具体产品A2:维多利亚椅子
public class VictorianChair : IChair
{
public void SitOn()
{
Console.WriteLine("Sitting on a Victorian chair.");
}
}
// 具体产品B2:维多利亚沙发
public class VictorianSofa : ISofa
{
public void LieOn()
{
Console.WriteLine("Lying on a Victorian sofa.");
}
}
// 抽象工厂
public interface IFurnitureFactory
{
IChair CreateChair();
ISofa CreateSofa();
}
// 具体工厂1:现代家具工厂
public class ModernFurnitureFactory : IFurnitureFactory
{
public IChair CreateChair()
{
return new ModernChair();
}
public ISofa CreateSofa()
{
return new ModernSofa();
}
}
// 具体工厂2:维多利亚家具工厂
public class VictorianFurnitureFactory : IFurnitureFactory
{
public IChair CreateChair()
{
return new VictorianChair();
}
public ISofa CreateSofa()
{
return new VictorianSofa();
}
}
// 客户端代码
class Program
{
static void Main(string[] args)
{
IFurnitureFactory modernFactory = new ModernFurnitureFactory();
IChair modernChair = modernFactory.CreateChair();
ISofa modernSofa = modernFactory.CreateSofa();
modernChair.SitOn();
modernSofa.LieOn();
IFurnitureFactory victorianFactory = new VictorianFurnitureFactory();
IChair victorianChair = victorianFactory.CreateChair();
ISofa victorianSofa = victorianFactory.CreateSofa();
victorianChair.SitOn();
victorianSofa.LieOn();
}
}
在实际应用中,工厂方法模式较适合较简单的对象创建场景,而抽象工厂模式则适合更复杂的、多产品族的对象创建需求。
单例模式确保一个类只有一个实例,并提供一个访问该实例的全局访问点。它可以防止类被多次实例化,并且在某些情况下可以节省内存、确保一致性或控制资源的访问。
单例模式的主要角色包括:
UML 类图
+----------------------+
| Singleton |
+----------------------+
| - instance: Singleton|
+----------------------+
| + getInstance(): |
| Singleton |
+----------------------+
单例模式的实现有多种方式,以下是最常见的几种。
懒汉式(Lazy Initialization)
懒汉式实现中,实例在第一次调用 getInstance()
方法时才被创建。这种方式可以延迟实例的创建,节省资源,但在多线程环境下需要进行同步以保证线程安全。
public class Singleton
{
private static Singleton _instance;
// 构造函数设置为私有,防止通过new创建实例
private Singleton() { }
public static Singleton GetInstance()
{
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
线程安全的懒汉式
为了保证线程安全,可以在 getInstance
方法上添加 lock
关键字,但这样可能会降低性能。
public class Singleton
{
private static Singleton _instance;
private static readonly object _lock = new object();
private Singleton() { }
public static Singleton GetInstance()
{
lock (_lock)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
return _instance;
}
}
双重检查锁定(Double-Check Locking)
这种方法在检查实例是否已经存在时只加一次锁,提高了性能。这是线程安全且高效的实现方式。
public class Singleton
{
private static Singleton _instance;
private static readonly object _lock = new object();
private Singleton() { }
public static Singleton GetInstance()
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
饿汉式(Eager Initialization)
饿汉式在类加载时就创建实例,因此不存在线程安全问题,但如果实例比较大且未使用时,会浪费资源。
public class Singleton
{
private static readonly Singleton _instance = new Singleton();
// 构造函数设置为私有,防止通过new创建实例
private Singleton() { }
public static Singleton GetInstance()
{
return _instance;
}
}
静态内部类(Static Inner Class)
使用静态内部类的方式可以实现延迟加载和线程安全。静态内部类的实例只会在第一次被引用时初始化,因此可以实现懒加载效果。
public class Singleton
{
private Singleton() { }
private static class SingletonHolder
{
internal static readonly Singleton _instance = new Singleton();
}
public static Singleton GetInstance()
{
return SingletonHolder._instance;
}
}
枚举(Enum)
使用枚举来实现单例是最简单和安全的方式,因为枚举实例化是线程安全的,并且只会被实例化一次。这种方式不仅实现了单例,而且还能防止反序列化和反射攻击。
public enum Singleton
{
Instance;
public void SomeMethod()
{
Console.WriteLine("Singleton method called.");
}
}
优点:
控制实例数量: 确保系统中只有一个实例存在,减少内存开销。
全局访问点: 提供了一个全局访问点,便于共享实例。
避免资源冲突: 多个线程或进程访问同一资源时,单例模式可以有效地避免冲突。
缺点:
不易扩展: 由于单例类不能被继承,或者不应该被继承,导致其难以扩展。
隐藏依赖: 单例模式通过全局访问点共享状态,可能导致隐藏依赖,使得代码难以理解和测试。
多线程问题: 在多线程环境下实现单例模式需要小心处理,否则可能导致线程安全问题。
单例模式在实际开发中非常常见,但在使用时要注意其潜在的缺陷,特别是在多线程和高并发的环境下,需要选择合适的实现方式以确保线程安全。
建造者模式将一个复杂对象的构造过程分离出来,使得相同的构造过程可以创建不同的表示。它使用多个简单的对象一步一步构建复杂对象,通过不同的建造者实现不同的构建方式。
建造者模式主要包含以下角色:
Builder
接口,构建和装配各个部分。Builder
接口,按步骤构建产品。UML 类图
+-------------------+ +-------------------+
| Director | | Builder |
+-------------------+ +-------------------+
| - Construct() |<------------->| + BuildPart() |
+-------------------+ +-------------------+
/|\ |
| |
| |
| +-------------------+
| | ConcreteBuilder |
| +-------------------+
| | + BuildPart() |
| | + GetResult() |
| +-------------------+
|
|
+------------------+
| Product |
+------------------+
| + AddPart() |
+------------------+
下面是一个用建造者模式构建复杂对象的示例。在这个例子中,我们通过建造者模式来创建一种复杂的 House
对象,包含多个部分如地基、墙壁和屋顶。
产品类
// 产品:House
public class House
{
private List<string> parts = new List<string>();
public void AddPart(string part)
{
parts.Add(part);
}
public void ShowParts()
{
Console.WriteLine("House parts:");
foreach (var part in parts)
{
Console.WriteLine(part);
}
}
}
抽象建造者
// 抽象建造者
public abstract class HouseBuilder
{
protected House house = new House();
public abstract void BuildFoundation();
public abstract void BuildWalls();
public abstract void BuildRoof();
public House GetResult()
{
return house;
}
}
具体建造者
// 具体建造者1:建造木质房屋
public class WoodenHouseBuilder : HouseBuilder
{
public override void BuildFoundation()
{
house.AddPart("Wooden Foundation");
}
public override void BuildWalls()
{
house.AddPart("Wooden Walls");
}
public override void BuildRoof()
{
house.AddPart("Wooden Roof");
}
}
// 具体建造者2:建造石质房屋
public class StoneHouseBuilder : HouseBuilder
{
public override void BuildFoundation()
{
house.AddPart("Stone Foundation");
}
public override void BuildWalls()
{
house.AddPart("Stone Walls");
}
public override void BuildRoof()
{
house.AddPart("Stone Roof");
}
}
指挥者
// 指挥者
public class ConstructionDirector
{
private HouseBuilder _houseBuilder;
public void SetBuilder(HouseBuilder builder)
{
_houseBuilder = builder;
}
public void ConstructHouse()
{
_houseBuilder.BuildFoundation();
_houseBuilder.BuildWalls();
_houseBuilder.BuildRoof();
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
var director = new ConstructionDirector();
// 建造木质房屋
var woodenHouseBuilder = new WoodenHouseBuilder();
director.SetBuilder(woodenHouseBuilder);
director.ConstructHouse();
House woodenHouse = woodenHouseBuilder.GetResult();
woodenHouse.ShowParts();
Console.WriteLine();
// 建造石质房屋
var stoneHouseBuilder = new StoneHouseBuilder();
director.SetBuilder(stoneHouseBuilder);
director.ConstructHouse();
House stoneHouse = stoneHouseBuilder.GetResult();
stoneHouse.ShowParts();
}
}
优点:
解耦建造过程和产品表示: 客户端不需要知道构造细节,只需要通过指挥者控制构建过程。
代码清晰: 将复杂对象的创建过程一步一步实现,使得代码易于维护和理解。
更好的控制: 允许逐步创建产品,使得每个部分的构建步骤可以独立变化。
缺点:
产品类型过多: 如果有很多不同的产品类型,可能会导致建造者类的数量过多,增加系统复杂性。
难以支持变化: 如果产品的构建步骤发生变化,所有具体建造者都需要修改,难以适应变化。
建造者模式非常适合在创建复杂对象时使用,尤其是在构建步骤明确且需要控制构建过程的情况下。
原型模式允许一个对象通过复制自身来创建新的对象。这种模式提供了一种创建对象的快捷方式,尤其适用于创建代价较高的对象。
原型模式的结构包含以下角色:
Prototype
接口并能够克隆自身。Prototype
对象复制自身来创建新对象。UML 类图
+----------------------+ +-------------------------+
| Prototype |<---------------| ConcretePrototype |
+----------------------+ +-------------------------+
| + Clone(): Prototype | | + Clone(): Prototype |
+----------------------+ +-------------------------+
/|\ |
| |
| |
+---------------------+ +---------------------------------+
| Client | | AnotherConcretePrototype |
+---------------------+ +---------------------------------+
| + Operation() | | + Clone(): Prototype |
+---------------------+ +---------------------------------+
原型模式的核心在于实现对象的深拷贝或浅拷贝。以下是一个实现原型模式的简单示例,其中我们克隆一个对象来创建新的对象。
原型接口
// 原型接口
public abstract class Prototype
{
public abstract Prototype Clone();
}
具体原型类
// 具体原型类
public class ConcretePrototype : Prototype
{
public string Name { get; set; }
public ConcretePrototype(string name)
{
Name = name;
}
// 实现克隆方法
public override Prototype Clone()
{
return (Prototype)this.MemberwiseClone(); // 浅拷贝
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建一个原型对象
ConcretePrototype prototype1 = new ConcretePrototype("Prototype 1");
// 克隆原型对象
ConcretePrototype clonedPrototype = (ConcretePrototype)prototype1.Clone();
// 显示克隆对象的属性
Console.WriteLine("Original Prototype Name: " + prototype1.Name);
Console.WriteLine("Cloned Prototype Name: " + clonedPrototype.Name);
// 修改克隆对象的属性
clonedPrototype.Name = "Cloned Prototype 1";
Console.WriteLine("\nAfter modification:");
Console.WriteLine("Original Prototype Name: " + prototype1.Name);
Console.WriteLine("Cloned Prototype Name: " + clonedPrototype.Name);
}
}
在这个例子中,ConcretePrototype
是具体的原型类,实现了 Clone
方法。Clone
方法使用 MemberwiseClone
来执行浅拷贝,这意味着对象的成员变量会被复制,但对象的引用类型成员变量依然指向同一个内存地址。
浅拷贝与深拷贝:
深拷贝的实现
要实现深拷贝,可以在 Clone
方法中手动复制引用类型的成员变量,或者通过序列化和反序列化来实现。
// 实现深拷贝的具体原型类
[Serializable]
public class DeepConcretePrototype : Prototype
{
public string Name { get; set; }
public List<string> Features { get; set; }
public DeepConcretePrototype(string name, List<string> features)
{
Name = name;
Features = features;
}
public override Prototype Clone()
{
// 深拷贝:通过序列化和反序列化
using (MemoryStream stream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
return (Prototype)formatter.Deserialize(stream);
}
}
}
优点:
减少对象创建的成本: 尤其是当对象的创建代价很高时,通过克隆一个现有的对象可以减少资源消耗。
避免复杂对象的重复创建: 当创建复杂对象需要设置大量参数或步骤时,原型模式可以避免重复工作。
易于扩展: 可以通过继承现有原型类来扩展新的克隆行为,而无需修改现有代码。
缺点:
深拷贝实现复杂: 如果对象包含复杂的引用关系,深拷贝的实现可能会很复杂。
容易引起混淆: 如果在系统中有多个原型对象,可能会导致代码的可读性降低,增加理解难度。
原型模式非常适合在对象创建复杂或成本较高的场景下使用,它通过克隆来简化对象的创建过程并提高性能。
适配器模式将一个类的接口转换成客户希望的另一个接口,适配器使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式主要包含以下角色:
Target
接口并包装一个 Adaptee
对象,从而将 Adaptee
的接口转换为 Target
的接口。Target
接口与适配器交互。UML 类图
+-------------------+ +-------------------+
| Client | | Target |
+-------------------+ +-------------------+
| - Request() |<-------| + Request() |
+-------------------+ +-------------------+
^
|
+-----------------------+
| Adapter |
+-----------------------+
| + Request() |
| - adaptee: Adaptee |
+-----------------------+
|
v
+-----------------------+
| Adaptee |
+-----------------------+
| + SpecificRequest() |
+-----------------------+
以下是一个实现适配器模式的简单示例。在这个例子中,Adaptee
类有一个接口 SpecificRequest
,它与 Target
接口 Request
不兼容。通过适配器模式,我们可以创建一个适配器类,将 Adaptee
的接口转换为 Target
的接口,使得客户端可以通过 Target
接口与 Adaptee
交互。
目标接口
// 目标接口
public interface ITarget
{
void Request();
}
适配者类
// 需要适配的类
public class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("Called SpecificRequest()");
}
}
适配器类
// 适配器类
public class Adapter : ITarget
{
private readonly Adaptee _adaptee;
public Adapter(Adaptee adaptee)
{
_adaptee = adaptee;
}
public void Request()
{
// 调用适配者的接口,将其转换为目标接口
_adaptee.SpecificRequest();
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 客户端需要通过 ITarget 接口调用方法
ITarget target = new Adapter(new Adaptee());
// 客户端通过目标接口与适配器交互
target.Request();
}
}
优点:
提高了类的复用性: 通过适配器模式,原本无法复用的类现在可以在新的环境中使用。
提高了类的灵活性: 通过使用适配器,可以轻松引入新接口,改变现有类的行为,而不需要修改现有代码。
符合开闭原则: 新增适配器类不会影响现有代码的功能,符合开闭原则。
缺点:
复杂性增加: 引入适配器模式可能会增加系统的复杂性,特别是在适配多个接口时,需要创建多个适配器类。
性能开销: 如果适配器做了大量的转换工作,可能会带来一定的性能开销。
适配器模式非常适合在项目中需要集成现有系统或类库,而它们的接口又与当前需求不兼容时使用。通过适配器模式,可以在不修改现有代码的前提下实现接口的兼容和扩展。
桥接模式允许在运行时将抽象类与其实现类解耦。它通过引入一个接口来使得抽象类与具体的实现类分开,从而支持两者的独立变化。这样可以避免复杂的多层继承结构。
桥接模式的结构包含以下角色:
UML 类图
+-------------------+ +-------------------+
| Abstraction | | Implementor |
+-------------------+ +-------------------+
| - implementor | | + OperationImpl() |
| + Operation() | +-------------------+
+-------------------+ ^
| |
| |
+-------------------+ +---------------------+
| RefinedAbstraction| | ConcreteImplementor |
+-------------------+ +---------------------+
| + Operation() | | + OperationImpl() |
+-------------------+ +---------------------+
以下是一个实现桥接模式的简单示例。假设我们要创建一个图形绘制程序,其中有不同类型的图形和不同的绘制方式。
实现类接口
// 实现者接口
public interface IColor
{
void ApplyColor();
}
具体实现类
// 具体实现者1:红色
public class RedColor : IColor
{
public void ApplyColor()
{
Console.WriteLine("Applying red color.");
}
}
// 具体实现者2:绿色
public class GreenColor : IColor
{
public void ApplyColor()
{
Console.WriteLine("Applying green color.");
}
}
抽象类
// 抽象类:形状
public abstract class Shape
{
protected IColor color;
protected Shape(IColor color)
{
this.color = color;
}
public abstract void Draw();
}
扩展抽象类
// 细化抽象类1:圆形
public class Circle : Shape
{
public Circle(IColor color) : base(color)
{
}
public override void Draw()
{
Console.Write("Circle is being drawn. ");
color.ApplyColor();
}
}
// 细化抽象类2:矩形
public class Rectangle : Shape
{
public Rectangle(IColor color) : base(color)
{
}
public override void Draw()
{
Console.Write("Rectangle is being drawn. ");
color.ApplyColor();
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建红色和绿色的实现者
IColor red = new RedColor();
IColor green = new GreenColor();
// 创建带有不同颜色的形状
Shape redCircle = new Circle(red);
Shape greenRectangle = new Rectangle(green);
// 画形状
redCircle.Draw(); // 输出: Circle is being drawn. Applying red color.
greenRectangle.Draw(); // 输出: Rectangle is being drawn. Applying green color.
}
}
在这个例子中:
IColor
是实现者接口,定义了 ApplyColor()
方法。RedColor
和 GreenColor
是具体实现者,实现了 IColor
接口。Shape
是抽象类,它维护了一个 IColor
类型的引用,并定义了 Draw()
方法。Circle
和 Rectangle
是 Shape
的细化抽象类,分别实现了 Draw()
方法。优点:
解耦抽象与实现: 通过桥接模式,抽象与实现可以独立变化,降低了系统的耦合度。
灵活性: 可以很方便地扩展新的抽象和实现类,而不需要修改已有的代码。
符合开闭原则: 新的实现可以在不修改原有代码的情况下添加。
缺点:
增加了系统的复杂性: 由于引入了多个类和接口,可能会使得系统结构变得复杂。
维护难度增加: 当扩展的类和接口数量增多时,维护这些类和接口的难度可能会增加。
桥接模式通过将抽象与实现分离,使得系统的扩展更加灵活,适用于需要同时支持多种变化的场景。
组合模式通过定义树形结构来组成对象,使得客户端对单个对象和对象集合的处理方式一致。这个模式让你可以使用相同的接口来操作单一对象和对象集合,简化了代码和操作逻辑。
组合模式的结构包含以下角色:
Component
接口,但没有子节点。Component
接口并能够管理其子节点。UML 类图
+---------------------+
| Component |
+---------------------+
| + Operation() |
+---------------------+
| + Add(Component) |
| + Remove(Component) |
| + GetChild(int) |
+---------------------+
^
|
+---------------------+ +---------------------+
| Leaf | | Composite |
+---------------------+ +---------------------+
| + Operation() | | + Operation() |
+---------------------+ +---------------------+
| + Add(Component) |
| + Remove(Component) |
| + GetChild(int) |
+---------------------+
以下是一个实现组合模式的简单示例。在这个示例中,我们有一个文件系统,其中 File
是叶子节点,Directory
是复合节点(目录),它可以包含多个文件或目录。
组件接口
// 组件接口
public abstract class FileSystemComponent
{
public abstract void Display(int depth);
// 组合节点的方法
public virtual void Add(FileSystemComponent component) { }
public virtual void Remove(FileSystemComponent component) { }
public virtual FileSystemComponent GetChild(int index) { return null; }
}
叶子节点类
// 叶子节点类:文件
public class File : FileSystemComponent
{
private string _name;
public File(string name)
{
_name = name;
}
public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + _name);
}
}
复合节点类
// 复合节点类:目录
public class Directory : FileSystemComponent
{
private List<FileSystemComponent> _children = new List<FileSystemComponent>();
private string _name;
public Directory(string name)
{
_name = name;
}
public override void Add(FileSystemComponent component)
{
_children.Add(component);
}
public override void Remove(FileSystemComponent component)
{
_children.Remove(component);
}
public override FileSystemComponent GetChild(int index)
{
return _children[index];
}
public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + _name);
foreach (var child in _children)
{
child.Display(depth + 2);
}
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建文件和目录
FileSystemComponent file1 = new File("File 1");
FileSystemComponent file2 = new File("File 2");
FileSystemComponent file3 = new File("File 3");
Directory directory1 = new Directory("Directory 1");
Directory directory2 = new Directory("Directory 2");
// 构建目录树
directory1.Add(file1);
directory1.Add(file2);
directory2.Add(file3);
directory2.Add(directory1);
// 显示目录树
directory2.Display(1);
}
}
在这个示例中:
FileSystemComponent
是组件接口,定义了叶子节点和复合节点的共同接口。File
是叶子节点,表示文件,没有子节点。Directory
是复合节点,表示目录,可以包含多个子节点(文件或其他目录)。优点:
简化客户端代码: 客户端代码可以统一处理叶子节点和复合节点,减少了代码复杂度。
增加灵活性: 通过将叶子节点和复合节点统一成一个接口,可以灵活地构建和管理复杂的树形结构。
符合开闭原则: 可以通过添加新的叶子节点或复合节点来扩展功能,而无需修改现有代码。
缺点:
设计复杂: 组合模式可能会增加系统的复杂性,因为你需要设计和管理树形结构。
性能问题: 如果树形结构非常庞大,操作树形结构可能会影响性能。
组合模式非常适合用来构建复杂的树形结构,通过将对象和对象集合统一成一个接口,它能够简化对复杂结构的操作,并提高系统的灵活性和可扩展性。
装饰器模式通过创建一个装饰器类,它实现了与被装饰对象相同的接口,从而可以在运行时动态地添加新功能。这种模式提供了一种灵活的替代方案来扩展对象的功能,避免了使用子类来扩展功能的需要。
装饰器模式的结构包含以下角色:
Component
接口的具体对象,表示要装饰的对象。Component
对象的引用,定义与 Component
接口相同的接口,并可以在运行时扩展功能。Decorator
,实现具体的附加功能。UML 类图
+-------------------+
| Component |
+-------------------+
| + Operation() |
+-------------------+
^
|
+-------------------+ +---------------------------+
| ConcreteComponent | <------- | Decorator |
+-------------------+ +---------------------------+
| + Operation() | | - component: Component |
+-------------------+ | + Operation() |
+---------------------------+
^
|
+------------------------+
| ConcreteDecorator |
+------------------------+
| + Operation() |
| + AdditionalBehavior() |
+------------------------+
以下是一个实现装饰器模式的简单示例。在这个示例中,我们有一个饮料的系统,可以为饮料添加不同的配料(装饰)。
组件接口
// 组件接口
public abstract class Beverage
{
public abstract string GetDescription();
public abstract double Cost();
}
具体组件类
// 具体组件:咖啡
public class Coffee : Beverage
{
public override string GetDescription()
{
return "Coffee";
}
public override double Cost()
{
return 2.00; // 基本咖啡的价格
}
}
装饰器类
// 装饰器类
public abstract class CondimentDecorator : Beverage
{
protected Beverage _beverage;
public CondimentDecorator(Beverage beverage)
{
_beverage = beverage;
}
}
具体装饰器类
// 具体装饰器:牛奶
public class MilkDecorator : CondimentDecorator
{
public MilkDecorator(Beverage beverage) : base(beverage)
{
}
public override string GetDescription()
{
return _beverage.GetDescription() + ", Milk";
}
public override double Cost()
{
return _beverage.Cost() + 0.50; // 添加牛奶的价格
}
}
// 具体装饰器:巧克力
public class ChocolateDecorator : CondimentDecorator
{
public ChocolateDecorator(Beverage beverage) : base(beverage)
{
}
public override string GetDescription()
{
return _beverage.GetDescription() + ", Chocolate";
}
public override double Cost()
{
return _beverage.Cost() + 0.70; // 添加巧克力的价格
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建基本的咖啡
Beverage beverage = new Coffee();
// 添加配料
beverage = new MilkDecorator(beverage);
beverage = new ChocolateDecorator(beverage);
// 显示描述和总费用
Console.WriteLine($"{beverage.GetDescription()} costs {beverage.Cost():C}");
}
}
在这个示例中:
Beverage
是组件接口,定义了饮料的基本功能。Coffee
是具体组件,表示基础饮料(咖啡)。CondimentDecorator
是装饰器类,它包含了对 Beverage
对象的引用,并实现了 Beverage
接口。MilkDecorator
和 ChocolateDecorator
是具体装饰器,分别添加牛奶和巧克力的功能。优点:
灵活性: 可以动态添加和扩展对象的功能,而不需要创建大量的子类。
符合开闭原则: 可以通过添加新的装饰器类来扩展功能,而无需修改已有的类。
避免类的膨胀: 通过组合多个装饰器,可以避免创建过多的子类,从而减少类的数量。
缺点:
复杂性: 装饰器模式可能会增加系统的复杂性,尤其是当装饰器层次过多时。
调试困难: 由于装饰器的嵌套,调试代码时可能会变得复杂。
装饰器模式提供了一种灵活的方式来扩展对象的功能,使得开发者可以在不影响已有代码的情况下,轻松地添加新的行为或属性。
外观模式通过为复杂的子系统提供一个简化的接口,使得客户端可以通过这个接口访问子系统的功能,而不需要直接与子系统的多个组件进行交互。外观模式可以隐藏子系统的复杂性,并且使得子系统与客户端的耦合度降低。
外观模式的结构包含以下角色:
UML 类图
+-------------------+
| Client |
+-------------------+
^
|
+-------------------+ +-------------------+ +-------------------+
| SubsystemA | ----> | Facade | <---- | SubsystemC |
+-------------------+ +-------------------+ +-------------------+
^ ^
| |
+-------------------+ +-------------------+
| SubsystemB | | SubsystemD |
+-------------------+ +-------------------+
以下是一个实现外观模式的简单示例。在这个示例中,我们构建了一个家庭影院系统,客户端通过外观类 HomeTheaterFacade
来控制各个子系统,如电视、音响、灯光等。
子系统类
// 子系统类:电视
public class Television
{
public void On()
{
Console.WriteLine("Television is on.");
}
public void Off()
{
Console.WriteLine("Television is off.");
}
}
// 子系统类:音响
public class SoundSystem
{
public void On()
{
Console.WriteLine("Sound system is on.");
}
public void Off()
{
Console.WriteLine("Sound system is off.");
}
public void SetVolume(int volume)
{
Console.WriteLine($"Sound system volume set to {volume}.");
}
}
// 子系统类:灯光
public class Lights
{
public void Dim(int level)
{
Console.WriteLine($"Lights dimmed to {level}%.");
}
public void On()
{
Console.WriteLine("Lights are on.");
}
}
外观类
// 外观类:家庭影院
public class HomeTheaterFacade
{
private readonly Television _television;
private readonly SoundSystem _soundSystem;
private readonly Lights _lights;
public HomeTheaterFacade(Television television, SoundSystem soundSystem, Lights lights)
{
_television = television;
_soundSystem = soundSystem;
_lights = lights;
}
public void WatchMovie()
{
Console.WriteLine("Get ready to watch a movie...");
_lights.Dim(30);
_television.On();
_soundSystem.On();
_soundSystem.SetVolume(5);
}
public void EndMovie()
{
Console.WriteLine("Shutting movie theater down...");
_television.Off();
_soundSystem.Off();
_lights.On();
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建子系统对象
Television television = new Television();
SoundSystem soundSystem = new SoundSystem();
Lights lights = new Lights();
// 创建外观对象
HomeTheaterFacade homeTheater = new HomeTheaterFacade(television, soundSystem, lights);
// 使用外观模式控制子系统
homeTheater.WatchMovie();
Console.WriteLine("\nMovie is running...\n");
homeTheater.EndMovie();
}
}
在这个例子中:
Television
、SoundSystem
、Lights
是子系统类,提供了各自的功能。HomeTheaterFacade
是外观类,它将各个子系统的操作封装成了 WatchMovie()
和 EndMovie()
两个简单的方法,客户端只需调用这些方法即可控制整个家庭影院系统。Program
通过 HomeTheaterFacade
类来控制整个家庭影院的各个设备,而不需要直接与每个设备交互。优点:
简化接口: 提供一个简化的接口来访问复杂的子系统,减少客户端与子系统之间的耦合。
隐藏子系统的复杂性: 客户端不需要了解子系统的内部结构,只需要与外观类交互即可。
减少依赖: 客户端与子系统之间的依赖关系减少,如果子系统发生变化,只需修改外观类,而不需要修改客户端代码。
缺点:
不符合开闭原则: 如果要扩展外观类的功能,可能需要修改外观类的代码,从而违反开闭原则。
潜在性能问题: 外观模式可能会因为封装了大量子系统的调用,而引入一定的性能开销。
外观模式通过提供一个统一和简化的接口来隐藏系统的复杂性,使得客户端能够更轻松地使用系统,同时保持系统内部结构的灵活性和可扩展性。
享元模式的核心思想是将对象的状态分为内部状态(可以共享的部分)和外部状态(不能共享的部分)。通过共享相同的内部状态,减少内存的重复占用,从而实现系统的资源优化。
享元模式的结构包含以下角色:
UML 类图
+-------------------+
| IFlyweight |
+-------------------+
| + Operation() |
+-------------------+
^
|
+-----------------------+
| ConcreteFlyweight |
+-----------------------+
| - property1 | // 共享的内部状态
| - property2 | // 共享的内部状态
| + Operation() |
+-----------------------+
+-------------------+
| FlyweightFactory |
+-------------------+
| - flyweights |
| + Operation() |
+-------------------+
+---------------------------+
| UnsharedConcreteFlyweight |
+---------------------------+
| - property1 | // 不共享的外部状态
| - property2 | // 不共享的外部状态
| - flyweight | // 组合享元
| + Operation() |
+---------------------------+
以下是一个实现享元模式的简单示例。在这个示例中,我们模拟了一个文字处理器,其中的字符对象可以被共享,以减少内存的占用。
享元接口
// 享元接口
public interface ICharacter
{
void Display(int fontSize);
}
具体享元类
// 具体享元类:字符
public class Character : ICharacter
{
private readonly char _symbol; // 内部状态(共享部分)
public Character(char symbol)
{
_symbol = symbol;
}
public void Display(int fontSize)
{
Console.WriteLine($"Character: {_symbol}, Font size: {fontSize}");
}
}
享元工厂类
// 享元工厂类
public class CharacterFactory
{
private readonly Dictionary<char, ICharacter> _characters = new Dictionary<char, ICharacter>();
public ICharacter GetCharacter(char symbol)
{
if (!_characters.ContainsKey(symbol))
{
_characters[symbol] = new Character(symbol); // 创建新的享元对象
}
return _characters[symbol]; // 返回共享的享元对象
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
CharacterFactory factory = new CharacterFactory();
// 获取并显示字符对象
ICharacter a = factory.GetCharacter('A');
a.Display(12);
ICharacter b = factory.GetCharacter('B');
b.Display(14);
ICharacter a2 = factory.GetCharacter('A');
a2.Display(18);
// 检查两个 'A' 字符是否为同一个实例
Console.WriteLine($"Is 'A' and 'A2' the same instance? {ReferenceEquals(a, a2)}");
}
}
在这个例子中:
ICharacter
是享元接口,定义了 Display()
方法,用于显示字符及其大小。Character
是具体享元类,存储了共享的字符符号 _symbol
,并在 Display()
方法中使用外部状态(字体大小)。CharacterFactory
是享元工厂类,负责创建和管理 Character
对象,并确保相同的字符符号只创建一个实例。享元接口
// 享元接口:棋子
public interface IChessPiece
{
void Display(int x, int y);
}
具体享元类
// 具体享元类:具体的棋子,如"黑车"或"白马"
public class ChessPiece : IChessPiece
{
private readonly string _color; // 内部状态(共享部分)
private readonly string _type; // 内部状态(共享部分)
public ChessPiece(string color, string type)
{
_color = color;
_type = type;
}
public void Display(int x, int y)
{
Console.WriteLine($"Chess Piece: {_color} {_type}, Position: ({x}, {y})");
}
}
享元工厂类
// 享元工厂类:负责管理棋子对象
public class ChessPieceFactory
{
private readonly Dictionary<string, IChessPiece> _pieces = new Dictionary<string, IChessPiece>();
public IChessPiece GetChessPiece(string color, string type)
{
string key = color + "_" + type;
if (!_pieces.ContainsKey(key))
{
_pieces[key] = new ChessPiece(color, type);
}
return _pieces[key];
}
}
非共享具体享元类
// 非共享具体享元类:棋盘上的棋子
public class ChessBoardPosition
{
private readonly int _x; // 外部状态
private readonly int _y; // 外部状态
private readonly IChessPiece _chessPiece; // 共享享元
public ChessBoardPosition(int x, int y, IChessPiece chessPiece)
{
_x = x;
_y = y;
_chessPiece = chessPiece;
}
public void Display()
{
_chessPiece.Display(_x, _y);
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
ChessPieceFactory factory = new ChessPieceFactory();
// 获取棋子,并在棋盘上设置位置
ChessBoardPosition position1 = new ChessBoardPosition(1, 1, factory.GetChessPiece("Black", "Rook"));
ChessBoardPosition position2 = new ChessBoardPosition(1, 2, factory.GetChessPiece("Black", "Knight"));
ChessBoardPosition position3 = new ChessBoardPosition(1, 3, factory.GetChessPiece("Black", "Bishop"));
ChessBoardPosition position4 = new ChessBoardPosition(1, 1, factory.GetChessPiece("White", "Pawn"));
// 显示棋盘上的棋子
position1.Display();
position2.Display();
position3.Display();
position4.Display();
}
}
在这个例子中:
IChessPiece
: 定义了显示棋子的方法 Display()
,要求提供棋子的位置信息。ChessPiece
: 实现了 IChessPiece
接口,包含了棋子的颜色和类型,这些是共享的内部状态。ChessPieceFactory
: 负责创建和管理享元对象(棋子),确保同种颜色和类型的棋子只创建一个实例。ChessBoardPosition
: 代表棋盘上的每一个棋子位置,包含棋子在棋盘上的位置坐标 _x
和 _y
,这些是非共享的外部状态。每个位置持有一个共享的棋子对象。优点:
减少内存占用: 通过共享对象,显著减少了系统中的内存消耗,特别适用于大量相似对象的场景。
提高性能: 减少了创建对象的开销,特别是在对象创建成本高昂的情况下。
缺点:
增加复杂性: 引入共享机制后,代码的复杂性增加,需要管理外部状态和内部状态。
非线程安全: 享元对象在多线程环境下可能会引发线程安全问题,需要谨慎处理。
享元模式通过共享对象的内部状态,有效地减少了内存的占用,是优化系统性能的一种有效手段,特别是在需要大量相似对象的情况下。
代理模式通过创建一个代理对象来控制对另一个对象的访问。代理对象具有与原对象相同的接口,客户端可以通过代理对象访问实际的服务对象。代理对象可以在不影响客户端的情况下,对请求进行预处理或后处理。
代理模式的结构包含以下角色:
UML 类图
+-----------------+
| Subject | <--------- 抽象主题
+-----------------+ |
| + Request() | |
+-----------------+ |
^ |
| |
+-----------------+ +-----------------+
| Proxy | | RealSubject |
+-----------------+ +-----------------+
| - realSubject: | ------- | + Request() |
| RealSubject | +-----------------+
| + Request() |
+-----------------+
假设我们有一个需要访问的图像文件对象。图像文件可能很大,所以我们希望在图像真正需要显示时才加载它。在这种情况下,我们可以使用代理模式。
抽象主题接口
// 抽象主题
public interface IImage
{
void Display();
}
真实主题类
// 真实主题类:真实的图像文件
public class RealImage : IImage
{
private readonly string _filename;
public RealImage(string filename)
{
_filename = filename;
LoadImageFromDisk(); // 模拟加载图像文件
}
private void LoadImageFromDisk()
{
Console.WriteLine($"Loading image from disk: {_filename}");
}
public void Display()
{
Console.WriteLine($"Displaying image: {_filename}");
}
}
代理类
// 代理类:代理图像
public class ProxyImage : IImage
{
private RealImage _realImage;
private readonly string _filename;
public ProxyImage(string filename)
{
_filename = filename;
}
public void Display()
{
if (_realImage == null)
{
_realImage = new RealImage(_filename); // 延迟加载
}
_realImage.Display();
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
IImage image = new ProxyImage("test_image.jpg");
// 图像第一次显示时,代理对象会加载真实图像
image.Display();
// 图像再次显示时,不需要再次加载
image.Display();
}
}
在这个例子中:
IImage
: 定义了 Display()
方法,所有图像对象都必须实现这个方法。RealImage
: 实现了 IImage
接口,并在构造函数中模拟加载图像文件。Display()
方法显示图像。ProxyImage
: 同样实现了 IImage
接口,持有一个 RealImage
对象的引用。只有在需要时,才会加载真实的图像文件,执行延迟加载。运行结果
运行上述代码,你会看到以下输出:
Loading image from disk: test_image.jpg
Displaying image: test_image.jpg
Displaying image: test_image.jpg
在这个例子中,图像在第一次调用 Display()
时才会加载,之后的调用直接显示图像,无需再次加载。这就是代理模式的延迟加载(Lazy Loading)应用。
优点:
控制对象访问: 可以在不影响客户端的情况下控制对目标对象的访问。
延迟加载: 通过代理可以实现对象的延迟初始化,节省资源。
权限控制: 可以通过代理实现对敏感对象的访问控制。
缺点:
增加复杂性: 增加了类的数量和系统的复杂性。
可能引入性能开销: 代理模式可能会引入额外的处理逻辑,导致性能开销。
代理模式在设计复杂系统时提供了非常灵活的对象管理方式,通过合理使用代理,可以有效地提升系统的性能和安全性。
责任链模式的核心思想是将请求沿着处理者链传递,直到其中一个处理者处理这个请求。这种模式的一个重要特性是:请求的发送者并不知道哪个对象会最终处理请求,系统中的处理者对象也无需知道其他处理者的结构,处理者之间的解耦提高了系统的灵活性。
责任链模式包含以下角色:
UML 类图
+-----------------------------------+
| Handler | <----- 抽象处理者
+-----------------------------------+
| - next: Handler | <----- 链中下一个处理者的引用
| + SetNext(handler: Handler) |
| + HandleRequest(request: Request) |
+-----------------------------------+
^
|
+-----------------------------------+ +-----------------------------------+
| ConcreteHandler1 | | ConcreteHandler2 |
+-----------------------------------+ +-----------------------------------+
| + HandleRequest(request: Request) | | + HandleRequest(request: Request) |
+-----------------------------------+ +-----------------------------------+
假设我们有一个支持多级审批的系统,每个级别的审批员处理不同级别的请求。请求可以从一个审批员传递到下一个审批员,直到某个审批员处理了请求或者请求被拒绝。
抽象处理者
// 抽象处理者
public abstract class Approver
{
protected Approver _nextApprover;
public void SetNext(Approver nextApprover)
{
_nextApprover = nextApprover;
}
public abstract void ProcessRequest(PurchaseRequest request);
}
具体处理者
// 具体处理者1:经理
public class Manager : Approver
{
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 10000)
{
Console.WriteLine($"{this.GetType().Name} approved request# {request.Number}");
}
else if (_nextApprover != null)
{
_nextApprover.ProcessRequest(request);
}
}
}
// 具体处理者2:总监
public class Director : Approver
{
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 25000)
{
Console.WriteLine($"{this.GetType().Name} approved request# {request.Number}");
}
else if (_nextApprover != null)
{
_nextApprover.ProcessRequest(request);
}
}
}
// 具体处理者3:副总裁
public class VicePresident : Approver
{
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 50000)
{
Console.WriteLine($"{this.GetType().Name} approved request# {request.Number}");
}
else if (_nextApprover != null)
{
_nextApprover.ProcessRequest(request);
}
}
}
// 具体处理者4:总裁
public class President : Approver
{
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 100000)
{
Console.WriteLine($"{this.GetType().Name} approved request# {request.Number}");
}
else
{
Console.WriteLine($"Request# {request.Number} requires an executive meeting!");
}
}
}
请求类
// 请求类:采购请求
public class PurchaseRequest
{
public int Number { get; }
public double Amount { get; }
public string Purpose { get; }
public PurchaseRequest(int number, double amount, string purpose)
{
Number = number;
Amount = amount;
Purpose = purpose;
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建处理者对象
Approver manager = new Manager();
Approver director = new Director();
Approver vp = new VicePresident();
Approver president = new President();
// 设置责任链
manager.SetNext(director);
director.SetNext(vp);
vp.SetNext(president);
// 创建多个采购请求
PurchaseRequest request1 = new PurchaseRequest(1, 5000, "Buy supplies");
PurchaseRequest request2 = new PurchaseRequest(2, 20000, "Buy projectors");
PurchaseRequest request3 = new PurchaseRequest(3, 35000, "Buy laptops");
PurchaseRequest request4 = new PurchaseRequest(4, 90000, "Buy servers");
// 处理请求
manager.ProcessRequest(request1);
manager.ProcessRequest(request2);
manager.ProcessRequest(request3);
manager.ProcessRequest(request4);
}
}
运行结果
Manager approved request# 1
Director approved request# 2
VicePresident approved request# 3
President approved request# 4
在这个例子中,Manager
、Director
、VicePresident
和 President
这四个处理者构成了一个责任链。每个处理者都检查请求的金额,并决定是否可以处理请求,如果不能处理则将请求传递给下一个处理者。
优点:
降低耦合度: 客户端不需要知道哪个处理者会最终处理请求,这使得系统更灵活、可扩展。
增加灵活性: 可以动态地添加或移除处理者,甚至可以动态调整处理者链的顺序。
职责分离: 每个处理者只关注自己能够处理的那部分职责,符合单一职责原则。
缺点:
不保证请求被处理: 如果链上的处理者都不能处理请求,那么请求可能会被丢弃。
可能影响性能: 如果责任链过长,或者某些处理者链路中的节点过多,可能导致请求处理的延迟。
责任链模式通过将处理者对象串联成一条链,使请求能够沿着链传递,直到被某个处理者处理。这种模式的灵活性使其适用于多种场景,尤其是那些需要动态指定请求处理者或处理者职责可能发生变化的场合。
命令模式的核心思想是将“请求”封装为对象,并将请求的执行和请求的具体操作细节解耦。这样可以在不同的时间点、不同的环境下执行请求,还可以通过命令对象的统一接口来记录日志、撤销操作等。
命令模式包含以下角色:
UML 类图
+-------------------+
| Command | <----- 命令接口
+-------------------+
| + Execute() |
+-------------------+
^
|
+-----------------------+ +----------------------+
| ConcreteCommand1 | | ConcreteCommand2 |
+-----------------------+ +----------------------+
| - receiver: Receiver | | - receiver: Receiver |
| + Execute() | | + Execute() |
+-----------------------+ +----------------------+
+-------------------+
| Receiver | <----- 接收者
+-------------------+
| + Action() |
+-------------------+
+-----------------------------+
| Invoker | <----- 调用者
+-----------------------------+
| - command: Command |
| + SetCommand(cmd: Command) |
| + ExecuteCommand() |
+-----------------------------+
假设我们要实现一个简单的家电控制系统,可以用命令模式来设计将家电的操作(如开灯、关灯等)封装为命令对象。
命令接口
// 命令接口
public interface ICommand
{
void Execute();
void Undo();
}
接收者
// 接收者:灯
public class Light
{
public void TurnOn()
{
Console.WriteLine("The light is on");
}
public void TurnOff()
{
Console.WriteLine("The light is off");
}
}
具体命令类
// 具体命令类:打开灯
public class LightOnCommand : ICommand
{
private readonly Light _light;
public LightOnCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOn();
}
public void Undo()
{
_light.TurnOff(); // 撤销打开灯,实际上是关闭灯
}
}
// 具体命令类:关闭灯
public class LightOffCommand : ICommand
{
private readonly Light _light;
public LightOffCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOff();
}
public void Undo()
{
_light.TurnOn(); // 撤销关闭灯,实际上是打开灯
}
}
调用者
public class RemoteControl
{
private ICommand _command;
private readonly Stack<ICommand> _history = new Stack<ICommand>();
private readonly Stack<ICommand> _redoStack = new Stack<ICommand>();
public void SetCommand(ICommand command)
{
_command = command;
}
public void PressButton()
{
_command.Execute();
_history.Push(_command); // 保存执行的命令
_redoStack.Clear(); // 清空恢复栈,因为有新操作了
}
public void PressUndo()
{
if (_history.Count > 0)
{
ICommand lastCommand = _history.Pop();
lastCommand.Undo();
_redoStack.Push(lastCommand); // 保存到恢复栈
}
}
public void PressRedo()
{
if (_redoStack.Count > 0)
{
ICommand lastUndoneCommand = _redoStack.Pop();
lastUndoneCommand.Execute();
_history.Push(lastUndoneCommand); // 重新保存到历史栈
}
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建接收者
Light livingRoomLight = new Light();
// 创建命令对象
ICommand lightOn = new LightOnCommand(livingRoomLight);
ICommand lightOff = new LightOffCommand(livingRoomLight);
// 创建调用者并设置命令
RemoteControl remote = new RemoteControl();
// 打开灯
remote.SetCommand(lightOn);
remote.PressButton();
// 关闭灯
remote.SetCommand(lightOff);
remote.PressButton();
// 撤销关闭灯操作(即再次打开灯)
remote.PressUndo();
// 撤销打开灯操作(即再次关闭灯)
remote.PressUndo();
// 恢复关闭灯操作(即再次打开灯)
remote.PressRedo();
}
}
运行结果
The light is on
The light is off
在这个例子中,LightOnCommand
和 LightOffCommand
这两个具体命令类封装了开灯和关灯的操作。RemoteControl
是调用者,通过调用 SetCommand
方法将具体命令对象传递给它,并通过 PressButton
方法执行命令。
优点:
解耦请求发送者和接收者: 发送者只需要知道命令接口,而不需要了解具体实现。
支持撤销操作: 可以实现命令的撤销和恢复功能。
支持宏命令: 可以将多个命令组合成一个宏命令,顺序执行。
支持请求排队和日志记录: 通过保存命令对象,可以将请求保存到队列中或日志中,便于后续操作。
缺点:
类数量增加: 每个具体命令都需要定义一个类,可能导致类的数量增加。
系统复杂性增加: 封装请求为对象虽然增加了灵活性,但也增加了系统的复杂性。
命令模式通过将操作封装为独立的命令对象,实现了请求发送者与接收者的解耦。它为系统增加了灵活性,尤其是在支持撤销、恢复、宏命令和请求排队等功能时非常有用。然而,命令模式的使用也可能导致类的数量增加,系统的复杂性增加,因此在设计时需要权衡使用。
解释器模式通过定义一种语言的语法规则,使用这些规则解析和执行语言中的语句。这种模式通常用于那些具有简单语法和小型命令集的领域特定语言(DSL)中。
解释器模式包含以下角色:
UML 类图
+---------------------------------------+
| AbstractExpression | <----- 抽象表达式
+---------------------------------------+
| + Interpret(context: Context): void |
+---------------------------------------+
^
|
+---------------------------------------+ +---------------------------------------+
| TerminalExpression | | NonTerminalExpression |
+---------------------------------------+ +---------------------------------------+
| + Interpret(context: Context): void | | + Interpret(context: Context): void |
+---------------------------------------+ +---------------------------------------+
+-------------------+
| Context | <----- 上下文
+-------------------+
| + GetInfo(): ... |
+-------------------+
假设我们要实现一个简单的数学表达式解释器,可以解析和计算简单的加法和减法运算表达式。
抽象表达式
// 抽象表达式
public interface IExpression
{
int Interpret();
}
终结符表达式
// 终结符表达式:数字
public class NumberExpression : IExpression
{
private readonly int _number;
public NumberExpression(int number)
{
_number = number;
}
public int Interpret()
{
return _number;
}
}
非终结符表达式
// 非终结符表达式:加法
public class AddExpression : IExpression
{
private readonly IExpression _leftExpression;
private readonly IExpression _rightExpression;
public AddExpression(IExpression leftExpression, IExpression rightExpression)
{
_leftExpression = leftExpression;
_rightExpression = rightExpression;
}
public int Interpret()
{
return _leftExpression.Interpret() + _rightExpression.Interpret();
}
}
// 非终结符表达式:减法
public class SubtractExpression : IExpression
{
private readonly IExpression _leftExpression;
private readonly IExpression _rightExpression;
public SubtractExpression(IExpression leftExpression, IExpression rightExpression)
{
_leftExpression = leftExpression;
_rightExpression = rightExpression;
}
public int Interpret()
{
return _leftExpression.Interpret() - _rightExpression.Interpret();
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 构造表达式:10 + 5 - 2
IExpression expression = new SubtractExpression(
new AddExpression(new NumberExpression(10), new NumberExpression(5)),
new NumberExpression(2)
);
// 解释并计算结果
int result = expression.Interpret();
Console.WriteLine($"Result: {result}");
}
}
运行结果
Result: 13
在这个例子中,表达式 10 + 5 - 2
被构造为一个解释器树,并通过调用 Interpret
方法递归地计算出结果。NumberExpression
是终结符表达式,用于表示具体的数字值,AddExpression
和 SubtractExpression
是非终结符表达式,用于表示加法和减法操作。
优点:
灵活性高: 解释器模式使得设计自定义语言变得更加容易,通过组合不同的表达式类可以实现复杂的语法解析。
可扩展性好: 新的语法规则可以通过添加新的表达式类来实现,而不需要修改现有的系统。
缺点:
性能问题: 解释器模式适用于语法规则相对简单的场景。对于复杂的语法解析,由于要递归解析表达式树,可能会导致性能问题。
类的数量增加: 每个语法规则都需要一个类来实现,可能导致类的数量急剧增加,增加了系统的复杂性。
解释器模式通过定义语言的语法规则,并使用这些规则解析和执行语句。它适用于简单的语法规则和小型语言解析任务,但不适用于复杂的语法解析和大规模系统。解释器模式的灵活性和扩展性使其在某些领域特定语言的实现中非常有用。
迭代器模式的核心思想是提供一种统一的接口来遍历聚合对象中的元素,而不需要了解聚合对象的内部结构。通过这种方式,集合和遍历算法之间解耦,遍历的方式可以更容易地改变或扩展。
迭代器模式包含以下角色:
Next
、HasNext
、Current
等方法。UML 类图
+-------------------------------+ +-------------------+
| Aggregate | | Iterator |
+-------------------------------+ +-------------------+
| + CreateIterator(): Iterator | | + HasNext(): bool |
+-------------------------------+ | + Next(): T |
^ | + Current(): T |
| +-------------------+
+-------------------------------+
|ConcreteAggregate | +-------------------+
+-------------------------------+ | ConcreteIterator |
| + CreateIterator(): Iterator | | + HasNext(): bool |
+-------------------------------+ | + Next(): T |
| + Current(): T |
+-------------------+
假设我们要实现一个自定义的 List
集合,并为它提供一个迭代器来遍历其中的元素。
迭代器接口
// 迭代器接口
public interface IIterator<T>
{
bool HasNext();
T Next();
T Current { get; }
}
具体迭代器
// 具体迭代器
public class ListIterator<T> : IIterator<T>
{
private readonly List<T> _list;
private int _position = 0;
public ListIterator(List<T> list)
{
_list = list;
}
public bool HasNext()
{
return _position < _list.Count;
}
public T Next()
{
return _list[_position++];
}
public T Current => _list[_position];
}
聚合接口
// 聚合接口
public interface IAggregate<T>
{
IIterator<T> CreateIterator();
}
具体聚合类
// 具体聚合类
public class CustomList<T> : IAggregate<T>
{
private readonly List<T> _items = new List<T>();
public void Add(T item)
{
_items.Add(item);
}
public IIterator<T> CreateIterator()
{
return new ListIterator<T>(_items);
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建聚合对象并添加元素
CustomList<string> list = new CustomList<string>();
list.Add("Item 1");
list.Add("Item 2");
list.Add("Item 3");
// 创建迭代器并遍历元素
IIterator<string> iterator = list.CreateIterator();
while (iterator.HasNext())
{
string item = iterator.Next();
Console.WriteLine(item);
}
}
}
运行结果
Item 1
Item 2
Item 3
在这个例子中,我们创建了一个自定义的 CustomList
类,并为其提供了 ListIterator
作为具体的迭代器。ListIterator
实现了遍历列表元素的逻辑。客户端代码通过迭代器接口来遍历 CustomList
中的元素,而无需了解 CustomList
的内部结构。
优点:
简化聚合类: 迭代器模式将遍历的职责从聚合类中分离出来,简化了聚合类的实现。
一致的接口: 迭代器模式提供了一致的接口用于遍历不同类型的聚合对象,无需关心其内部实现。
灵活性高: 可以自由更改迭代算法而不影响聚合类。
缺点:
迭代器模式提供了一种遍历聚合对象的标准方法,通过解耦遍历逻辑和聚合对象的实现,增强了系统的灵活性和可扩展性。该模式特别适合需要在不暴露对象内部结构的情况下对对象进行遍历的场景。
中介者模式将系统中多个对象之间复杂的交互和依赖关系抽象为一个中介者对象,各个对象不直接引用彼此,而是通过中介者进行通信。这样做可以减少对象之间的直接依赖,从而使系统更加易于维护和扩展。
中介者模式包含以下角色:
UML 类图
+---------------------------------------------------+
| Mediator |
+---------------------------------------------------+
| + Notify(sender: Colleague, event: string): void |
+---------------------------------------------------+
^
|
+---------------------------------------------------+
|ConcreteMediator |
+---------------------------------------------------+
| + Notify(sender: Colleague, event: string): void |
| + RegisterColleague(colleague: Colleague): void |
+---------------------------------------------------+
+-------------------------------------------+
| Colleague |
+-------------------------------------------+
| + SetMediator(mediator: Mediator): void |
| + Send(event: string): void |
+-------------------------------------------+
^ ^
| |
+-------------------------------+ +-------------------------------+
| ColleagueA | | ColleagueB |
+-------------------------------+ +-------------------------------+
| + Send(event: string): void | | + Send(event: string): void |
+-------------------------------+ +-------------------------------+
假设我们要实现一个聊天室系统,其中用户可以通过聊天室中介者互相发送消息。
中介者接口
// 中介者接口
public interface IChatMediator
{
void SendMessage(string message, User user);
void RegisterUser(User user);
}
具体中介者
// 具体中介者
public class ChatMediator : IChatMediator
{
private readonly List<User> _users = new List<User>();
public void RegisterUser(User user)
{
_users.Add(user);
}
public void SendMessage(string message, User sender)
{
foreach (var user in _users)
{
// 不要发给自己
if (user != sender)
{
user.Receive(message);
}
}
}
}
同事类
// 同事类
public abstract class User
{
protected IChatMediator _mediator;
protected string _name;
public User(IChatMediator mediator, string name)
{
_mediator = mediator;
_name = name;
}
public abstract void Send(string message);
public abstract void Receive(string message);
}
// 具体同事类
public class ConcreteUser : User
{
public ConcreteUser(IChatMediator mediator, string name) : base(mediator, name)
{
}
public override void Send(string message)
{
Console.WriteLine($"{_name} sends: {message}");
_mediator.SendMessage(message, this);
}
public override void Receive(string message)
{
Console.WriteLine($"{_name} receives: {message}");
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
IChatMediator chatMediator = new ChatMediator();
User user1 = new ConcreteUser(chatMediator, "User1");
User user2 = new ConcreteUser(chatMediator, "User2");
User user3 = new ConcreteUser(chatMediator, "User3");
chatMediator.RegisterUser(user1);
chatMediator.RegisterUser(user2);
chatMediator.RegisterUser(user3);
user1.Send("Hello, everyone!");
}
}
运行结果
User1 sends: Hello, everyone!
User2 receives: Hello, everyone!
User3 receives: Hello, everyone!
在这个例子中,ChatMediator
是中介者,负责协调 User
对象之间的通信。用户通过调用中介者的 SendMessage
方法来发送消息,中介者会将消息转发给其他用户。
优点:
降低耦合度: 中介者模式减少了对象之间的直接依赖,各个对象不需要知道其他对象的存在,只需要与中介者交互。
增强可维护性: 对象之间的交互逻辑集中在中介者中,使得修改交互逻辑变得更加容易,而无需修改各个对象。
易于扩展: 可以通过增加新的中介者或同事类来扩展系统,而不会影响现有代码。
缺点:
中介者复杂性: 随着系统的复杂度增加,中介者可能变得庞大且复杂,难以维护。
可能引入单点故障: 中介者作为通信的中心,如果中介者失败,整个系统的通信可能会中断。
中介者模式通过引入中介者对象来协调多个对象之间的交互,减少了对象之间的耦合度,并使系统更具可维护性和可扩展性。尽管中介者模式能够简化对象的交互逻辑,但需要注意中介者对象的复杂性管理,以避免其变得过于庞大。
备忘录模式的核心思想是将对象的状态保存在一个备忘录对象中,并允许在未来的某个时刻恢复该状态。备忘录模式保证了状态的封装性,外部对象无法直接访问备忘录中的内容,从而保护了原发器对象的内部细节。
备忘录模式包含以下角色:
UML 类图
+---------------------------------------+
| Originator |
+---------------------------------------+
| - state: String |
| + CreateMemento(): Memento |
| + SetMemento(memento: Memento): void |
+---------------------------------------+
|
|
+-----------------------+ +-----------------------+
| Memento | | Caretaker |
+-----------------------+ +-----------------------+
| - state: String | | - memento: Memento |
| + GetState(): String | +-----------------------+
+-----------------------+
假设我们要实现一个简单的文本编辑器,它能够保存文本的状态,并在需要时撤销或恢复状态。
原发器
// 原发器类
public class TextEditor
{
private string _text;
public void SetText(string text)
{
_text = text;
}
public string GetText()
{
return _text;
}
public Memento CreateMemento()
{
return new Memento(_text);
}
public void RestoreMemento(Memento memento)
{
_text = memento.GetState();
}
}
备忘录
// 备忘录类
public class Memento
{
private readonly string _state;
public Memento(string state)
{
_state = state;
}
public string GetState()
{
return _state;
}
}
负责人
// 负责人类
public class Caretaker
{
private readonly Stack<Memento> _mementos = new Stack<Memento>();
public void SaveMemento(Memento memento)
{
_mementos.Push(memento);
}
public Memento GetMemento()
{
if (_mementos.Count > 0)
{
return _mementos.Pop();
}
return null;
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
TextEditor editor = new TextEditor();
Caretaker caretaker = new Caretaker();
editor.SetText("Version 1");
caretaker.SaveMemento(editor.CreateMemento());
editor.SetText("Version 2");
caretaker.SaveMemento(editor.CreateMemento());
editor.SetText("Version 3");
Console.WriteLine("Current Text: " + editor.GetText());
editor.RestoreMemento(caretaker.GetMemento());
Console.WriteLine("Restored Text: " + editor.GetText());
editor.RestoreMemento(caretaker.GetMemento());
Console.WriteLine("Restored Text: " + editor.GetText());
}
}
运行结果
Current Text: Version 3
Restored Text: Version 2
Restored Text: Version 1
在这个例子中,TextEditor
是原发器,负责创建和恢复文本的状态;Memento
是备忘录,保存文本的状态;Caretaker
是负责人,管理备忘录的存储和恢复。在运行过程中,我们保存了多个文本状态,并通过恢复操作撤销了修改,返回到之前的状态。
优点:
封装性: 备忘录模式保证了原发器状态的封装性,外部对象无法直接访问备忘录的内容。
状态恢复: 允许对象恢复到之前的状态,提供了实现“撤销/恢复”功能的简单方法。
简化复杂性: 通过将状态的存储和恢复职责分离到不同的类中,简化了复杂系统的管理。
缺点:
开销大: 如果原发器的状态占用大量资源(如内存),备忘录模式可能会导致开销较大,尤其是在频繁保存和恢复状态时。
管理复杂: 如果备忘录数量众多,管理这些备忘录可能变得复杂,尤其是在涉及多线程操作时。
备忘录模式通过保存对象的状态,提供了恢复该状态的机制。它通过封装状态,确保了对象内部细节的保护,同时又允许状态的恢复。该模式非常适合用于实现“撤销/恢复”功能,并在需要管理复杂状态变更的场景中提供了很好的解决方案。
观察者模式的核心思想是当一个对象(被观察者)的状态改变时,所有依赖于它的对象(观察者)都会被通知并更新。这样一来,观察者模式实现了对象之间的松散耦合,使得一个对象的变化可以自动地传播到相关的对象。
观察者模式包含以下角色:
UML 类图
+---------------------------+ +-------------------+
| Subject | <------ | Observer |
+---------------------------+ +-------------------+
| + Attach(obs: Observer) | | + Update(): void |
| + Detach(obs: Observer) | +-------------------+
| + Notify(): void | ^
+---------------------------+ |
^ |
| |
+-----------------------+ +-------------------+
| ConcreteSubject | | ConcreteObserver |
+-----------------------+ +-------------------+
| - state: State | | - state: State |
| + GetState(): State | | + Update(): void |
| + SetState(State) | +-------------------+
+-----------------------+
假设我们要实现一个天气站系统,天气站会记录当前的天气信息,并通知注册的显示设备(如手机应用、网站等)进行更新。
观察者接口
// 观察者接口
public interface IObserver
{
void Update(string temperature, string humidity, string pressure);
}
主题接口
// 主题接口
public interface ISubject
{
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObservers();
}
具体主题
// 具体主题
public class WeatherStation : ISubject
{
private List<IObserver> _observers;
private string _temperature;
private string _humidity;
private string _pressure;
public WeatherStation()
{
_observers = new List<IObserver>();
}
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
public void NotifyObservers()
{
foreach (var observer in _observers)
{
observer.Update(_temperature, _humidity, _pressure);
}
}
public void SetMeasurements(string temperature, string humidity, string pressure)
{
_temperature = temperature;
_humidity = humidity;
_pressure = pressure;
NotifyObservers();
}
}
具体观察者
// 具体观察者
public class PhoneDisplay : IObserver
{
private string _temperature;
private string _humidity;
private string _pressure;
public void Update(string temperature, string humidity, string pressure)
{
_temperature = temperature;
_humidity = humidity;
_pressure = pressure;
Display();
}
public void Display()
{
Console.WriteLine($"Phone Display -> Temperature: {_temperature}, Humidity: {_humidity}, Pressure: {_pressure}");
}
}
public class WebDisplay : IObserver
{
private string _temperature;
private string _humidity;
private string _pressure;
public void Update(string temperature, string humidity, string pressure)
{
_temperature = temperature;
_humidity = humidity;
_pressure = pressure;
Display();
}
public void Display()
{
Console.WriteLine($"Web Display -> Temperature: {_temperature}, Humidity: {_humidity}, Pressure: {_pressure}");
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
WeatherStation weatherStation = new WeatherStation();
IObserver phoneDisplay = new PhoneDisplay();
IObserver webDisplay = new WebDisplay();
weatherStation.RegisterObserver(phoneDisplay);
weatherStation.RegisterObserver(webDisplay);
weatherStation.SetMeasurements("30°C", "65%", "1013 hPa");
weatherStation.RemoveObserver(phoneDisplay);
weatherStation.SetMeasurements("28°C", "70%", "1012 hPa");
}
}
运行结果
Phone Display -> Temperature: 30°C, Humidity: 65%, Pressure: 1013 hPa
Web Display -> Temperature: 30°C, Humidity: 65%, Pressure: 1013 hPa
Web Display -> Temperature: 28°C, Humidity: 70%, Pressure: 1012 hPa
在这个例子中,WeatherStation
是具体的主题,当天气数据发生变化时,它通知所有注册的观察者(如 PhoneDisplay
和 WebDisplay
)进行更新并显示新的数据。
优点:
松散耦合: 观察者和主题之间是松散耦合的,观察者可以独立于主题的变化而变化,增加了系统的灵活性。
动态更新: 观察者模式使得对象之间的通信更加灵活,可以动态添加或删除观察者,实时更新数据。
符合开放-封闭原则: 可以在不修改现有代码的情况下,增加新的观察者。
缺点:
通知开销: 如果有大量的观察者,通知所有观察者可能会引起开销,影响性能。
可能出现循环依赖: 如果观察者之间也相互依赖,可能会导致循环依赖问题,影响系统的稳定性。
观察者模式通过定义一对多的依赖关系,实现了对象间的松散耦合和动态通信。它允许对象自动通知相关的依赖对象并更新状态,非常适合用于事件驱动的系统和需要动态更新的场景。尽管可能会带来一定的通知开销和复杂性管理,但它依然是实现对象间动态通信的强大工具。
状态模式的核心思想是将与状态相关的行为封装在独立的状态对象中,并通过状态对象来管理对象的状态转换。这样,原始对象在其状态发生变化时,会自动切换到对应的状态对象,从而表现出不同的行为。
状态模式包含以下角色:
UML 类图
+-------------------+ +-----------------------------------+
| Context | | State |
+-------------------+ +-----------------------------------+
| - state: State |<------| + Handle(context: Context): void |
| + Request(): void | +-----------------------------------+
+-------------------+ ^ ^
| |
| |
+-----------------------------------+ +-----------------------------------+
| ConcreteStateA | | ConcreteStateB |
+-----------------------------------+ +-----------------------------------+
| + Handle(context: Context): void | | + Handle(context: Context): void |
+-----------------------------------+ +-----------------------------------+
假设我们要实现一个简单的电灯开关系统,电灯可以处于“开”和“关”两种状态,并且根据当前的状态来执行不同的操作。
状态接口
// 状态接口
public interface IState
{
void Handle(Context context);
}
具体状态类
// 具体状态 - 开灯状态
public class OnState : IState
{
public void Handle(Context context)
{
Console.WriteLine("The light is already ON.");
context.SetState(new OffState());
}
}
// 具体状态 - 关灯状态
public class OffState : IState
{
public void Handle(Context context)
{
Console.WriteLine("The light is OFF. Turning it ON.");
context.SetState(new OnState());
}
}
上下文类
// 上下文类
public class Context
{
private IState _state;
public Context(IState state)
{
_state = state;
}
public void SetState(IState state)
{
_state = state;
}
public void Request()
{
_state.Handle(this);
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
Context context = new Context(new OffState());
// 初始状态为关灯状态
context.Request(); // 关灯 -> 开灯
// 再次请求
context.Request(); // 开灯 -> 关灯
// 再次请求
context.Request(); // 关灯 -> 开灯
}
}
运行结果
The light is OFF. Turning it ON.
The light is already ON.
The light is OFF. Turning it ON.
在这个例子中,Context
类维护一个当前状态,当调用 Request()
方法时,会根据当前状态执行相应的操作并切换到下一个状态。OnState
和 OffState
是两种具体的状态类,分别定义了在不同状态下的行为。
优点:
减少复杂性: 通过将状态相关的行为封装在独立的状态对象中,状态模式消除了大量的条件分支语句,使代码更加清晰和易于维护。
状态转换灵活: 可以很容易地添加、删除或修改状态对象,扩展系统的功能,而无需修改上下文类。
符合单一职责原则: 状态模式将与状态相关的行为封装在不同的状态类中,使得每个类只负责一种状态的行为,简化了代码的管理。
缺点:
增加类的数量: 每个状态都需要定义一个具体的状态类,当状态过多时,可能会导致类的数量急剧增加,增加系统的复杂性。
状态之间的依赖: 如果状态之间存在复杂的依赖关系,可能会导致状态之间的转换逻辑变得复杂,难以维护。
状态模式通过将状态相关的行为封装在独立的状态对象中,简化了对象的状态管理逻辑。它消除了大量的条件分支语句,使代码更加清晰和易于扩展。尽管状态模式可能会增加类的数量,但它为管理复杂的状态转换逻辑提供了一种灵活且有效的解决方案。
策略模式的核心思想是将不同的算法或行为封装到独立的策略类中,并通过上下文类来管理和使用这些策略。客户端可以通过上下文类来动态选择使用哪种策略,而无需关心策略的具体实现细节。
策略模式包含以下角色:
UML 类图
+---------------------------+ +-----------------------+
| Context | | Strategy |
+---------------------------+ +-----------------------+
| - strategy: Strategy | | + Algorithm(): void |
| + SetStrategy(Strategy) | +-----------------------+
| + ExecuteStrategy(): void | ^ ^
+---------------------------+ | |
| |
| |
+-----------------------+ +-----------------------+
| ConcreteStrategyA | | ConcreteStrategyB |
+-----------------------+ +-----------------------+
| + Algorithm(): void | | + Algorithm(): void |
+-----------------------+ +-----------------------+
假设我们要实现一个简单的支付系统,它支持多种支付方式,如信用卡支付、PayPal支付和比特币支付。我们可以使用策略模式来封装这些支付方式,并让客户端在运行时选择不同的支付策略。
策略接口
// 策略接口
public interface IPaymentStrategy
{
void Pay(double amount);
}
具体策略类
// 具体策略 - 信用卡支付
public class CreditCardPayment : IPaymentStrategy
{
private string _cardNumber;
public CreditCardPayment(string cardNumber)
{
_cardNumber = cardNumber;
}
public void Pay(double amount)
{
Console.WriteLine($"Paid {amount} using Credit Card {_cardNumber}.");
}
}
// 具体策略 - PayPal支付
public class PayPalPayment : IPaymentStrategy
{
private string _email;
public PayPalPayment(string email)
{
_email = email;
}
public void Pay(double amount)
{
Console.WriteLine($"Paid {amount} using PayPal account {_email}.");
}
}
// 具体策略 - 比特币支付
public class BitcoinPayment : IPaymentStrategy
{
private string _walletAddress;
public BitcoinPayment(string walletAddress)
{
_walletAddress = walletAddress;
}
public void Pay(double amount)
{
Console.WriteLine($"Paid {amount} using Bitcoin wallet {_walletAddress}.");
}
}
上下文类
// 上下文类
public class PaymentContext
{
private IPaymentStrategy _paymentStrategy;
public void SetPaymentStrategy(IPaymentStrategy paymentStrategy)
{
_paymentStrategy = paymentStrategy;
}
public void Pay(double amount)
{
_paymentStrategy.Pay(amount);
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
PaymentContext context = new PaymentContext();
// 使用信用卡支付
context.SetPaymentStrategy(new CreditCardPayment("1234-5678-9012-3456"));
context.Pay(100.0);
// 使用PayPal支付
context.SetPaymentStrategy(new PayPalPayment("[email protected]"));
context.Pay(200.0);
// 使用比特币支付
context.SetPaymentStrategy(new BitcoinPayment("1BitcoinAddressXYZ"));
context.Pay(300.0);
}
}
运行结果
Paid 100 using Credit Card 1234-5678-9012-3456.
Paid 200 using PayPal account [email protected].
Paid 300 using Bitcoin wallet 1BitcoinAddressXYZ.
在这个例子中,PaymentContext
是上下文类,它持有一个 IPaymentStrategy
策略接口的引用。客户端可以动态设置不同的支付策略,如 CreditCardPayment
、PayPalPayment
和 BitcoinPayment
,并通过 Pay()
方法执行支付操作。这样,支付方式的变化不会影响客户端代码。
优点:
算法的灵活性: 策略模式允许在运行时选择不同的算法或行为,增加了系统的灵活性和扩展性。
避免使用条件语句: 通过将不同的算法封装在独立的策略类中,避免了在客户端代码中使用大量的条件分支语句。
遵循开放-封闭原则: 可以在不修改现有代码的情况下,通过添加新的策略类来扩展系统的功能。
缺点:
增加类的数量: 每个策略都需要一个具体的策略类,可能会导致类的数量增加,增加系统的复杂性。
策略选择的复杂性: 在一些情况下,策略的选择逻辑可能本身就比较复杂,如何选择合适的策略可能会成为一个挑战。
策略模式通过将不同的算法封装到独立的策略类中,实现了算法的灵活互换。它消除了大量的条件分支语句,使代码更加清晰和可扩展。尽管策略模式可能会增加类的数量,但它为系统的算法选择和扩展提供了一种灵活且强大的解决方案。在需要灵活选择算法或行为的场景中,策略模式是一种非常有效的设计模式。
模板方法模式通过在基类中定义一个模板方法,该方法封装了一个算法的固定步骤,然后允许子类实现或重写这些步骤。这样,子类可以定制算法的具体行为,而无需改变算法的整体结构。
模板方法模式包含以下角色:
UML 类图
+---------------------------+
| AbstractClass |
+---------------------------+
| + TemplateMethod(): void |
| + Step1(): void |
| + Step2(): void |
| - Step3(): void |
+---------------------------+
^
|
+-------------------+
| ConcreteClass |
+-------------------+
| - Step3(): void |
+-------------------+
假设我们要制作一杯饮料,制作的过程包括煮水、冲泡、倒入杯中、添加配料等步骤。咖啡和茶是两种不同的饮料,它们在制作过程中的步骤基本相同,但在某些步骤上有所不同。我们可以使用模板方法模式来实现这个场景。
抽象类
// 抽象类 - 饮料制作过程
public abstract class Beverage
{
// 模板方法
public void PrepareRecipe()
{
BoilWater();
Brew();
PourInCup();
AddCondiments();
}
// 具体方法 - 煮水
private void BoilWater()
{
Console.WriteLine("Boiling water");
}
// 抽象方法 - 冲泡
protected abstract void Brew();
// 具体方法 - 倒入杯中
private void PourInCup()
{
Console.WriteLine("Pouring into cup");
}
// 抽象方法 - 添加配料
protected abstract void AddCondiments();
}
具体类
// 具体类 - 茶
public class Tea : Beverage
{
protected override void Brew()
{
Console.WriteLine("Steeping the tea");
}
protected override void AddCondiments()
{
Console.WriteLine("Adding lemon");
}
}
// 具体类 - 咖啡
public class Coffee : Beverage
{
protected override void Brew()
{
Console.WriteLine("Dripping coffee through filter");
}
protected override void AddCondiments()
{
Console.WriteLine("Adding sugar and milk");
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
Beverage tea = new Tea();
tea.PrepareRecipe(); // 制作茶
Console.WriteLine();
Beverage coffee = new Coffee();
coffee.PrepareRecipe(); // 制作咖啡
}
}
运行结果
Boiling water
Steeping the tea
Pouring into cup
Adding lemon
Boiling water
Dripping coffee through filter
Pouring into cup
Adding sugar and milk
在这个例子中,Beverage
是抽象类,定义了一个模板方法 PrepareRecipe()
,它包含了制作饮料的固定步骤。这些步骤中,有些是具体实现的(如 BoilWater()
和 PourInCup()
),而有些是抽象方法,由子类 Tea
和 Coffee
来实现(如 Brew()
和 AddCondiments()
)。客户端代码可以使用不同的具体类来制作不同的饮料,而不需要关心具体的实现细节。
优点:
代码复用: 将通用的算法步骤封装到基类中,子类只需要实现差异化的部分,减少了重复代码。
扩展性强: 子类可以根据需要重写或扩展某些步骤,增加算法的灵活性。
控制算法结构: 父类定义了算法的骨架,确保了算法的整体结构不被子类破坏。
缺点:
增加代码复杂性: 由于引入了继承关系和抽象方法,可能会使代码结构变得复杂。
对子类的依赖: 父类依赖子类来实现某些步骤,可能导致子类必须实现某些方法,即使这些方法在特定情况下并不需要。
模板方法模式通过将通用的算法步骤封装到抽象类中,允许子类重写或扩展特定的步骤,实现了算法的复用和扩展。它确保了算法的整体结构不被破坏,同时为子类提供了灵活性。在多个类具有相似的操作步骤时,模板方法模式是一种非常有效的设计模式。
访问者模式通过引入一个访问者接口,使得你可以在元素类中接受访问者,并让访问者决定对元素的具体操作。访问者模式的关键在于分离算法和数据结构,使得新的操作可以轻松地添加而不影响已有的数据结构。
访问者模式包含以下角色:
Visit
方法,针对不同的元素类有不同的实现。Accept
方法,该方法接受访问者对象并调用访问者的Visit
方法。Accept
方法中调用访问者的对应方法。UML 类图
+---------------------------+ +---------------------------+
| Visitor | <------ | Element |
+---------------------------+ +---------------------------+
| + VisitElementA():void | | + Accept(v:Visitor): void |
| + VisitElementB():void | +---------------------------+
+---------------------------+ ^
^ |
| |
+---------------------------+ +---------------------------+
| ConcreteVisitor | | ConcreteElement |
+---------------------------+ +---------------------------+
| + VisitElementA():void | | + Accept(v:Visitor): void |
| + VisitElementB():void | | + OperationA(): void |
+---------------------------+ +---------------------------+
假设我们要实现一个报表系统,系统中包含不同类型的员工(如工程师和经理),每种员工有不同的报表要求。我们可以使用访问者模式来实现报表的生成,使得报表的生成与员工类型的实现分离。
访问者接口
// 访问者接口
public interface IVisitor
{
void Visit(Engineer engineer);
void Visit(Manager manager);
}
具体访问者
// 具体访问者 - 报表生成器
public class ReportGenerator : IVisitor
{
public void Visit(Engineer engineer)
{
Console.WriteLine($"Generating report for Engineer: {engineer.Name}");
}
public void Visit(Manager manager)
{
Console.WriteLine($"Generating report for Manager: {manager.Name} with {manager.SubordinatesCount} subordinates.");
}
}
元素接口
// 元素接口
public interface IEmployee
{
void Accept(IVisitor visitor);
}
具体元素类
// 具体元素 - 工程师
public class Engineer : IEmployee
{
public string Name { get; private set; }
public Engineer(string name)
{
Name = name;
}
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// 具体元素 - 经理
public class Manager : IEmployee
{
public string Name { get; private set; }
public int SubordinatesCount { get; private set; }
public Manager(string name, int subordinatesCount)
{
Name = name;
SubordinatesCount = subordinatesCount;
}
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
对象结构
// 对象结构 - 员工列表
public class EmployeeStructure
{
private List<IEmployee> _employees = new List<IEmployee>();
public void Attach(IEmployee employee)
{
_employees.Add(employee);
}
public void Detach(IEmployee employee)
{
_employees.Remove(employee);
}
public void Accept(IVisitor visitor)
{
foreach (var employee in _employees)
{
employee.Accept(visitor);
}
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建员工结构
EmployeeStructure employeeStructure = new EmployeeStructure();
// 添加员工
employeeStructure.Attach(new Engineer("John"));
employeeStructure.Attach(new Manager("Alice", 5));
// 创建报表生成器
ReportGenerator reportGenerator = new ReportGenerator();
// 生成报表
employeeStructure.Accept(reportGenerator);
}
}
运行结果
Generating report for Engineer: John
Generating report for Manager: Alice with 5 subordinates.
在这个例子中,IVisitor
定义了对不同员工类型(Engineer
和 Manager
)的访问方法。ReportGenerator
是具体的访问者,实现了生成报表的逻辑。IEmployee
接口定义了 Accept
方法,Engineer
和 Manager
作为具体的元素,实现了接受访问者的逻辑。EmployeeStructure
作为对象结构,管理了所有的员工,并允许访问者访问这些员工。
优点:
增加新的操作容易: 可以在不修改元素类的情况下,通过添加新的访问者类来增加新的操作。
将操作与对象结构分离: 访问者模式将数据结构和操作分离,使得数据结构和操作各自独立,符合单一职责原则。
扩展性好: 可以很容易地增加新的访问者来实现新的功能。
缺点:
增加元素类的复杂性: 每个元素类都必须实现接受访问者的方法,这可能会增加类的复杂性。
违反开闭原则: 如果需要修改元素的结构或添加新的元素类型,则需要修改所有的访问者类,这与开闭原则相违背。
双分派: 访问者模式要求进行双分派,即根据元素类型和访问者类型分别调用相应的方法,这可能会导致系统的复杂性增加。
访问者模式通过将操作分离到独立的访问者对象中,使得在不修改元素类的情况下,可以增加新的操作。它适用于对象结构稳定但操作经常变化的场景。然而,由于需要对每个元素类增加接受访问者的方法,并且可能导致违反开闭原则,因此在使用时需要权衡利弊。在需要对复杂对象结构进行扩展和管理时,访问者模式是一种强大的设计模式。