Asp.net设计模式笔记之一:理解设计模式
GOF设计模式著作中的23种设计模式可以分成三组:创建型(Creational),结构型(Structural),行为型(Behavioral)。下面来做详细的剖析。
创建型
创建型模式处理对象构造和引用。他们将对象实例的实例化责任从客户代码中抽象出来,从而让代码保持松散耦合,将创建复杂对象的责任放在一个地方,这遵循了单一责任原则和分离关注点原则。
下面是“创建型”分组中的模式:
1.Abstract Factory(抽象工厂)模式:提供一个接口来创建一组相关的对象。
2.Factory Method(工厂方法)模式:支持使用一个类来委托创建有效对象的责任。
3.Builder(生成器)模式:将对象本身的构造分离出来,从而能够构造对象的不同版本。
4.Prototype(原型)模式:能够从一个原型实例来复制或克隆类,而不是创建新实例。
5.Singleton(单例)模式:支持一个类只实例化一次,并只有一个可用来访问它的全局访问点。
结构型
结构型模式处理对象的组合与关系,以满足大型系统的需要。
下面是“结构型”分组中的模式:
1.Adapter(适配器)模式:使不兼容接口的类能够一起使用。
2.Bridge(桥接)模式:将抽象与其实现分离,允许实现和抽象彼此独立地改变。
3.Composite(组合)模式:可以像对待对象的单个实例那样来对待一组表示层次结构的对象。
4.Decorator(装饰)模式:能够动态包装一个类并扩展其行为。
5.Facade(门面)模式:提供一个简单的接口并控制对一组复杂接口和子系统的访问。
6.Flyweight(享元)模式:提供一种在许多小类之间高效共享数据的方式。
7.Proxy(代理)模式:为一个实例化成本很高的更复杂的类提供一个占位符。
行为型
行为型模式处理对象之间在责任和算法方面的通信。这个分组中的模式将复杂行为封装起来并将其从系统控制流中抽象出来,这样就使复杂系统更容易理解和维护。
下面是”行为型“分组中的模式:
1.Chain Of Responsibility(责任链)模式:允许将命令动态链接起来处理请求。
2.Command(命令)模式:将一个方法封装成一个对象,并将该命令的执行与它的调用者分离。
3.Interpreter(解释器)模式:指定如何执行某种语言中的语句。
4.Iterator(迭代器)模式:提供以形式化的方式来导航集合的方法。
5.Mediator(中介者)模式:定义一个对象,可以让其他两个对象进行通信而不必让它们知道彼此。
6.Memento(备忘录)模式:允许将对象恢复到以前的状态。
7.Observer(观察者)模式:定义一个或多个类在另一个类发生变化时接到报警。
8.State(状态)模式:允许对象通过委托给独立的,可改变的状态对象来改变自己的行为。
9.Strategy(策略)模式:能够将算法封装到一个类中并在运行时转换,以改变对象的行为。
10.Template Method(模板方法)模式:定义算法流程控制,但允许子类重写或实现执行步骤。
11.Vistor(访问者)模式:能够在类上执行新的功能而不影响类的结构。
上面介绍了众多的设计模式及其分组。但是如何来选择和运用呢?下面有一些需要注意的事项:
1.在不了解模式的情况下不能运用他们。
2.在设计的时候,要衡量是否有必要引入设计模式的复杂性。最好能衡量下实现某种模式所需的时间与该模式能够带来的效益。谨记KISS原则:保持简单浅显。
3.将问题泛化,以更抽象的方式识别正在处理的问题。设计模式是高层次的解决方案,试着把问题抽象,而且不要过于关注具体问题的细节。
4.了解具有类似性质的模式以及同组中的其他模式。以前已经使用过某个模式并不意味着在解决问题时它总是正确的模式选择。
5.封装变化的部分。了解应用程序中什么可能发生变化。如果知道某个特殊的报价折扣算法将随时间发生变化,那么寻找一种模式来帮助您在不影响应用程序其余部分的情况下改变该算法。
6.在选择好设计模式之后,确保在命名解决方案中的参与者时使用该模式的语言及领域语言。例如,如果正在使用策略模式为不同的快递公司计价提供解决方案,那么相应地为他们明明,如FedExShippingCostStrategy。通过组合使用模式的公共词汇表和领域语言,会让代码更具可读性,而且更能够让其他具备模式知识的开发者理解。
就设计模式而言,除了学习之外没有其他替代方法。对每种设计模式了解得越多,在运用他们时就会准备的更好。当遇到一个问题正在寻找解决方案时,扫描一下每种模式的目的,唤起自己的记忆。
一种很好地学习方法就是试着识别.net框架中的模式,比如:Asp.net Cache使用了Singleton模式,在创建新的Guid实例时使用了Factory Method模式,.Net 2 xml类使用Factory Method模式,而1.0版并没有使用。
下面我们以一个快速模式示例来进行讲解,以便于加深映像。
新建一个类库项目0617.DaemonPattern.Service,然后引用System.web程序集。
首先添加一个Product.cs的空类作为我们的Model:
public class Product
{ }
然后添加ProductRepository.cs类作为我们的数据存储仓库,从这里我们可以从数据库获取数据实体对象:
public class ProductRepository
{
public IList<Product> GetAllProductsIn(int categoryId)
{
var products = new List<Product>();
//Database operation to populate products.
return products;
}
}
最后添加一个名称为ProductService.cs的类,代码如下:
public class ProductService
{
public ProductService()
{
this.productRepository = new ProductRepository();
} private ProductRepository productRepository; public IList<Product> GetAllProductsIn(int categoryId)
{
IList<Product> products;
string storageKey = string.Format("products_in_category_id_{0}", categoryId);
products = (List<Product>)HttpContext.Current.Cache.Get(storageKey);
if (products == null)
{
products = productRepository.GetAllProductsIn(categoryId);
HttpContext.Current.Cache.Insert(storageKey, products);
}
return products;
}
}
从代码的逻辑,我们可以清楚的看到,ProductService通过ProductRepository仓库从数据库获取数据。
这个类库带来的问题有以下几点:
1.ProductService依赖于ProductRepository类。如果ProductRepository类中的API发生改变,就需要在ProductService类中进行修改。
2.代码不可测试。如果不让真正的ProductRepository类连接到真正的数据库,就不能测试ProductService的方法,因为这两个类之间存在着紧密耦合。另一个与测试有关的问题是,该代码依赖于使用Http上下文来缓存商品。很难测试这种与Http上下文紧密耦合的代码。
3.被迫使用Http上下文来缓存。在当前状态,若使用Velocity或Memcached之类的缓存存储提供者,则需要修改ProductService类以及所有其他使用缓存的类。Verlocity和Memcached都是分布式内存对象缓存系统,可以用来替代Asp.net的默认缓存机制。
随意,综上看来,代码耦合度过高,不易进行测试,同时也不易进行替换。
既然知道了存在的问题,那么就让我们来对其进行重构。
首先,考虑到ProductService类依赖于ProductRepository类的问题。在当前状态中,ProductService类非常脆弱,如果ProductRepository类的API改变,就需要修改ProductService类。这破坏了分离关注点和单一职责原则。
1.依赖倒置原则(依赖抽象而不要依赖具体)
可以通过依赖倒置原则来解耦ProductService类和ProductRepository类,让它们都依赖于抽象:接口。
在ProductRepository类上面右击,选择“重构”->“提取接口”选项,会自动给我们生成一个IProductRepository.cs类:
public interface IProductRepository
{
IList<Product> GetAllProductsIn(int categoryId);
}
修改现有的ProductRepository类,以实现新创建的接口,代码如下:
public class ProductRepository : IProductRepository
{
public IList<Product> GetAllProductsIn(int categoryId)
{
var products = new List<Product>();
//Database operation to populate products.
return products;
}
}
之后更新ProductService类,以确保它引用的是接口而非具体:
public class ProductService
{
public ProductService()
{
this.productRepository = new ProductRepository();
} private IProductRepository productRepository; public IList<Product> GetAllProductsIn(int categoryId)
{
IList<Product> products;
string storageKey = string.Format("products_in_category_id_{0}", categoryId);
products = (List<Product>)HttpContext.Current.Cache.Get(storageKey);
if (products == null)
{
products = productRepository.GetAllProductsIn(categoryId);
HttpContext.Current.Cache.Insert(storageKey, products);
}
return products;
}
}
这样修改之后,ProductService类现在只依赖于抽象而不是具体的实现,这意味着ProductService类完全不知道任何实现,从而确保它不是那么容易的被破坏掉,而且代码在整体上说来对变化更有弹性。
但是,这里还有个问题,既是ProductService类仍然负责创建具体的实现。而且目前在没有有效的ProductRepository类的情况下不可能测试代码。所以这里我们需要引入另一个设计原则来解决这个问题:依赖注入原则。
由于ProductService类仍然与ProductRepository的具体实现绑定在了一起,通过依赖注入原则,我们可以将这一过程移到外部进行,具体方法就是通过该类的构造器将其注入:
public class ProductService
{
public ProductService(IProductRepository productRepository)
{
this.productRepository = productRepository;
} private IProductRepository productRepository; public IList<Product> GetAllProductsIn(int categoryId)
{
IList<Product> products;
string storageKey = string.Format("products_in_category_id_{0}", categoryId);
products = (List<Product>)HttpContext.Current.Cache.Get(storageKey);
if (products == null)
{
products = productRepository.GetAllProductsIn(categoryId);
HttpContext.Current.Cache.Insert(storageKey, products);
}
return products;
}
}
这样就可以在测试期间向ProductService类传递替代者,从而能够孤立地测试ProductService类。通过把获取依赖的责任从ProductService类中移除,能够确保ProductService类遵循单一职责原则:它现在只关心如何协调从缓存或资源库中检索数据,而不是创建具体的IProductRepository实现。
依赖注入有三种形式:构造器,方法以及属性。我们这里只是使用了构造器注入。
当然,现在的代码看上去基本没问题了,但是一旦替换缓存机制的话,将会是一个比较棘手的问题,因为基于Http上下文的缓存没有被封装,替换其需要对当前类进行修改。这破坏了开放封闭原则:对扩展开放,对修改关闭。
由于Adapter(适配器)模式主要用来将一个类的某个转换成一个兼容的接口,所以在当前的例子中,我们可以将HttpContext缓存API修改成想要使用的兼容API。然后可以使用依赖注入原则,通过一个接口将缓存API注入到ProductService类。
这里我们创建一个名为ICacheStorage的新街口,它包含有如下契约:
public interface ICacheStorage
{
void Remove(string key);
void Store(string key, object data);
T Retrieve<T>(string key);
}
在ProductService类中,我们就可以将其取代基于HttpContext的缓存实例:
public class ProductService
{
public ProductService(IProductRepository productRepository,ICacheStorage cacheStroage)
{
this.productRepository = productRepository;
this.cacheStroage = cacheStroage;
} private IProductRepository productRepository;
private ICacheStorage cacheStroage; public IList<Product> GetAllProductsIn(int categoryId)
{
IList<Product> products;
string storageKey = string.Format("products_in_category_id_{0}", categoryId);
products = cacheStroage.Retrieve<List<Product>>(storageKey);
if (products == null)
{
products = productRepository.GetAllProductsIn(categoryId);
cacheStroage.Store(storageKey, products);
}
return products;
}
}
而具体的缓存类我们可以继承自ICacheStorage来实现:
public class HttpCacheAdapterStorage:ICacheStorage
{
public void Remove(string key)
{
if (HttpContext.Current.Cache[key] != null)
HttpContext.Current.Cache.Remove(key);
} public void Store(string key, object data)
{
if (HttpContext.Current.Cache[key] != null)
HttpContext.Current.Cache.Remove(key);
HttpContext.Current.Cache.Insert(key,data);
} public T Retrieve<T>(string key)
{
if(HttpContext.Current.Cache[key]!=null)
return (T)HttpContext.Current.Cache[key];
return default(T);
}
}
现在再回头看看,我们解决了开始列举的种种问题,使得代码更加容易测试,更易读,更易懂。
下面是Adapter(适配器)模式的UML图示:
从图中可以看出,客户有一个对抽象(Target)的引用。在这里,该抽象就是ICacheStorage接口。Adapter是Target接口的一个实现,它只是将Operation方法委托给Adaptee类,这里的Adapter类就是指我们的HttpCacheStorage类,而Adaptee类则是指HttpContext.Current.Cache提供的具体操作方法。
具体的描述如下:
这样,当我们切换到Memcached,抑或是MS Velocity的时候,只需要创建一个Adapter,让ProductService类与该缓存存储提供者通过公共的ICacheStorage接口交互即可。
从这里我们知道:
Adapter模式非常简单,它唯一的作用就是让具有不兼容接口的类能够在一起工作。
由于Adapter模式并不是唯一能够帮助处理缓存数据的模式,下面的章节将会研究Proxy设计模式如何来帮助解决缓存问题的。
在这里,我们还有最后一个问题没有解决,就是在当前设计中,为了使用ProductService类,总是不得不为构造器提供ICacheStorage实现,但是如果不希望缓存数据呢? 一种做法是提供一个null引用,但是这意味着需要检查空的ICacheStorage实现从而弄乱代码,更好的方式则是使用NullObject模式来处理这种特殊情况。
Null Object(空对象模式,有时也被称为特殊情况模式)也是一种极为简单的模式。当不希望指定或不能指定某个类的有效实例而且不希望到处传递null引用时,这个模式就有用武之地。Null对象的作用是代替null引用并实现相同的接口但是没有行为。
如果不希望ProductService类中缓存数据,Null Object模式可以派上用场:
public class NullObjectCache:ICacheStorage
{
public void Remove(string key)
{
} public void Store(string key, object data)
{
} public T Retrieve<T>(string key)
{
return default(T);
}
}
这样,当我们请求缓存数据的时候,它什么都不做而且总是向ProductService返回null值,确保不会缓存任何数据。
最后,总结一下:
三种设计模式分组。
依赖注入原则。
Adapter模式具体应用。
Null Object模式用于处理空对象。
Asp.net设计模式笔记之一:理解设计模式的更多相关文章
- <转>ASP.NET学习笔记之理解MVC底层运行机制
ASP.NET MVC架构与实战系列之一:理解MVC底层运行机制 今天,我将开启一个崭新的话题:ASP.NET MVC框架的探讨.首先,我们回顾一下ASP.NET Web Form技术与ASP.NET ...
- [设计模式] .NET设计模式笔记 - 有多少种设计模式
.NET下的23中设计模式. ※创建型模式篇 ●单件模式(Single Pattern) ●抽象工厂模式(Abstract Factory) ●建造者模式(Builder Pattern) ●工厂方法 ...
- php设计模式笔记--总结篇
一.引入 设计模式的一般定义不再说,只大概说一下我理解的设计模式,我理解的设计模式的主要目的是利用面向对象(类.接口等)特点,让代码更加易于扩展,易于重用,易于维护.这三个特点也就要求我们不要将太多 ...
- php设计模式笔记:单例模式
php设计模式笔记:单例模式 意图: 保证一个类仅有一个实例,并且提供一个全局访问点 单例模式有三个特点: 1.一个类只有一个实例2.它必须自行创建这个实例3.必须自行向整个系统提供这个实例 主要实现 ...
- 再起航,我的学习笔记之JavaScript设计模式05(简单工程模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...
- Java 设计模式笔记
0. 说明 转载 & 参考大部分内容 JAVA设计模式总结之23种设计模式 1. 什么是设计模式 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设 ...
- 再起航,我的学习笔记之JavaScript设计模式05(简单工厂模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...
- 设计模式笔记(一):Singleton 设计模式
今天开始学习设计模式,借此机会学习并整理学习笔记. 设计模式是一门不区分语言的课程,什么样的编程语言都可以用到设计模式.如果说java语法规则比作武功招式的话,那么设计模式就是心法. 设计模式共有23 ...
- Java中常用的设计模式代码与理解
Java中常用的设计模式代码与理解 一.单例模式 1.饿汉式 (太饿了,类加载的时候就创建实例) /** * 饿汉式单例模式 */ public class HungrySingleInstance ...
- head first 设计模式笔记2-观察者模式:气象观测站
设计原则:为了交互对象之间的松耦合设计而努力. 1.设计模式的一些理解 1)知道OO基础,并不足以让你设计出良好的OO系统 2)良好的OO设计必须具备可复用.可扩充.可维护三个特性 3)模式可以让我们 ...
随机推荐
- 【原】iOS下KVO使用过程中的陷阱
KVO,全称为Key-Value Observing,是iOS中的一种设计模式,用于检测对象的某些属性的实时变化情况并作出响应.网上广为流传普及的一个例子是利用KVO检测股票价格的变动,例如这里.这个 ...
- Block的使用及循环引用的解决
Block是一个很好用的东西,这篇文章主要来介绍:1.什么是Block?2.Block的使用?3.Block的循环引用问题及解决. 1.什么是Block? 说这个问题之前,我先来说一下闭包(Closu ...
- C++语言-02-函数
普通函数 C++是在C语言的基础上增加了面向对象特性的语言,是C语言的超集 C++中的普通函数与C语言中的普通函数具有类似的性质.请参照以下博客:C语言-04-函数 与类相关的函数 C是一种OOP语言 ...
- C语言-07-预处理、typedef、static和extern
预处理 1> 使用注意 以#开头 在代码被翻译成0和1之前执行 预处理指令可以出现在任何位置 作用域是从编写指令那一行开始到文件结束 2> 宏定义 基本使用 ① 使用#define定义 ② ...
- 2、IOS开发--iPad之仿制QQ空间 (初始化HomeViewController子控件视图)
1.先初始化侧边的duck,效果图: 实现步骤: 2.然后初始化BottomMenu,效果: 步骤: 其实到这里,会出现一个小bug,那就是: 子控件的位置移高了,主要原因是: 逻辑分析图: 问题解决 ...
- Volley源码分析(1)----Volley 队列
Android网络框架很多,但是基于Google自己的volley,无疑是优秀的一款. 网络框架,无外乎解决一下几个问题,队列,缓存,图片异步加载,统一的网络请求和处理等. 一.Volley 队列 启 ...
- SAM4E单片机之旅——19、CAN间通信
CAN协议具有良好的可靠性,在工业中应用广泛.这次就先熟悉CAN的基本功能. 开发板有两个CAN,每个CAN有8个信箱.这次内容是从CAN0的信箱0发送数据到CAN1的信箱0. 除本次使用的功能外,C ...
- Effective Java 42 Use varargs judiciously
Implementation theory The varargs facility works by first creating an array whose size is the number ...
- nodejs创建一个HTTP服务器 简单入门级
const http = require('http');//请求http.createServer(function(request, response){ /*createServer该函数 ...
- 数据库update的异常一例
调查一列bug,偶然发现了update的一个特性:update t set a=a+1 where id=4; 这样一条简单的语句,也会发生让人意外的事情: 如果 a 的初始值为null时,无论你up ...