迈向简洁的旅程:模式(Pattern),我们该如何接纳?

设计方法论”这一理念已经发展成为一个庞大的学术领域。 而我被认为是所谓设计方法论的主要倡导者之一。 我对这一现象感到非常遗憾,并公开声明,我拒绝将设计方法论作为研究对象。 因为我认为将设计实践与设计研究分开是荒谬的。 事实上,那些只研究设计方法论而不进行设计实践的人,几乎总是失败的设计师。 他们缺乏内在的生命力,失去了创造的欲望,或者从一开始就没有这种欲望。(Alexander,1971)

人们常说,设计模式(Design Patterns)是万能解决方案。

在OOP(面向对象编程,Object-Oriented Programming)出现后,GOF(四人组,Gang of Four)的设计模式书问世并成为某种福音书。

许多开发者在阅读模式书籍时,仅看结构图就开始编写代码,结果往往导致僵化的实现。

我们在学习设计方法和模式时,总是在“图式理解”和“实际经验”之间的鸿沟中摇摆不定。

正如亚历山大所说,将设计方法从设计实体中分离出来,最终只会谈论空洞的幻影。

其实,在讨论模式之前,我们可以谈论习语(Idiom)等其他内容,但这个话题留到以后再说。

本文我们将讨论设计模式。

(艾伦·凯2003年的电子邮件故事)

我们今天所讨论的模式是如何产生的?

通常我们认为的面向对象编程(OOP)起源于Simula,

但一般认为第一个提出这个概念的人是艾伦·凯(Alan Kay),他在1967年初使用了这个术语。

然而,艾伦·凯在2003年的采访中表示,

他所说的面向对象编程指的是

消息传递(Messaging)、封装(Encapsulation)、动态绑定(Dynamic Binding)。

但如果这样说,这并不是我们现在所理解的OOP概念。

那么,我们现在所理解的概念是从哪里来的呢?

(我们实际认知中的OOP概念)

目前我们使用的OOP概念是由格雷迪·布奇(Booch)提出的,并在此基础上扩展的。

  1. OOP是一种逻辑构建组件,使用对象而非算法。
  2. 每个对象是某个类的实例。
  3. 类通过继承关系相互关联。

通常我们将其总结为三个要素:

封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)。

这就是我们常说的面向对象编程的起点。

随着这种面向对象编程方式迅速成为主流,一本伟大的编程经典诞生了。

那就是

GOF(四人组,Gang of Four)。

埃里希·伽玛(Erich Gamma)、理查德·赫尔姆(Richard Helm)、拉尔夫·约翰逊(Ralph Johnson)和约翰·弗利西德斯(John Vlissides)

撰写的《设计模式》成为了现代编程的经典之作。

GOF展示了23种设计模式,并将其分为三种类型:

三种类型:

创建型模式(Creational Pattern)- 结构型模式(Structural Pattern)- 行为型模式(Behavioral Pattern)

创建型模式(Creational Pattern)

  • 与对象创建相关的模式。
  • 提高对象创建过程的灵活性,便于代码维护。

示例:单例模式(Singleton Pattern)、抽象工厂模式(Abstract Factory Pattern)、建造者模式(Builder Pattern)、工厂方法模式(Factory Method Pattern)、原型模式(Prototype Pattern)。

结构型模式(Structural Pattern)

  • 与程序结构相关的模式。
  • 设计程序中的数据结构或接口结构。
  • 通过组合类或对象形成更大的结构。

示例:适配器模式(Adapter Pattern)、桥接模式(Bridge Pattern)、组合模式(Composite Pattern)、装饰器模式(Decorator Pattern)、外观模式(Facade Pattern)、享元模式(Flyweight Pattern)、代理模式(Proxy Pattern)。

行为型模式(Behavioral Pattern)

  • 将对象间的重复交互模式化。
  • 关注对象间算法或责任分配。
  • 最小化耦合度(解耦)。

示例:命令模式(Command Pattern)、解释器模式(Interpreter Pattern)、迭代器模式(Iterator Pattern)、中介者模式(Mediator Pattern)、备忘录模式(Memento Pattern)、观察者模式(Observer Pattern)、状态模式(State Pattern)、策略模式(Strategy Pattern)、模板方法模式(Template Method Pattern)、访问者模式(Visitor Pattern)。

