我们在做软件开发时,经常会使用设计模式,比如单例模式、工厂模式、观察者模式等。这些设计模式帮助我们更好地组织代码,提高代码的可维护性和可扩展性。
但是这次我们换一个角度,来看一看框架本身有没有体现出这些设计模式。希望这篇文章可以给大家一个不一样的视角。
观察者模式 ¶
对于观察者模式,相信大多数人可能首先会想到的是在 MVVM 模式下,ViewModel 作为数据的提供者,而 View 作为数据的消费者。当 ViewModel 中的数据发生变化时,它会通知所有绑定的 View 进行更新(具体做法为实现 INotifyPropertyChanged
接口,并触发相应的事件)。这种模式使得 View 和 ViewModel 之间的交互变得简单而高效。
但实际上,原生 WPF 就已经充分体现出了观察者模式。对于大多数控件,本身它就提供了两种实现了观察者模式,或者说可以被观察的特性:
- 事件:控件的事件机制允许开发者注册事件处理程序,从而在特定事件发生时接收通知。例如,当用户点击按钮时,按钮会触发 Click 事件;当用户修改文本框的内容时,文本框会触发 TextChanged 事件。
- 依赖属性:控件的依赖属性发生变化时,如果有绑定的目标,它会自动通知这些目标进行更新。这种机制使得控件的状态变化能够被及时“观察”到,从而实现了观察者模式的效果。
所以,在 WPF 开发中,观察者模式并不仅仅体现在 MVVM 模式上,它在控件的事件和依赖属性中也得到了充分的体现。
桥接模式 ¶
我们简单回顾一下桥接模式大概是怎么一回事:
如果我们的某一个类存在多个维度的变化(比如不同的外观和不同的行为),比如图形类可能有不同的形状(Shape)以及颜色(Color),那么假如我们采用传统的 OOP 的思想,就不可避免地会引入大量的子类,比如 RedRectangle
、BlueCircle
等,这显然是不理想的。
此时,我们就可以使用桥接模式将这两个维度分离开来。这样一来,我们就可以独立地对这两个维度进行扩展,而不必相互影响。
这时候我们回来看 WPF,比如 Control
类就包含了多种不同的实现,包括 Button
、Label
、TextBox
等;同时,它们又都包含 Background
、Foreground
等属性,用于控制它们实际的外观。这是否就和我们上面的例子不谋而合了?
所以,WPF 的 Control
类就是一个典型的桥接模式的实现。它将控件的外观(如样式、模板)和行为(如事件、命令)分离开来,使得我们可以独立地对这两个维度进行扩展。当然,体现桥接模式的不仅仅是 Control
类,其他很多控件也都遵循了这一模式。
装饰器模式 ¶
装饰器模式(Decorator Pattern)是一种结构性设计模式,它允许在不改变对象自身的情况下,动态地给对象添加一些额外的职责。装饰器模式通常用于遵循开闭原则(对扩展开放,对修改关闭)。
在 WPF 中,装饰器模式的一个典型应用就是附加属性和行为了。通过附加属性,我们可以在不修改原有控件的情况下,为其添加新的功能。例如,我们可以为一个 TextBox
控件添加一个附加属性,用于控制其是否显示占位符文本。
|
|
在这个例子中,TextBoxHelper
就是一个提供了附加属性的类,它可以为任意 TextBox
控件添加占位符文本(Placeholder
)属性。然后我们就可以在 TextBox
的 Style
或 Template
中响应这个附加属性,从而真的为文本框添加占位符。
行为也类似,而且行为本质上也是附加属性,或者说就是依靠附加属性来实现的。所以这里不再赘述。
所以我们可以说,附加属性利用了装饰器模式。不仅如此,其实它还体现出了其他一些设计模式,比如我们开头提到的观察者模式,此外还有享元模式(附加属性与依赖属性为同一类型的控件提供了相同的属性元数据)、中介者模式(比如一些与布局相关的附加属性,如 DockPanel.Dock
、Grid.Row
等)等等。
适配器模式 ¶
在 WPF 的绑定中,我们常常会利用到值转换器(ValueConverter
),它就是适配器模式的典型体现。
这里我们看一个最典型的例子:我们要将一个布尔属性绑定到控件的 Visibility
属性上。为了能够实现源类型(bool
)到目标类型(Visibility
)的转换,最常见的方式就是借助 BooleanToVisibilityConverter
了:
|
|
另外,值转换器不仅体现出了适配器模式,还体现出了一定程度的策略模式。它们把“如何将一个值转换成另一个值”的算法抽象成一个接口(IValueConverter
),并通过不同的实现类来提供具体的转换逻辑,这正是策略模式的核心思想——把一系列可互换的算法封装为独立的策略对象,并在运行时根据需要选择使用哪一个策略。
责任链模式 ¶
前面我们提到,控件的事件体现出了观察者模式。其实不仅如此,WPF 在传统 C# 事件的基础上,还引入了路由事件(RoutedEvent
)这一概念。
路由事件允许事件在控件的视觉树中沿着特定的路径进行传播,这种传播机制使得我们可以在父级控件中处理子级控件的事件,或者反过来。具体要看路由的方式是冒泡(Bubble)还是隧道(Tunnel)。
但不管哪一种方式,我们都可以在事件的处理过程中形成一个责任链。对于这一点,最明显的体现方式就是对于 e.Handled
的使用。比如我们触发了一个鼠标事件,并且希望在某个父级控件中处理这个事件,那么我们可以在到达该控件时将 e.Handled
设置为 true
,从而阻止事件继续向上传播。
|
|
这一操作就和责任链模式不谋而合:我们可以将事件的处理过程看作是一个链条,每个处理节点都可以选择是否将事件继续传递下去,从而形成一个灵活的事件处理机制。
其他设计模式 ¶
除了上面这些典型的例子外,WPF 还体现出了很多其他的设计模式,比如:
- 状态模式:控件的视觉状态(如鼠标悬停、按下等)可以通过 VisualStateManager 进行管理,这实际上就是一种状态模式的应用。
- 组合模式:WPF 的控件树结构使得我们可以将多个控件组合成一个复合控件,这正是组合模式的体现。
- 单例模式:
Application.Current
就是一个单例模式的实现。 - 原型模式:WPF 中资源的
Freezable
以及Style
被多个控件使用,都体现出了对于原型实例的“克隆”。
总结 ¶
在软件开发中,设计模式为我们提供了高效、灵活的解决方案,帮助提升代码的可维护性和可扩展性。本文通过分析 WPF 中的几个经典设计模式,如观察者模式、桥接模式、装饰器模式、适配器模式和责任链模式,展示了这些模式如何在 WPF 框架中得以体现。
通过控件的事件机制、依赖属性、附加属性等机制,WPF 为开发者提供了丰富的设计模式支持,帮助开发者更好地组织和扩展应用程序。除此之外,WPF 还体现了状态模式、组合模式、单例模式等其他设计模式,为开发者提供了多种优秀的架构思想。