三大分类
设计模式通常被分类为三大类:创建型、结构型和行为型。以下是对这三大类设计模式的详解:
1. 创建型模式 (Creational Patterns)
创建型模式与对象的创建过程有关。它们为对象的创建提供更大的灵活性和复用性。
主要创建型模式有:
- 单例模式 (Singleton): 确保一个类只有一个实例,并提供一个全局点来访问这个实例。
- 原型模式 (Prototype): 允许复制已存在的对象,而不是通过新建来创建。
- 工厂方法模式 (Factory Method): 允许子类决定要实例化的对象类别。
- 抽象工厂模式 (Abstract Factory): 提供一个接口以创建一系列相关或相互依赖的对象,而不指定它们具体的类。
- 建造者模式 (Builder): 分离一个复杂对象的构建和表示,确保相同的构建过程可以创建不同的表示。
2. 结构型模式 (Structural Patterns)
结构型模式与对象的组合有关。它们确保结构中的各个元素能够清晰、灵活地组合在一起。
主要结构型模式有:
- 适配器模式 (Adapter): 允许一个已存在的类的接口被转化为另一个接口。
- 桥接模式 (Bridge): 将抽象与其实现分离,以便二者可以独立变化。
- 组合模式 (Composite): 组合多个对象形成树形结构以表示“部分-整体”的层次结构。
- 装饰器模式 (Decorator): 动态地给一个对象添加一些额外的职责。
- 外观模式 (Facade): 提供一个统一的接口以访问一系列接口,从而为这些子系统提供一个高级的接口。
- 享元模式 (Flyweight): 使用共享来有效地支持大量细粒度对象。
- 代理模式 (Proxy): 为其他对象提供一个代理或占位符,以控制对这个对象的访问。
3. 行为型模式 (Behavioral Patterns)
行为型模式与对象间的责任和算法有关,它们定义了对象如何相互交互和协作。
主要行为型模式有:
- 责任链模式 (Chain of Responsibility): 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。
- 命令模式 (Command): 将请求封装为对象,允许用户使用不同的请求、队列请求或记录请求日志。
- 解释器模式 (Interpreter): 为语言创建解释器,通常用于解析语法。
- 迭代器模式 (Iterator): 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
- 中介者模式 (Mediator): 用一个中介对象来封装一系列的对象交互。
- 备忘录模式 (Memento): 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
- 观察者模式 (Observer): 当对象间存在一对多关系时,使用观察者模式。例如,当一个对象被修改,其依赖对象都会被通知并更新。
- 状态模式 (State): 允许对象在其内部状态改变时改变它的行为。
- 策略模式 (Strategy): 定义了算法族,分别封装起来,让它们之间可以互相替换。
- 模板方法模式 (Template Method): 在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。
- 访问者模式 (Visitor): 在不修改现有代码的前提下增加新的操作。
每种设计模式都有其特定的应用场景和优点,它们的主要目的是帮助软件开发者编写可重用、可维护和可扩展的代码。
原型模式
原型模式
定义
原型模式是一种创建型设计模式,它允许您通过克隆现有对象来创建新对象,而不是通过实例化新对象并复制它的数据。克隆的对象是原型,它定义了创建对象的种类。原型模式的实现通常涉及一个抽象原型类和多个具体原型类,这些类实现了抽象原型接口以克隆自己。原型模式通常与工厂方法模式一起使用,以便在运行时动态创建新对象。
结构
- 抽象原型 (Prototype):声明一个克隆自身的接口。
- 具体原型 (Concrete Prototype):实现克隆方法,返回一个新的对象。
- 客户端 (Client):创建一个新对象的对象。
优点
- 原型模式允许您复制现有对象,从而减少了代码的复杂性和开销。它是创建新对象的一种有效方式,因为它比实例化一个新对象并复制其数据要快得多。
- 它使得您可以动态地克隆对象,从而避免了静态工厂方法模式中的类构造函数调用和初始化代码的需求。
- 它允许您更轻松地实现深复制,因为您可以使用现有对象中的数据来填充新对象的属性。
缺点
- 原型模式的主要缺点在于它可能会使代码更加复杂,因为您需要创建和维护原型类和原型注册表。
- 一些编程语言可能不支持克隆操作,从而可能使得实现原型模式更加困难。
适用场景
- 当创建对象的成本比克隆对象的成本更高时,原型模式是一个很好的选择。
- 如果您希望动态地添加和删除对象,并且希望它们的副本具有相同的状态,原型模式是一个很好的选择。
- 如果您需要更轻松地实现深复制,并且不会影响到您的应用程序的性能,原型模式是一个很好的选择。
示例
public interface IPrototype<T>
{
T Clone();
}
public class Person : IPrototype<Person>
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public Person Clone()
{
return new Person(Name, Age);
}
public override string ToString()
{
return $"{Name}, {Age} years old";
}
}
class Program
{
static void Main()
{
Person person1 = new Person("John", 30);
Person person2 = person1.Clone();
person2.Name = "Jane";
person2.Age = 25;
Console.WriteLine(person1);
Console.WriteLine(person2);
}
}
输出结果:
John, 30 years old
Jane, 25 years
工厂模式
结构
抽象工厂类 (Abstract Factory):提供了创建抽象产品的接口,包括创建一组相关或相互依赖的产品。
具体工厂类 (Concrete Factory):实现了抽象工厂接口,负责具体产品的创建。
产品类 (Product):定义了具体产品的基本行为和属性,可以由具体工厂创建。
案例
案例一
public abstract class Product
{
public abstract void DoWork();
}
public class ConcreteProductA : Product
{
public override void DoWork()
{
Console.WriteLine("Doing work in ConcreteProductA");
}
}
public class ConcreteProductB : Product
{
public override void DoWork()
{
Console.WriteLine("Doing work in ConcreteProductB");
}
}
public class Creator
{
public Product FactoryMethod(string productType)
{
switch (productType)
{
case "A":
return new ConcreteProductA();
case "B":
return new ConcreteProductB();
default:
throw new ArgumentException("Invalid product type", "productType");
}
}
}
class Program
{
static void Main()
{
Creator creator = new Creator();
Product productA = creator.FactoryMethod("A");
productA.DoWork();
Product productB = creator.FactoryMethod("B");
productB.DoWork();
}
}
抽象工厂模式
抽象工厂模式
定义
抽象工厂模式是一种创建型设计模式,它允许您将对象的创建委托给其他对象,而不是在应用程序中直接实例化它们。抽象工厂模式提供了一个接口,用于创建相关或依赖对象的家族,而不需要指定它们的具体类。它是工厂方法模式的扩展。
结构
- 抽象工厂 (Abstract Factory):定义了一个接口,用于创建一系列相关或依赖对象的家族。抽象工厂允许客户端使用抽象接口来创建一组相关的产品对象,而不必关心实际产生这些对象的具体工厂类。
- 具体工厂 (Concrete Factory):实现了抽象工厂接口,生成一组具体产品。具体工厂与某个具体产品相关联,负责实现其实例化。
- 抽象产品 (Abstract Product):定义了一个产品的接口,它是工厂方法模式所创建的所有对象的父类,但它不负责具体的产品创建,而是由其子类负责创建具体的产品对象。
- 具体产品 (Concrete Product):是工厂方法模式的创建目标,所有创建的对象都是具体产品的实例。
优点
- 抽象工厂模式使得客户端代码从具体产品类中解耦。
- 它能够确保一组产品对象被设计成一起使用。
- 它能够保证新产品的一致性,因为新产品的添加不会影响现有产品的接口。
- 它能够提供对产品类库的访问。
缺点
- 抽象工厂模式的最大缺点在于难以支持新种类的产品。
- 增加新种类的产品需要更改抽象工厂的接口,这样就需要更改所有的具体工厂。
适用场景
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节。
- 系统中有多于一个的产品族,而每次只使用其中某一产品族。
- 属于同一个产品族的产品将在一起使用,而不会将这些产品族的产品随意混合。
- 产品等级结构稳定,不会向系统中增加新的产品族或者产品等级结构。
示例
public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("Dog says: Bow-Wow.");
}
}
public class Cat : IAnimal
{
public void Speak()
{
Console.WriteLine("Cat says: Meow-Meow.");
}
}
public interface IAnimalFactory
{
IAnimal CreateAnimal();
}
public class PetFactory : IAnimalFactory
{
public IAnimal CreateAnimal()
{
return new Dog();
}
}
public class WildFactory : IAnimalFactory
{
public IAnimal CreateAnimal()
{
return new Cat();
}
}
class Program
{
static void Main(string[] args)
{
IAnimalFactory factory = new PetFactory();
IAnimal animal = factory.CreateAnimal();
animal.Speak();
factory = new WildFactory();
animal = factory.CreateAnimal();
animal.Speak();
}
}
输出结果:
Dog says: Bow-Wow.
Cat says: Meow-Meow.
在上面的示例中,我们定义了一个
IAnimal
接口和两个具体类 Dog
和 Cat
,这些类代表了两个不同的产品族。我们还定义了一个 IAnimalFactory
接口和两个具体工厂类 PetFactory
和 WildFactory
,这些工厂类分别用于创建宠物和野生动物。最后,我们在主函数中创建了一个 PetFactory
实例,使用它来创建一个 Dog
对象,并调用 Speak
方法。然后,我们创建了一个 WildFactory
实例,使用它来创建一个 Cat
对象,并再次调用 Speak
方法。工厂模式总结
工厂模式的退化
当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;
当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。
建造者模式
建造者模式
定义
建造者模式是一种创建型设计模式,允许使用相同的创建代码生成多个不同的对象。建造者模式将对象构造步骤分离,以便可以使用相同的构造过程创建不同的表示形式。建造者模式的实现通常涉及一个抽象建造者类和多个具体建造者类,这些类实现了抽象建造者接口以构建不同部件和最终产品。另外,还有一个指导者类,它控制建造过程,允许构建具有不同表示形式的对象。
结构
- 产品 (Product):表示正在构建的复杂对象。具体部件由具体建造者实例化和组装。
- 建造者 (Builder):定义了生成对象的步骤和接口。
- 具体建造者 (Concrete Builder):实现了 Builder 接口,并提供了一种定义对象各个部分的方法。
- 指导者 (Director):构建一个使用 Builder 接口的对象。它主要是用于控制建造过程,也可以用来检查最终产品。
优点
- 建造者模式使得代码更加模块化,使得对象的创建和表示分离并且易于扩展。它将创建代码与表示代码分离,从而使代码更具可读性和可维护性。
- 它使得您可以按步骤创建一个对象,从而使得创建过程更加精细化,并且可以定制每个对象的细节。
- 它允许您使用相同的构建代码生成不同的对象,从而节省了代码和时间。
缺点
- 建造者模式的主要缺点在于它需要您创建多个类,这些类可能会增加代码的复杂性和开销。如果您的对象具有少量部件或者只有一个非常复杂的部件,则建造者模式可能不是最好的选择。
- 与其他创建型模式相比,建造者模式需要更多的代码,这可能会导致性能问题。如果您的应用程序需要高性能,请考虑使用其他模式。
适用场景
- 如果您需要按步骤创建复杂对象,建造者模式是一个很好的选择。
- 如果您的对象具有许多可选部件或者需要以不同的方式进行构建,建造者模式是一个很好的选择。
- 如果您需要从具有相同部件的简单对象中创建复杂对象,建造者模式是一个很好的选择。
示例
// 产品
public class Burger
{
public string Bun { get; set; }
public string Patty { get; set; }
public string Sauce { get; set; }
}
// 抽象建造者
public interface IBurgerBuilder
{
void AddBun();
void AddPatty();
void AddSauce();
Burger GetBurger();
}
// 具体建造者
public class ChickenBurgerBuilder : IBurgerBuilder
{
private Burger _burger = new Burger();
public void AddBun()
{
_burger.Bun = "Wheat Bun";
}
public void AddPatty()
{
_burger.Patty = "Chicken Patty";
}
public void AddSauce()
{
_burger.Sauce = "Mayo";
}
public Burger GetBurger()
{
return _burger;
}
}
// 指挥者
public class Chef
{
private IBurgerBuilder _burgerBuilder;
public Chef(IBurgerBuilder burgerBuilder)
{
_burgerBuilder = burgerBuilder;
}
public void MakeBurger()
{
_burgerBuilder.AddBun();
_burgerBuilder.AddPatty();
_burgerBuilder.AddSauce();
}
public Burger GetBurger()
{
return _burgerBuilder.GetBurger();
}
}
// 客户端代码
public class Program
{
static void Main()
{
IBurgerBuilder builder = new ChickenBurgerBuilder();
Chef chef = new Chef(builder);
chef.MakeBurger();
Burger chickenBurger = chef.GetBurger();
Console.WriteLine($"Created a burger with {chickenBurger.Bun}, {chickenBurger.Patty} and {chickenBurger.Sauce}");
}
}
适配器模式
结构
目标接口 (Target): 这是你期望得到的接口
需要适配的类 (Adaptee): 你希望适配的现有类或接口
适配器 (Adapter): 它在目标接口和需要适配的类之间起到中介的作用
案例
案例一
public interface IMediaPlayer
{
void Play(string audioType, string fileName);
}
public abstract class VlcPlayer
{
public void PlayVlc(string fileName)
{
Console.WriteLine($"Playing vlc file. Name: {fileName}");
}
}
public class MediaAdapter : IMediaPlayer,VlcPlayer
{
public void Play()
{
// 调用两个孔插头方法
this.PlayVlc();
}
}
IMediaPlayer mediaAdapter= new MediaAdapter();
mediaAdapter.Play(filename);
桥接模式
桥接模式
定义
桥接模式是一种结构型设计模式,它将抽象和实现分离,以便它们可以独立地变化。桥接模式的实现通常涉及一个抽象类和多个具体类,这些具体类实现了抽象类并提供了不同的实现。桥接模式通常比使用单一继承更灵活,并且可以更方便地扩展和维护。它还可以使您更好地控制类的层次结构,并且可以更轻松地实现依赖反转。
结构
- 抽象类 (Abstraction):定义了抽象类的接口,并维护一个指向实现类的引用。
- 扩展抽象类 (Refined Abstraction):扩展了抽象类,添加了更多的功能。
- 实现类接口 (Implementor):定义了实现类的接口。
- 具体实现类 (Concrete Implementor):实现实现类接口,并提供具体的行为。
优点
- 桥接模式使得您可以将抽象和实现分离,从而可以更容易地扩展和维护代码。它将继承关系转换为组合关系,并将其隔离在不同的类中,从而使得代码更加灵活和可扩展。
- 它允许您在运行时切换实现,从而使得您可以更灵活地适应不同的需求。
- 它可以使您更好地控制类的层次结构,并使其更加简单和易于理解。
缺点
- 桥接模式的主要缺点在于它可能会增加代码的复杂性,并且可能会导致性能问题。如果您的应用程序需要高性能,请考虑使用其他模式。
- 它还需要您创建和维护多个类,这可能会增加代码的复杂性和开销。
适用场景
- 如果您需要在运行时动态地将实现绑定到一个抽象类,桥接模式是一个很好的选择。
- 如果您需要更好地控制类的层次结构,桥接模式是一个很好的选择。
- 如果您的代码需要更好地扩展和维护,桥接模式是一个很好的选择。
示例
// Implementor
public interface IColor
{
string ApplyColor();
}
// ConcreteImplementors
public class RedColor : IColor
{
public string ApplyColor() => "Red";
}
public class BlueColor : IColor
{
public string ApplyColor() => "Blue";
}
// Abstraction
public abstract class Shape
{
protected IColor color;
public Shape(IColor color)
{
this.color = color;
}
public abstract string Draw();
}
// RefinedAbstraction
public class Circle : Shape
{
public Circle(IColor color) : base(color) { }
public override string Draw() => $"Circle is drawn in {color.ApplyColor()}";
}
public class Square : Shape
{
public Square(IColor color) : base(color) { }
public override string Draw() => $"Square is drawn in {color.ApplyColor()}";
}
// Client
public class Client
{
public static void Main()
{
IColor red = new RedColor();
IColor blue = new BlueColor();
Shape circle = new Circle(red);
Console.WriteLine(circle.Draw());
Shape square = new Square(blue);
Console.WriteLine(square.Draw());
}
}
组合模式
结构
组件 (Component):这是所有参与组合对象的抽象类。它可以是一个接口或抽象类,为叶子对象和组合对象声明接口
叶子 (Leaf):在组合中表示叶节点对象,叶子没有任何子节点
组合 (Composite):定义有子部件的那些分支的行为,它存储组件,并实现在组件中定义的接口
案例
案例一
public abstract class FileSystemComponent
{
public string Name { get; set; }
public FileSystemComponent(string name)
{
Name = name;
}
public abstract void Display();
}
public class File : FileSystemComponent
{
private int _size;
public File(string name, int size) : base(name)
{
_size = size;
}
public override void Display()
{
Console.WriteLine($"File: {Name}, Size: {_size}KB");
}
}
public class Folder : FileSystemComponent
{
private List<FileSystemComponent> _components = new List<FileSystemComponent>();
public Folder(string name) : base(name)
{ }
public void AddComponent(FileSystemComponent component)
{
_components.Add(component);
}
public void RemoveComponent(FileSystemComponent component)
{
_components.Remove(component);
}
public override void Display()
{
Console.WriteLine($"Folder: {Name}");
foreach(var component in _components)
{
component.Display();
}
}
}
class Program
{
static void Main()
{
var file1 = new File("File1.txt", 100);
var file2 = new File("File2.txt", 200);
var file3 = new File("File3.jpg", 500);
var folder = new Folder("Documents");
folder.AddComponent(file1);
folder.AddComponent(file2);
var imagesFolder = new Folder("Images");
imagesFolder.AddComponent(file3);
folder.AddComponent(imagesFolder);
folder.Display();
}
}
Folder: Documents
File: File1.txt, Size: 100KB
File: File2.txt, Size: 200KB
Folder: Images
File: File3.jpg, Size: 500KB
装饰器模式
装饰器模式
定义
装饰器模式是一种结构型设计模式,它允许您通过将对象放入包装器中来为其添加行为。装饰器模式的实现通常涉及一个抽象装饰器类和多个具体装饰器类,这些类继承了抽象装饰器接口以增强对象的行为。装饰器模式通常与组件模式一起使用,以便在运行时动态地添加新行为。
结构
- 组件 (Component):定义了基本操作的接口,这些操作可以被具体装饰器和具体组件实现。
- 具体组件 (Concrete Component):实现组件接口,并提供基本操作的默认实现。
- 抽象装饰器 (Decorator):实现组件接口,并保存一个指向被装饰对象的引用。
- 具体装饰器 (Concrete Decorator):继承自抽象装饰器,并在组件的基础上添加了新的行为或修改了原有行为。
优点
- 装饰器模式允许您在运行时动态地添加新行为,而不是在编译时静态地修改代码。这使得代码更加灵活,并且使得您可以更好地满足客户需求。
- 它使得您可以使用多个小型、简单的装饰器来实现复杂的行为。这使得代码更加模块化,更易于阅读和维护。
- 它允许您在不影响其他对象的情况下对对象进行修改。
缺点
- 装饰器模式可能会导致您在代码中添加大量的小型类,这可能会导致代码复杂性和重复性的增加。
- 装饰器模式可能会使代码变得难以理解,特别是在实现复杂的装饰器时。这使得调试和修复错误变得更加困难。
适用场景
- 当您需要在运行时动态地添加行为时,装饰器模式是一种很好的选择。
- 如果您需要使用多个小型、简单的装饰器来实现复杂的行为,装饰器模式是一种很好的选择。
- 当您需要对现有对象进行修改,而不影响其他对象时,装饰器模式是一种很好的选择。
示例
using System;
namespace DecoratorPattern
{
// 抽象组件
public interface ICoffee
{
string GetDescription();
double GetCost();
}
// 具体组件
public class SimpleCoffee : ICoffee
{
public string GetDescription()
{
return "Simple coffee";
}
public double GetCost()
{
return 5;
}
}
// 抽象装饰器
public abstract class CoffeeDecorator : ICoffee
{
protected ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee)
{
_coffee = coffee;
}
public virtual string GetDescription()
{
return _coffee.GetDescription();
}
public virtual double GetCost()
{
return _coffee.GetCost();
}
}
// 具体装饰器
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return base.GetDescription() + ", with milk";
}
public override double GetCost()
{
return base.GetCost() + 1.5;
}
}
public class SugarDecorator : CoffeeDecorator
{
public SugarDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return base.GetDescription() + ", with sugar";
}
public override double GetCost()
{
return base.GetCost() + 0.5;
}
}
class Program
{
static void Main()
{
ICoffee coffee = new SimpleCoffee();
Console.WriteLine($"Cost: {coffee.GetCost()}; Description: {coffee.GetDescription()}");
coffee = new MilkDecorator(coffee);
Console.WriteLine($"Cost: {coffee.GetCost()}; Description: {coffee.GetDescription()}");
coffee = new SugarDecorator(coffee);
Console.WriteLine($"Cost: {coffee.GetCost()}; Description: {coffee.GetDescription()}");
}
}
}
外观模式
外观模式
定义
外观模式是一种结构型设计模式,它为一组复杂的子系统提供了一个简单的接口。外观模式的实现通常涉及一个外观类,该类为客户端提供了一个简单的接口,以便访问子系统的功能。外观模式通常被认为是另一种类似于适配器模式的包装器模式,因为它们都涉及到将一个对象包装在另一个对象中。但是,这两种模式的意图是不同的。适配器模式旨在将一个接口转换为另一个接口,而外观模式旨在简化接口并隐藏实现细节。
结构
- 客户端 (Client):使用外观类的对象。
- 外观 (Facade):为客户端提供了一个简单的接口,以便访问子系统的功能。
- 子系统 (Subsystem):实现了子系统的功能的类。
优点
- 外观模式使得客户端代码更加简单和易于使用,因为它们不需要了解子系统的复杂性和实现细节。
- 它隐藏了子系统的实现细节,从而使得子系统更加容易修改和更换。
- 外观模式使得客户端代码更加松散耦合,因为客户端只需要与外观类交互,而不需要了解子系统的实现细节。
缺点
- 外观模式可能会导致您的代码变得更加复杂,因为您需要创建和维护外观类。
- 如果您需要访问子系统的所有功能,则外观模式可能不是最佳选择。
适用场景
- 当您需要简化复杂子系统的接口时,外观模式是一种很好的选择。
- 如果您希望将子系统与客户端代码分离,从而使其更加可维护和可测试,外观模式是一种很好的选择。
- 如果您需要简化客户端代码并使其更加易于使用,外观模式是一种很好的选择。
示例
using System;
namespace FacadePatternExample
{
class Projector
{
public void On() => Console.WriteLine("Projector is on");
public void Off() => Console.WriteLine("Projector is off");
}
class SoundSystem
{
public void On() => Console.WriteLine("SoundSystem is on");
public void SetVolume(int level) => Console.WriteLine($"Volume set to {level}");
}
class DVDPlayer
{
public void On() => Console.WriteLine("DVDPlayer is on");
public void Play(string movie) => Console.WriteLine($"Playing {movie}");
}
class HomeTheaterFacade
{
private Projector _projector;
private SoundSystem _soundSystem;
private DVDPlayer _dvdPlayer;
public HomeTheaterFacade(Projector projector, SoundSystem soundSystem, DVDPlayer dvdPlayer)
{
_projector = projector;
_soundSystem = soundSystem;
_dvdPlayer = dvdPlayer;
}
public void WatchMovie(string movie)
{
Console.WriteLine("Getting ready to watch a movie...");
_projector.On();
_soundSystem.On();
_soundSystem.SetVolume(5);
_dvdPlayer.On();
_dvdPlayer.Play(movie);
}
public void EndMovie()
{
_dvdPlayer.Off();
_soundSystem.Off();
_projector.Off();
}
}
class Program
{
static void Main(string[] args)
{
Projector projector = new Projector();
SoundSystem soundSystem = new SoundSystem();
DVDPlayer dvdPlayer = new DVDPlayer();
HomeTheaterFacade homeTheater = new HomeTheaterFacade(projector, soundSystem, dvdPlayer);
homeTheater.WatchMovie("Inception");
Console.WriteLine("...");
homeTheater.EndMovie();
}
}
}
享元模式
享元模式
定义
享元模式是一种结构型设计模式,它将对象拆分成更小、更可复用的部分。享元模式的实现通常涉及一个工厂类,该类维护一个缓存池以存储已经创建的对象。每次客户端请求对象时,工厂类都会首先检查缓存池中是否已经存在该对象。如果存在,则返回缓存池中的对象;否则,工厂类将创建一个新的对象并将其添加到缓存池中。享元模式通常用于优化内存使用,特别是在需要大量重复对象的情况下。
结构
- 享元工厂 (Flyweight Factory):维护一个缓存池以存储已创建的对象。每次客户端请求对象时,工厂类都会首先检查缓存池中是否已经存在该对象。如果存在,则返回缓存池中的对象;否则,工厂类将创建一个新的对象并将其添加到缓存池中。
- 享元接口 (Flyweight):定义了一个接口,用于描述具体享元的基本属性和方法。
- 具体享元 (Concrete Flyweight):实现享元接口,并存储了具体享元的内部状态。
优点
- 享元模式可以大量减少内存使用,特别是在需要大量重复对象的情况下。
- 它可以提高对象的创建速度,因为工厂类可以重复使用现有对象,而不必每次都创建新的对象。
- 它可以简化代码逻辑,因为它将对象拆分成更小、更可复用的部分。
缺点
- 享元模式可能会增加代码的复杂性,因为它需要您创建和维护多个类。
- 它可能会导致性能问题,特别是在需要频繁创建和销毁对象的情况下。如果您的应用程序需要高性能,请考虑使用其他模式。
适用场景
- 当您需要大量重复对象时,享元模式是一个很好的选择。
- 如果您需要提高对象的创建速度,享元模式是一个很好的选择。
- 如果您需要简化代码逻辑并将对象拆分成更小、更可复用的部分,享元模式是一个很好的选择。
示例
using System;
using System.Collections.Generic;
// 1. Flyweight (抽象享元类)
public interface ICharacter
{
char Symbol { get; }
void Display(int fontSize);
}
// 2. ConcreteFlyweight (具体享元类)
public class Character : ICharacter
{
public char Symbol { get; private set; }
public Character(char symbol)
{
Symbol = symbol;
}
public void Display(int fontSize)
{
Console.WriteLine($"Symbol: {Symbol} at Font Size: {fontSize}");
}
}
// 3. FlyweightFactory (享元工厂类)
public class CharacterFactory
{
private Dictionary<char, ICharacter> _characters = new Dictionary<char, ICharacter>();
public ICharacter GetCharacter(char key)
{
ICharacter character = null;
if (!_characters.ContainsKey(key))
{
character = new Character(key);
_characters.Add(key, character);
}
else
{
character = _characters[key];
}
return character;
}
}
// Client
public class FlyweightExample
{
static void Main(string[] args)
{
var factory = new CharacterFactory();
int fontSize = 10; // 外部状态
string document = "AAZZ";
foreach (char c in document)
{
ICharacter character = factory.GetCharacter(c);
character.Display(fontSize++);
}
}
}
代理模式
代理模式
定义
代理模式是一种结构型设计模式,它允许您提供替代品或占位符,以控制对对象的访问。使用代理模式,您可以在访问一个对象时添加额外的功能,而不必更改该对象的代码。
结构
- 主题 (Subject):定义了抽象主题接口,该接口可被代理和具体主题实现。
- 代理 (Proxy):实现主题接口,并保持一个指向具体主题对象的引用。
- 具体主题 (Real Subject):实现主题接口,定义了代理所代表的对象。
优点
- 控制对象的访问:
- 当你希望在访问对象之前或之后执行某些操作时(例如,安全检查、日志记录、性能计数),代理模式非常有用。
- 延迟初始化(虚拟代理):
- 当一个对象需要很多资源或时间来初始化,但又不希望在启动应用时就初始化它时,可以使用代理模式。代理可以等到真正需要该对象时再创建它。
- 访问远程对象(远程代理):
- 当需要在客户端和远程服务之间进行交互时,可以使用代理作为远程对象的局部代表。代理负责将请求发送到远程服务器,并处理服务器的响应。
- 保护代理:
- 当需要根据请求者的权限控制对对象的访问时,代理可以起到中介的作用,只允许有权限的用户访问。
- 引用计数:
- 代理可以跟踪谁引用了实际对象,从而在没有引用时释放资源。
- 添加额外的功能(装饰器模式的亲戚):
- 当想要增强目标对象的功能,但又不修改其代码时,可以使用代理。代理可以为真实对象的操作提供前后的增强。
- 智能指针:
- 在C++中,代理模式经常被用作智能指针来跟踪对象的引用并避免内存泄漏。
- 网络操作:
- 代理可以控制对网络资源的访问,例如,为网页内容提供缓存或控制对特定内容的访问。
- 代理模式允许您通过在代理中添加额外的功能来保持代码的一致性。这使得代码更加模块化,并使您能够更轻松地维护代码。
- 它允许您使用代理对象来控制对实际对象的访问。这使得代码更加安全,并提供了更好的灵活性和可扩展性。
- 它允许您在不更改实际对象的情况下添加或删除对象。
缺点
- 代理模式可能会导致代码的复杂性增加。这可能会导致代码变得难以理解和维护。
- 它可能会导致应用程序的性能下降,因为它需要生成额外的对象。
适用场景
- 当您需要控制对对象的访问时,代理模式是一种很好的选择。
- 如果您希望在不更改实际对象的情况下添加或删除对象,代理模式是一种很好的选择。
- 如果您希望在访问对象时添加额外的功能,但又不希望更改该对象的代码,代理模式是一种很好的选择。
示例
public interface IImage
{
void Display();
}
public class RealImage : IImage
{
private string _filename;
public RealImage(string filename)
{
_filename = filename;
LoadFromDisk(_filename);
}
public void Display()
{
Console.WriteLine($"Displaying image {_filename}");
}
private void LoadFromDisk(string filename)
{
Console.WriteLine($"Loading {filename} from disk");
}
}
public class ProxyImage : IImage
{
private RealImage _realImage;
private 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()
{
IImage image = new ProxyImage("test.jpg");
image.Display();
}
}
输出结果:
Loading test.jpg from disk
Displaying image test.jpg
责任链模式
责任链模式
定义
责任链模式是一种行为型设计模式,它允许您将请求沿着处理链传递,直到其中一个处理程序处理请求。责任链模式的实现通常涉及一个抽象处理程序类和多个具体处理程序类,这些类按顺序链接在一起以形成处理链。责任链模式通常比使用简单的条件语句更灵活,并且可以更方便地扩展和维护。它还可以使您更好地控制代码的执行流程,并使其更易于理解。
结构
- 抽象处理程序 (Handler):定义了处理请求的接口,并维护一个指向下一个处理程序的引用。
- 具体处理程序 (Concrete Handler):实现处理程序接口,并处理请求。如果不能处理请求,则将请求转发给下一个处理程序。
优点
- 责任链模式使代码更加灵活,并且可以更方便地扩展和维护。它将条件语句转换为对象之间的链接,从而使得代码更加模块化和可重用。
- 它可以使您更好地控制代码的执行流程,并使其更易于理解。它可以使您更好地组织代码,并且可以更方便地进行单元测试。
- 它可以更好地处理复杂的请求,因为它可以将请求分解为多个简单的处理程序。
缺点
- 责任链模式可能会导致请求的处理时间变长,因为请求必须遍历整个链才能得到处理。这可能会导致性能问题,特别是在处理大量请求时。
- 责任链模式可能会使代码变得复杂,特别是在处理复杂的请求时。这可能会导致代码难以理解、调试和维护。
适用场景
- 当您需要处理复杂的请求时,责任链模式是一个很好的选择。
- 如果您需要更好地控制代码的执行流程,并使其更易于理解,责任链模式是一个很好的选择。
- 如果您需要将条件语句转换为对象之间的链接,责任链模式是一个很好的选择。
示例
abstract class Approver
{
protected Approver nextApprover;
public void SetNext(Approver approver)
{
this.nextApprover = approver;
}
public abstract void HandleRequest(int days);
}
class TeamLead : Approver
{
public override void HandleRequest(int days)
{
if (days <= 2)
{
Console.WriteLine($"TeamLead approves leave for {days} days.");
}
else if (nextApprover != null)
{
nextApprover.HandleRequest(days);
}
}
}
class Manager : Approver
{
public override void HandleRequest(int days)
{
if (days <= 5)
{
Console.WriteLine($"Manager approves leave for {days} days.");
}
else if (nextApprover != null)
{
nextApprover.HandleRequest(days);
}
}
}
class Director : Approver
{
public override void HandleRequest(int days)
{
if (days <= 10)
{
Console.WriteLine($"Director approves leave for {days} days.");
}
else
{
Console.WriteLine($"Request for {days} days leave is rejected.");
}
}
}
Approver teamLead = new TeamLead();
Approver manager = new Manager();
Approver director = new Director();
teamLead.SetNext(manager);
manager.SetNext(director);
teamLead.HandleRequest(3); // Output: Manager approves leave for 3 days.
teamLead.HandleRequest(7); // Output: Director approves leave for 7 days.
teamLead.HandleRequest(12); // Output: Request for 12 days leave is rejected.
策略模式
结构
策略接口(Strategy):这是代表所有策略的接口或抽象类。所有的具体策略都要实现这个接口
具体策略(Concrete Strategy):实现了策略接口的具体算法
上下文(Context):持有一个策略对象的引用,提供给客户端调用的接口,以便客户端可以选择和更改具体的策略
案例
案例一
public interface ITransportationStrategy
{
RoutePlan PlanRoute(string start, string destination);
}
public class WalkingStrategy : ITransportationStrategy
{
public RoutePlan PlanRoute(string start, string destination)
{
// Implement walking route planning logic
// Consider factors like pedestrian paths, crosswalks, etc.
return new RoutePlan(/* details here */);
}
}
public class BicyclingStrategy : ITransportationStrategy
{
public RoutePlan PlanRoute(string start, string destination)
{
// Implement bicycling route planning logic
// Consider factors like bike paths, terrain, etc.
return new RoutePlan(/* details here */);
}
}
public class DrivingStrategy : ITransportationStrategy
{
public RoutePlan PlanRoute(string start, string destination)
{
// Implement driving route planning logic
// Consider factors like highways, traffic conditions, etc.
return new RoutePlan(/* details here */);
}
}
public class PublicTransportStrategy : ITransportationStrategy
{
public RoutePlan PlanRoute(string start, string destination)
{
// Implement public transport route planning logic
// Consider factors like bus/train schedules, stations, etc.
return new RoutePlan(/* details here */);
}
}
public class RoutePlanner
{
private ITransportationStrategy _strategy;
public RoutePlanner(ITransportationStrategy strategy)
{
_strategy = strategy;
}
public void SetStrategy(ITransportationStrategy strategy)
{
_strategy = strategy;
}
public RoutePlan GetRoute(string start, string destination)
{
return _strategy.PlanRoute(start, destination);
}
}
RoutePlanner planner = new RoutePlanner(new WalkingStrategy());
RoutePlan walkingRoute = planner.GetRoute("Point A", "Point B");
planner.SetStrategy(new BicyclingStrategy());
RoutePlan bicyclingRoute = planner.GetRoute("Point A", "Point B");
// and so on for other strategies...
命令模式
命令模式
定义
命令模式是一种行为设计模式,它将请求和接收者封装在单个对象中,并允许您通过不同的请求将请求参数化。命令模式的实现通常涉及一个抽象命令类、多个具体命令类和一个调用程序类。抽象命令类定义了一个接口,该接口由具体命令类实现,并调用接收者执行实际工作。调用程序类负责创建命令对象,并将其发送到接收者。命令模式通常用于实现撤消和重做操作,以及实现事务性系统。
结构
- 接收者 (Receiver):执行实际工作的对象。
- 抽象命令 (Command):定义了一个接口,该接口由具体命令类实现。
- 具体命令 (Concrete Command):实现命令接口,并将请求发送到接收者以执行实际工作。
- 调用程序 (Invoker):创建和发送命令对象的对象。
优点
- 命令模式使得代码更加模块化,并将请求和实际工作分离。这使得代码更加灵活,并且更容易扩展和维护。
- 它可以使得您更好地控制代码库,并且可以更轻松地实现撤消和重做操作。
- 它可以实现事务性系统,以确保所有操作都成功或都失败。
缺点
- 命令模式可能会导致代码库中的类数量增加,从而增加了代码复杂性。
- 命令模式可能会使代码变得难以理解,特别是在实现复杂的命令时。
适用场景
- 当您需要将请求和实际工作分离,并将其封装在单个对象中时,命令模式是一种很好的选择。
- 如果您需要实现撤消和重做操作,命令模式是一种很好的选择。
- 如果您需要实现事务性系统,以确保所有操作都成功或都失败,命令模式是一种很好的选择。
示例
// 命令接口
public interface ICommand
{
void Execute();
}
// 接收者
public class Light
{
public void TurnOn()
{
Console.WriteLine("Light is ON");
}
public void TurnOff()
{
Console.WriteLine("Light is OFF");
}
}
// 具体命令
public class LightOnCommand : ICommand
{
private Light _light;
public LightOnCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOn();
}
}
public class LightOffCommand : ICommand
{
private Light _light;
public LightOffCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOff();
}
}
// 请求者
public class RemoteControl
{
private ICommand _command;
public void SetCommand(ICommand command)
{
_command = command;
}
public void PressButton()
{
_command.Execute();
}
}
// 客户端
public class Client
{
public static void Main()
{
// 创建接收者、命令和请求者
Light light = new Light();
ICommand lightOn = new LightOnCommand(light);
ICommand lightOff = new LightOffCommand(light);
RemoteControl remote = new RemoteControl();
// 为遥控器设置命令并执行
remote.SetCommand(lightOn);
remote.PressButton();
remote.SetCommand(lightOff);
remote.PressButton();
}
}
迭代器模式
迭代器模式
定义
迭代器模式是一种行为型设计模式,它提供了一种方法来访问聚合对象,而不暴露其底层表示。迭代器模式通过提供一个通用的接口来遍历聚合对象,从而使得客户端代码不需要知道聚合对象的内部结构。迭代器模式的实现通常涉及一个抽象迭代器和一个具体迭代器,这些迭代器分别实现了抽象迭代器接口以遍历聚合对象中的元素。另外,还有一个聚合对象,它实现了一个 CreateIterator 方法,用于创建具体迭代器。迭代器模式可以使得聚合对象和迭代器对象的设计更加灵活。
结构
- 抽象迭代器 (Iterator):定义了遍历聚合对象所需的接口。
- 具体迭代器 (Concrete Iterator):实现了 Iterator 接口,并维护了遍历过程中的位置。
- 抽象聚合 (Aggregate):定义了创建迭代器对象的接口。
- 具体聚合 (Concrete Aggregate):实现了 Aggregate 接口,并返回一个具体迭代器的实例。
优点
- 迭代器模式可以使得代码更加模块化,因为它允许您将聚合对象和迭代器对象的代码分开。
- 它可以使得聚合对象和迭代器对象的设计更加灵活,并且可以通过更改迭代器对象来影响聚合对象的遍历方式。
- 它可以将遍历算法从聚合对象中分离出来,从而使得聚合对象可以专注于其本身的职责。
缺点
- 迭代器模式可能会使得代码更加复杂,因为它涉及到多个对象和接口。
- 它可能会降低代码的性能,因为迭代器对象需要维护状态,并且需要进行额外的方法调用。
适用场景
- 如果您需要遍历一个聚合对象,并且不想暴露其底层表示,迭代器模式是一个很好的选择。
- 如果您需要针对多个聚合对象使用相同的遍历算法,迭代器模式是一个很好的选择。
- 如果您需要将遍历算法与聚合对象分离出来,迭代器模式是一个很好的选择。
示例
using System;
using System.Collections.Generic;
namespace IteratorPattern
{
// 迭代器接口
public interface IIterator<T>
{
T First();
T Next();
bool HasNext();
}
// 集合接口
public interface ICollection<T>
{
IIterator<T> GetIterator();
}
// 具体集合
public class ConcreteCollection : ICollection<string>
{
private List<string> _items = new List<string>();
public int Count => _items.Count;
public void Add(string item)
{
_items.Add(item);
}
public string this[int index]
{
get { return _items[index]; }
set { _items[index] = value; }
}
public IIterator<string> GetIterator()
{
return new ConcreteIterator(this);
}
}
// 具体迭代器
public class ConcreteIterator : IIterator<string>
{
private readonly ConcreteCollection _collection;
private int _current = 0;
public ConcreteIterator(ConcreteCollection collection)
{
_collection = collection;
}
public string First()
{
return _collection[0];
}
public string Next()
{
if (HasNext())
{
return _collection[_current++];
}
return string.Empty;
}
public bool HasNext()
{
return _current < _collection.Count;
}
}
class Program
{
static void Main()
{
var collection = new ConcreteCollection();
collection.Add("Item 1");
collection.Add("Item 2");
collection.Add("Item 3");
var iterator = collection.GetIterator();
while (iterator.HasNext())
{
Console.WriteLine(iterator.Next());
}
}
}
}
中介者模式
中介者模式
定义
中介者模式是一种行为设计模式,它允许您降低对象之间的耦合性,通过将它们的通信转移至中介者对象来实现。中介者对象充当了对象之间的协调者,从而使对象能够在不显式相互引用的情况下进行通信。中介者模式使得代码更加模块化,并且使得对象更加易于复用和维护。
结构
- 中介者 (Mediator):定义了对象之间通信的接口,并维护一个对象列表,用于管理这些对象。
- 具体中介者 (Concrete Mediator):实现中介者接口,并负责协调对象之间的通信。
- 同事 (Colleague):定义了对象之间通信所需的接口。每个同事都知道中介者对象,并可以通过中介者来与其他同事通信。
- 具体同事 (Concrete Colleague):实现同事接口,并负责与其他同事通信。在需要与其他对象通信时,具体同事对象会将请求发送给中介者对象,然后由中介者对象将请求转发给其他对象。
优点
- 中介者模式可以将对象之间的通信转移至中介者对象,从而降低它们之间的耦合度。这使得代码更加模块化,并且使得对象更加易于复用和维护。
- 它可以使代码更加灵活,因为您可以在运行时更改对象之间的连接方式。
- 它可以使代码更加简单,因为您可以将通信逻辑集中在一个对象中。
缺点
- 中介者模式可能会导致您创建一个过于复杂的中介者对象,而且可能会使得代码难以理解。
- 它可能会导致您在应用程序中引入一个新的单点故障。
适用场景
- 当您需要将对象之间的通信逻辑集中在一个对象中时,中介者模式是一种很好的选择。
- 如果您需要降低对象之间的耦合度,中介者模式是一种很好的选择。
- 如果您需要在运行时更改对象之间的连接方式,中介者模式是一种很好的选择。
示例
// Colleague
public abstract class User
{
protected ChatRoomMediator _mediator;
protected string _name;
public User(ChatRoomMediator mediator, string name)
{
_mediator = mediator;
_name = name;
}
public abstract void Send(string message);
public abstract void Receive(string message);
}
// Concrete Colleague
public class ChatUser : User
{
public ChatUser(ChatRoomMediator 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} received: {message}");
}
}
// Mediator
public interface ChatRoomMediator
{
void SendMessage(string message, User user);
}
// Concrete Mediator
public class ChatRoom : ChatRoomMediator
{
private List<User> _users;
public ChatRoom()
{
_users = new List<User>();
}
public void AddUser(User user)
{
_users.Add(user);
}
public void SendMessage(string message, User user)
{
foreach (var u in _users)
{
// 不要将消息发送给自己
if (u != user)
{
u.Receive(message);
}
}
}
}
// Client
class Program
{
static void Main(string[] args)
{
ChatRoom chatRoom = new ChatRoom();
User alice = new ChatUser(chatRoom, "Alice");
User bob = new ChatUser(chatRoom, "Bob");
chatRoom.AddUser(alice);
chatRoom.AddUser(bob);
alice.Send("Hello, Bob!");
bob.Send("Hi, Alice!");
}
}
备忘录模式
备忘录模式
定义
备忘录模式是一种行为型设计模式,它允许您在不暴露对象实现细节的情况下保存和还原对象之前的状态。备忘录模式的实现通常涉及一个备忘录类、一个发起人类和一个负责人类。发起人类是要被保存状态的对象,而备忘录类是保存状态的对象。负责人类负责协调发起人和备忘录之间的交互,并且通常用于管理备忘录对象。备忘录模式通常与命令模式一起使用,以便在撤销操作中保存对象状态。
结构
- 备忘录 (Memento):存储发起人对象的内部状态的对象。
- 发起人 (Originator):创建备忘录对象以保存其内部状态,并使用备忘录对象还原其内部状态。
- 负责人 (Caretaker):负责管理备忘录对象。它可以存储和检索备忘录对象,并且可以决定何时将备忘录对象返回给发起人。
优点
- 备忘录模式允许您保存和还原对象之前的状态,从而可以轻松地实现撤销操作。它使得对象状态的保存和还原变得更加直观,并且可以减少代码的复杂性和开销。
- 它使得您可以在不暴露对象实现细节的情况下保存和还原对象的状态,从而提高了对象的安全性。
缺点
- 备忘录模式的主要缺点在于它可能会增加代码的复杂性和开销,因为您需要创建和维护备忘录、发起人和负责人类。
- 它可能会导致大量的内存使用,因为备忘录对象可能会占用大量的内存。
适用场景
- 当您需要保存和还原对象之前的状态时,备忘录模式是一个很好的选择。
- 如果您需要实现一个撤销操作,备忘录模式是一个很好的选择。
- 如果您需要在不暴露对象实现细节的情况下保存对象的状态,备忘录模式是一个很好的选择。
示例
using System.Collections.Generic;
// Memento
public class GameMemento
{
public int Level { get; }
public int HealthPoints { get; }
public GameMemento(int level, int healthPoints)
{
Level = level;
HealthPoints = healthPoints;
}
}
// Originator
public class Game
{
public int Level { get; set; }
public int HealthPoints { get; set; }
public GameMemento SaveGame()
{
return new GameMemento(Level, HealthPoints);
}
public void LoadGame(GameMemento memento)
{
Level = memento.Level;
HealthPoints = memento.HealthPoints;
}
}
// Caretaker
public class GameManager
{
private Stack<GameMemento> savedGames = new Stack<GameMemento>();
public void Save(Game game)
{
savedGames.Push(game.SaveGame());
}
public GameMemento Load()
{
return savedGames.Pop();
}
}
// Client code
public class Program
{
public static void Main()
{
Game myGame = new Game { Level = 1, HealthPoints = 100 };
GameManager gameManager = new GameManager();
myGame.Level = 2;
gameManager.Save(myGame);
myGame.Level = 3;
myGame.HealthPoints = 80;
gameManager.Save(myGame);
// Oops, we lost! Let's load the last saved game.
myGame.LoadGame(gameManager.Load());
System.Console.WriteLine($"Loaded Game: Level {myGame.Level}, HealthPoints {myGame.HealthPoints}");
}
}
观察者模式
观察者模式
定义
观察者模式是一种行为型设计模式,它允许您定义一种订阅机制,以便在对象的状态发生更改时通知其他对象。观察者模式的实现通常涉及两种类型的对象:观察者和被观察者。被观察者维护一个观察者列表,并在其状态更改时通知观察者。观察者使用被观察者提供的接口进行注册和注销。当状态更改时,被观察者通常会调用观察者的回调函数,以便它们可以执行适当的操作。
结构
- 被观察者 (Subject):维护一个观察者列表,并在其状态更改时通知观察者。
- 观察者 (Observer):定义了一个更新接口,以便被被观察者通知其状态更改。
- 具体被观察者 (Concrete Subject):实现被观察者接口,并在其状态更改时通知观察者。
- 具体观察者 (Concrete Observer):实现观察者接口,并接收被观察者的通知以便更新自身状态。
优点
- 观察者模式使得对象之间的通信更加灵活和解耦。它将观察者对象从被观察者对象中解耦,从而使得它们可以独立地变化。
- 它允许您动态地添加和删除观察者,从而使得您能够更好地适应不同的需求。
- 它可以使您更好地控制对象之间的交互,并且可以更轻松地实现依赖反转。
缺点
- 观察者模式可能会导致您的代码变得复杂,并且可能会导致性能问题。如果您的应用程序需要高性能,请考虑使用其他模式。
- 它可能会导致观察者对象之间的竞争条件和死锁问题。请确保您的代码是线程安全的,并且避免在观察者之间共享状态。
适用场景
- 如果您需要在对象之间建立松散的耦合关系,并且需要在对象状态更改时通知其他对象,观察者模式是一个很好的选择。
- 如果您需要动态地添加和删除观察者,或者需要适应不同的需求,观察者模式是一个很好的选择。
- 如果您需要更好地控制对象之间的交互,并且需要更轻松地实现依赖反转,观察者模式是一个很好的选择。
示例
using System;
using System.Collections.Generic;
namespace ObserverPattern
{
// 主题
public interface ISubject
{
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObservers();
}
// 观察者
public interface IObserver
{
void Update(string message);
}
// 具体主题
public class ConcreteSubject : ISubject
{
private List<IObserver> _observers = new List<IObserver>();
private string _message;
public void SetMessage(string message)
{
_message = message;
NotifyObservers();
}
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(_message);
}
}
}
// 具体观察者
public class ConcreteObserver : IObserver
{
public void Update(string message)
{
Console.WriteLine($"Observer received: {message}");
}
}
class Program
{
static void Main()
{
ISubject subject = new ConcreteSubject();
IObserver observer1 = new ConcreteObserver();
IObserver observer2 = new ConcreteObserver();
subject.RegisterObserver(observer1);
subject.RegisterObserver(observer2);
subject.SetMessage("Hello, Observers!");
}
}
}
状态模式
状态模式
定义
状态模式是一种行为设计模式,它允许对象在其内部状态发生变化时更改其行为。该模式将状态封装到单独的类中,并将状态转换委托给表示每个状态的对象。这使得对象在运行时似乎更改了它的类。
结构
- 上下文(Context):具有内部状态并将状态特定行为委托给不同具体状态对象的对象。
- 状态(State):定义特定状态行为的抽象类或接口。
- 具体状态(Concrete State):实现 State 接口并定义特定状态行为的类。
优点
- 状态模式通过将状态特定行为分开到单独的类中简化了代码。
- 使状态转换显式且易于理解。
- 它允许轻松添加新状态,而无需修改现有代码。
缺点
- 状态模式可能导致大量的小类,这可能使代码更难以跟踪。
- 该模式还可能使控制流更难以跟踪,因为状态机分布在多个类中。
示例
// 状态
public interface IState
{
void Handle();
}
// 具体状态
public class ConcreteStateA : IState
{
public void Handle()
{
Console.WriteLine("ConcreteStateA.Handle()");
}
}
public class ConcreteStateB : IState
{
public void Handle()
{
Console.WriteLine("ConcreteStateB.Handle()");
}
}
// 上下文
public class Context
{
private IState _state;
public Context(IState state)
{
TransitionTo(state);
}
public void TransitionTo(IState state)
{
Console.WriteLine($"Context: 转换到 {state.GetType().Name} 状态.");
_state = state;
_state.Handle();
}
}
class Program
{
static void Main()
{
var context = new Context(new ConcreteStateA());
context.TransitionTo(new ConcreteStateB());
}
}
输出结果:
Context: 转换到 ConcreteStateA 状态.
ConcreteStateA.Handle()
Context: 转换到 ConcreteStateB 状态.
ConcreteStateB.Handle()
模板模式
模板模式
定义
模板模式是一种行为设计模式,它允许您定义一个算法的骨架,将一些步骤委托给子类完成。模板模式的实现通常涉及一个抽象模板类和多个具体模板类,这些类实现了抽象模板接口以完成算法的每个步骤。具体模板类通常不允许在算法中进行修改,但是可以通过重写父类方法来添加新的操作。模板模式通常用于在不同类之间共享相同的行为,而不需要复制代码,同时还允许子类进行局部更改。
结构
- 抽象模板 (Abstract Template):定义了算法的骨架和每个步骤的抽象方法。它还可以包含一些公共方法。
- 具体模板 (Concrete Template):实现了抽象模板的每个抽象方法,并且可能包含额外的方法。
- 客户端 (Client):创建一个具体模板的对象,然后使用它来完成算法。
优点
- 模板模式允许您定义一个算法的骨架,并将一些步骤委托给子类实现。这使得代码更加模块化,并使得算法的每个步骤更容易定制。
- 它允许您在不同类之间共享相同的行为,而不需要复制代码。这可以减少代码的复杂性,并使其更易于维护。
- 它使得您可以更轻松地添加新的行为,因为您只需要向抽象模板类添加新的抽象方法即可。
缺点
- 模板模式可能会增加代码的复杂性,因为它涉及到多个类之间的交互,并且需要正确地组织它们。
- 它可能会限制子类的自由度,因为它们需要遵循父类定义的算法骨架。
适用场景
- 如果您有几个类需要共享相同的行为,但是实现方式略有不同,则模板模式是一个很好的选择。
- 如果您的应用程序包含一些复杂算法,您希望将其分解为几个步骤,并且希望能够在不同类之间共享步骤,则模板模式是一个很好的选择。
- 如果您希望能够轻松地添加新的行为,并且希望能够重用现有代码,则模板模式是一个很好的选择。
示例
using System;
namespace TemplateMethodPattern
{
// 抽象类
public abstract class Bread
{
public void Make()
{
MixIngredients();
Bake();
Slice();
}
public abstract void MixIngredients();
public abstract void Bake();
public void Slice()
{
Console.WriteLine("Slicing the bread.");
}
}
// 具体类
public class WheatBread : Bread
{
public override void MixIngredients()
{
Console.WriteLine("Gathering ingredients for wheat bread.");
}
public override void Bake()
{
Console.WriteLine("Baking the wheat bread.");
}
}
public class WhiteBread : Bread
{
public override void MixIngredients()
{
Console.WriteLine("Gathering ingredients for white bread.");
}
public override void Bake()
{
Console.WriteLine("Baking the white bread.");
}
}
class Program
{
static void Main()
{
Bread wheatBread = new WheatBread();
wheatBread.Make();
Console.WriteLine("\n");
Bread whiteBread = new WhiteBread();
whiteBread.Make();
}
}
}
访问者模式
访问者模式
定义
访问者模式是一种行为型设计模式,它允许您将算法与其它对象结构分离。访问者模式的实现通常涉及一个访问者类和多个元素类,这些类实现了访问者接口。元素类提供了一个接受访问者的方法,该方法接受访问者对象作为参数,并调用访问者对象中的特定方法。访问者对象可以在不修改元素类的情况下,向元素类添加新的行为。
结构
- 访问者 (Visitor):定义了访问元素对象的接口。
- 具体访问者 (Concrete Visitor):实现了访问者接口的具体类。
- 元素 (Element):定义了接受访问者的接口,通常包含一个接受访问者的方法。
- 具体元素 (Concrete Element):实现了元素接口的具体类。
- 对象结构 (Object Structure):通常由多个具体元素对象组成,并提供了一个迭代方法,以便访问者可以遍历所有元素。
优点
- 访问者模式允许您向现有的类层次结构中添加新的行为,而无需修改这些类。
- 它使得您可以将相关的行为放在同一个类中,从而使代码更加易于理解和维护。
- 它使得您可以在运行时动态地添加新的访问者,从而使您可以更好地满足客户需求。
缺点
- 访问者模式在一定程度上增加了代码的复杂性,并且可能会使代码难以理解和调试。
- 它通常需要您定义多个类,这可能会增加代码的复杂性和开销。
适用场景
- 当您需要向现有的类层次结构中添加新的行为时,访问者模式是一种很好的选择。
- 如果您需要将相关的行为放在同一个类中,从而使代码更加易于理解和维护,访问者模式是一种很好的选择。
- 当您需要在运行时动态地添加新的访问者时,访问者模式是一种很好的选择。
示例
using System;
using System.Collections.Generic;
// Visitor
public interface IVisitor
{
void Visit(ConcreteElementA elementA);
void Visit(ConcreteElementB elementB);
}
// ConcreteVisitor
public class ConcreteVisitor : IVisitor
{
public void Visit(ConcreteElementA elementA)
{
Console.WriteLine("Visited " + elementA.GetType().Name);
}
public void Visit(ConcreteElementB elementB)
{
Console.WriteLine("Visited " + elementB.GetType().Name);
}
}
// Element
public interface IElement
{
void Accept(IVisitor visitor);
}
// ConcreteElementA
public class ConcreteElementA : IElement
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// ConcreteElementB
public class ConcreteElementB : IElement
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// ObjectStructure
public class ObjectStructure
{
private List<IElement> elements = new List<IElement>();
public void Add(IElement element)
{
elements.Add(element);
}
public void Remove(IElement element)
{
elements.Remove(element);
}
public void Accept(IVisitor visitor)
{
foreach (var element in elements)
{
element.Accept(visitor);
}
}
}
// Client
public class Program
{
public static void Main()
{
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.Add(new ConcreteElementA());
objectStructure.Add(new ConcreteElementB());
IVisitor visitor = new ConcreteVisitor();
objectStructure.Accept(visitor);
}
}
解释器模式
解释器模式
定义
解释器模式是一种行为型设计模式,它允许您定义语言的语法,同时提供一种解释器来解释该语言中的句子。解释器模式的实现涉及两个主要组件:抽象语法树和解释器。抽象语法树是句子的抽象表示,而解释器是用于解释该语言中的句子的组件。
结构
- 抽象表达式 (Abstract Expression):定义了一个解释器的接口,该解释器可以解释特定类型的表达式。
- 终端表达式 (Terminal Expression):实现了抽象表达式接口的具体类。它表示语言中的终端符号。
- 非终端表达式 (Nonterminal Expression):实现了抽象表达式接口的具体类。它表示语言中的非终端符号,并且可能包含其他表达式。
- 上下文 (Context):包含了需要被解释的语言的信息。
- 客户端 (Client):创建一个上下文对象,并使用它来解释语言中的句子。
优点
- 解释器模式使得您可以定义语言的语法,并提供一种解释器来解释该语言中的句子。这使得您可以更容易地扩展和修改语言。
- 它使得您可以更轻松地添加新的表达式类型,从而扩展语言的功能。
- 它使得您可以将表达式的解释分布在多个类中,从而使代码更加模块化。
缺点
- 解释器模式可能会增加代码的复杂性,并且可能会使代码难以理解和维护。
- 它可能会降低执行效率,特别是在解释复杂语法时。
适用场景
- 当您需要定义语言的语法,并提供一种解释器来解释该语言中的句子时,解释器模式是一种很好的选择。
- 如果您需要扩展语言,以便可以更轻松地添加新的表达式类型,则解释器模式是一种很好的选择。
- 如果您需要将表达式的解释分布在多个类中,则解释器模式是一种很好的选择。
示例
public interface Expression {
public boolean interpret(String context);
}
public class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data){
this.data = data;
}
@Override
public boolean interpret(String context) {
if(context.contains(data)){
return true;
}
return false;
}
}
public class OrExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
public class AndExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
public class InterpreterPatternDemo {
//规则:Robert 和 John 是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}
//规则:Julie 是一个已婚的女性
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}
public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWoman = getMarriedWomanExpression();
System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julie is a married women? "
+ isMarriedWoman.interpret("Married Julie"));
}
}
建造者模式
建造者模式
定义
建造者模式是一种创建型设计模式,它允许您按照步骤构建复杂对象。建造者模式将构建过程与表示分离,从而使您可以使用相同的构建过程创建不同的表示。
结构
- 产品 (Product):定义了由建造者构建的对象的表示。
- 建造者 (Builder):定义了构建产品的抽象接口。
- 具体建造者 (Concrete Builder):实现了建造者接口,并提供了一种构建产品的具体实现。
- 指挥者 (Director):负责使用建造者对象构建产品。它定义了构建产品的顺序和步骤。
- 客户端 (Client):创建一个指挥者对象并使用它来构建产品。
优点
- 建造者模式允许您按照步骤构建复杂对象,从而使构建过程更加灵活和可控。
- 它将构建过程与表示分离,从而使您可以使用相同的构建过程创建不同的表示。
- 它使得您可以更好地控制对象的构建过程,并且可以避免对象在构建过程中处于不完整的状态。
缺点
- 建造者模式可能会增加代码的复杂性,并且可能会使代码难以理解和维护。
- 它通常需要您定义多个类,这可能会增加代码的复杂性和开销。
适用场景
- 当您需要按照步骤构建复杂对象时,建造者模式是一种很好的选择。
- 如果您需要使用相同的构建过程创建不同的表示,则建造者模式是一种很好的选择。
- 当您需要更好地控制对象的构建过程时,建造者模式是一种很好的选择。
示例
using System;
// Product
class Pizza
{
public string Dough { get; set; }
public string Sauce { get; set; }
public string Topping { get; set; }
public void Display()
{
Console.WriteLine($"Pizza with {Dough} dough, {Sauce} sauce, and {Topping} topping");
}
}
// Builder
interface IPizzaBuilder
{
void BuildDough();
void BuildSauce();
void BuildTopping();
Pizza GetPizza();
}
// ConcreteBuilder
class HawaiianPizzaBuilder : IPizzaBuilder
{
private Pizza _pizza = new Pizza();
public void BuildDough()
{
_pizza.Dough = "thin";
}
public void BuildSauce()
{
_pizza.Sauce = "tomato";
}
public void BuildTopping()
{
_pizza.Topping = "ham and pineapple";
}
public Pizza GetPizza()
{
return _pizza;
}
}
// ConcreteBuilder
class SpicyPizzaBuilder : IPizzaBuilder
{
private Pizza _pizza = new Pizza();
public void BuildDough()
{
_pizza.Dough = "thick";
}
public void BuildSauce()
{
_pizza.Sauce = "spicy tomato";
}
public void BuildTopping()
{
对象的组合模式
WorkNode的设计模式


总结:
模板模式:
优点:
代码重用:将不变的行为移至父类,子类只需实现可变部分,从而减少代码重复
封装:封装了算法的具体步骤和流程
缺点:
限制性:模板方法中定义的算法是静态的,不容易扩展
组合模式
- 统一处理: 组合模式使客户端可以统一对待单个对象和组合对象,从而简化了客户端代码。
- 扩展性: 可以很容易地增加新的组件类型,系统更为灵活。
缺点:
- 设计复杂: 设计过于通用,如果使用不当,可能会导致系统的复杂度增加
思维导图

ui组件的使用优化
发现问题

UI组件维护流程:Panel脚本声明组件、unity编辑器中手动拖拽赋值
Panel脚本声明组件、unity编辑器中手动拖拽赋值
存在的问题
人工维护,手动赋值,工作量大
命名问题,存在脚本声明的组件与编辑器中组件的名称不一致的情况,导致有时候找组件的声明代码或者unity的UI组件慢的问题
panel脚本声明的组件名称与prefab组件的命名不一致
组件命名也没有规范,无法通过命名来知道这个组件是干嘛的
意图:用一个中介对象来封装这一系列的组件交互
解决问题
新建一个代理,统一管理这些组件
进一步思考,为生产代理写一个工具,让代理发挥作用
定义命名规范,可扩展,统一的脚本命名规则,Panel与PanelWrap脚本名称一一对应,Prefab的组件与PanelWrap的组件名称一一对应

可维护,支持重新生成PanelWrap脚本文件,Reset重新绑定赋值组件,不再用手动维护
Panel不需要知道的工作
中介者Wrap怎么来的
调用


模板文件

支持wrap的组件判断的扩展

生成的Wrap文件

Loading Comments...