这些例子引起了爆炸性的流行。

它们成为了OOP中解决问题的一种模板。

然而,随着这些模式的流行,滥用模式的问题也随之而来,

很多人开始把这些模式当作“公理”,而不是“前辈程序员的经验产物”。

本文旨在强调,编程模式不是“公理”,

而是“前辈程序员创造的通用工具”的观点。

了解模式确实可以增加应对问题的选择,

但重要的是如何解决当前面临的问题,

而不是死记硬背模式目录。

利用模式进行重构,

乔舒亚的名著中也提到以下内容:

“模式的结构图只是示例,而不是规范。它描述了我们最常见的实现方式。
因此,结构图很可能与你自己的实现方式有许多共同点,
但差异是不可避免的,甚至是可取的。至少你会根据你的领域更改参与者的名称。
在调整实现上的优缺点后,你的实现方式可能与结构图有很大不同。”
——
John Vlissides

这是GOF作者之一约翰·弗利西德斯的话。

这句话的重点是什么?

也就是说,即使是同一个模式,也可以用多种方式实现。

让我们简单举个例子。

// 策略接口
interface IDiscountStrategy
{
    double ApplyDiscount(double price);
}

// 具体策略类 1
class FixedDiscountStrategy : IDiscountStrategy
{
    private double discountAmount;
    public FixedDiscountStrategy(double amount)
    {
        discountAmount = amount;
    }
    public double ApplyDiscount(double price)
    {
        return price - discountAmount;
    }
}

// 具体策略类 2
class PercentageDiscountStrategy : IDiscountStrategy
{
    private double discountRate;
    public PercentageDiscountStrategy(double rate)
    {
        discountRate = rate;
    }
    public double ApplyDiscount(double price)
    {
        return price * (1 - discountRate);
    }
}

// 上下文类
class ShoppingCart
{
    private IDiscountStrategy discountStrategy;

    public ShoppingCart(IDiscountStrategy strategy)
    {
        discountStrategy = strategy;
    }

    public double CalculateTotalPrice(double originalPrice)
    {
        return discountStrategy.ApplyDiscount(originalPrice);
    }
}

// 使用示例
ShoppingCart cart1 = new ShoppingCart(new FixedDiscountStrategy(1000));
ShoppingCart cart2 = new ShoppingCart(new PercentageDiscountStrategy(0.1));

Console.WriteLine($"Fixed Discount: {cart1.CalculateTotalPrice(10000)}"); // 固定折扣:9000
Console.WriteLine($"Percentage Discount: {cart2.CalculateTotalPrice(10000)}"); // 百分比折扣:9000

常见的策略模式(Strategy Pattern)。

让我们尝试应用Lambda表达式。

这样就不需要定义接口和具体实现类。

// 折扣策略委托(函数类型定义)
delegate double DiscountStrategy(double price);

// 上下文类
class ShoppingCart
{
    private DiscountStrategy discountStrategy;

    public ShoppingCart(DiscountStrategy strategy)
    {
        discountStrategy = strategy;
    }

    public double CalculateTotalPrice(double originalPrice)
    {
        return discountStrategy(originalPrice);
    }
}

// 使用示例(使用Lambda表达式直接定义策略)
ShoppingCart cart1 = new ShoppingCart(price => price - 1000); // 固定折扣策略(Lambda表达式)
ShoppingCart cart2 = new ShoppingCart(price => price * 0.9);   // 百分比折扣策略(Lambda表达式)

Console.WriteLine($"Fixed Discount (Lambda): {cart1.CalculateTotalPrice(10000)}"); // 固定折扣(Lambda):9000
Console.WriteLine($"Percentage Discount (Lambda): {cart2.CalculateTotalPrice(10000)}"); // 百分比折扣(Lambda):9000

代码量显著减少,各种策略可以用Lambda表达式轻松定义,
并且可以根据需要更加灵活地动态更改策略!

接下来,让我们尝试用Lambda表达式/方法引用(Method Reference)来实现命令模式(Command Pattern)。

// 命令接口
interface ICommand
{
    void Execute();
}

// 具体命令类 1
class LightOnCommand : ICommand
{
    private Light light;
    public LightOnCommand(Light light)
    {
        this.light = light;
    }
    public void Execute()
    {
        light.TurnOn();
    }
}

// 具体命令类 2
class LightOffCommand : ICommand
{
    private Light light;
    public LightOffCommand(Light light)
    {
        this.light = light;
    }
    public void Execute()
    {
        light.TurnOff();
    }
}

// 接收者类
class Light
{
    public void TurnOn() { Console.WriteLine("Light On"); }
    public void TurnOff() { Console.WriteLine("Light Off"); }
}

// 调用者类
class RemoteControl
{
    private ICommand onCommand;
    private ICommand offCommand;

    public RemoteControl(ICommand on, ICommand off)
    {
        this.onCommand = on;
        this.offCommand = off;
    }

    public void PressOnButton() { onCommand.Execute(); }
    public void PressOffButton() { offCommand.Execute(); }
}

// 使用示例
Light livingRoomLight = new Light();
RemoteControl remote = new RemoteControl(new LightOnCommand(livingRoomLight), new LightOffCommand(livingRoomLight));

remote.PressOnButton();  // Light On
remote.PressOffButton(); // Light Off
// 命令委托 (void 返回类型,无参数)
delegate void Command();

// 接收者类 (同上)
class Light
{
    public void TurnOn() { Console.WriteLine("Light On"); }
    public void TurnOff() { Console.WriteLine("Light Off"); }
}

// 调用者类
class RemoteControl
{
    private Command onCommand;
    private Command offCommand;

    public RemoteControl(Command on, Command off)
    {
        this.onCommand = on;
        this.offCommand = off;
    }

    public void PressOnButton() { onCommand(); }
    public void PressOffButton() { offCommand(); }
}

// 使用示例 (使用 Lambda 表达式、方法引用直接定义命令)
Light livingRoomLight = new Light();
RemoteControl remote = new RemoteControl(
    livingRoomLight.TurnOn,  // 方法引用 (替代 LightOnCommand)
    () => livingRoomLight.TurnOff() // Lambda 表达式 (替代 LightOffCommand)
);

remote.PressOnButton();  // Light On
remote.PressOffButton(); // Light Off

代码量显著减少,各种策略可以用Lambda表达式轻松定义,
并且可以根据需要更加灵活地动态更改策略!

接下来,让我们尝试用Lambda表达式/方法引用(Method Reference)来实现命令模式(Command Pattern)。

代码行数再次减少,各种命令可以通过方法引用轻松定义,
并且可以根据需要动态更改命令,变得更加灵活!

现在让我们实现工厂模式(Factory Pattern)。

// 产品接口
interface IProduct
{
    void DoSomething();
}

// 具体产品类 1
class ConcreteProductA : IProduct
{
    public void DoSomething() { Console.WriteLine("Product A"); }
}

// 具体产品类 2
class ConcreteProductB : IProduct
{
    public void DoSomething() { Console.WriteLine("Product B"); }
}

// 工厂接口
interface IFactory
{
    IProduct CreateProduct();
}

// 具体工厂类 1
class ConcreteFactoryA : IFactory
{
    public IProduct CreateProduct() { return new ConcreteProductA(); }
}

// 具体工厂类 2
class ConcreteFactoryB : IFactory
{
    public IProduct CreateProduct() { return new ConcreteProductB(); }
}

// 客户端代码
class Client
{
    public void UseProduct(IFactory factory)
    {
        IProduct product = factory.CreateProduct();
        product.DoSomething();
    }
}

// 使用示例
Client client = new Client();
client.UseProduct(new ConcreteFactoryA()); // Product A
client.UseProduct(new ConcreteFactoryB()); // Product B

(典型的工厂模式)

// 产品接口 (同上)
interface IProduct
{
    void DoSomething();
}

// 具体产品类 (同上)
class ConcreteProductA : IProduct
{
    public void DoSomething() { Console.WriteLine("Product A"); }
}

class ConcreteProductB : IProduct
{
    public void DoSomething() { Console.WriteLine("Product B"); }
}

// 工厂委托 (返回产品接口,无参数)
delegate IProduct ProductFactory();

// 工厂字典
class ProductFactoryDictionary
{
    private Dictionary<string, ProductFactory> factories = new Dictionary<string, ProductFactory>();

    public void RegisterFactory(string productName, ProductFactory factory)
    {
        factories.Add(productName, factory);
    }

    public IProduct CreateProduct(string productName)
    {
        if (factories.ContainsKey(productName))
        {
            return factories[productName]();
        }
        return null; // 或者异常处理
    }
}

// 客户端代码
class Client
{
    public void UseProduct(ProductFactoryDictionary factoryDict, string productName)
    {
        IProduct product = factoryDict.CreateProduct(productName);
        if (product != null)
        {
            product.DoSomething();
        }
    }
}

// 使用示例 (使用 Lambda 表达式注册工厂)
ProductFactoryDictionary factoryDict = new ProductFactoryDictionary();
factoryDict.RegisterFactory("A", () => new ConcreteProductA()); // 使用 Lambda 表达式注册工厂
factoryDict.RegisterFactory("B", () => new ConcreteProductB()); // 使用 Lambda 表达式注册工厂

Client client = new Client();
client.UseProduct(factoryDict, "A"); // Product A
client.UseProduct(factoryDict, "B"); // Product B

使用Lambda表达式和字典实现

使用字典可以在运行时动态注册和管理工厂,便于从配置文件或外部数据源读取工厂信息。

可以说变得更加灵活了。

看到这里,有人可能会问:

“你是想说使用Lambda表达式的方法就是正确答案吗?”

不。

使用Lambda表达式本质上是在传统的OOP中临时引入FP(函数式编程)语法糖。

有些人可能会说应该使用FP,

但这只是表明除了OOP之外,还可以使用其他“范式”来解决问题。

但这并不是我想表达的本质。

使用Lambda表达式也会带来另一个问题:

“容易破坏团队代码的一致性。”

在编写代码时,我们通常遵循软件规则。

这意味着我们要遵循编码规范和代码指南,而团队代码的基本原则是

“看起来像一个人写的”才是正确编写的代码。

但如果开始使用Lambda表达式,为了保持代码一致性,其余部分也需要改用Lambda表达式,这就带来了问题。

此外,FP并不总是正确的答案。实际上,FP会增加内存使用量,带来不必要的开销。
(当然,在异步编程中使用FP是防止数据竞争的好方法,但这不是我们现在讨论的重点。)

实际上,在OOP中,一些模式也在进化为更好的模式。

例如,曾经使用单例模式(Singleton Pattern)的部分场景被服务定位器模式(Service Locator Pattern)取代。
(不过,单例模式和服务定位器模式在某些情况下都被视为反模式,因此依赖注入的方式也被广泛采用。)

还有,建造者模式(Builder Pattern)也出现了Fluent Builder这样的变体。

这些都是通过单纯记忆模式无法得出的改进。

因此,我想说的是:

“始终要在团队约定下追求更好的答案。”

在学习模式的过程中,沉迷于模式可能是不可避免的。
事实上,我们大多数人都是通过错误来学习的。我也曾多次沉迷于模式。

模式真正的乐趣在于明智地使用它们。
重构通过消除重复、简化代码以及清晰传达代码意图,帮助我们做到这一点。
当模式通过重构自然融入系统时,过度设计的风险就会降低。
随着重构能力的提高,发现模式真正乐趣的可能性也会增加。

模式的真正乐趣在于明智地使用它们。

大家在使用模式之前,不妨再思考一下?

也许你会发现属于自己的新方法。

虽然这只是我的个人观点,

但我认为学习与实践可以提高人生的分辨率。

随着新知识的积累,我们会逐渐认识周围的事物,

并对它们的意义有更深的理解。

我们的未来充满了不确定,但如果回头看时,风景以高分辨率呈现且美丽,那便已足够有趣,不是吗?

就此结束这篇文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值