Asp.net设计模式笔记之一:理解设计模式

GOF设计模式著做中的23种设计模式能够分红三组:建立型(Creational),结构型(Structural),行为型(Behavioral)。下面来作详细的剖析。web

建立型算法

建立型模式处理对象构造和引用。他们将对象实例的实例化责任从客户代码中抽象出来,从而让代码保持松散耦合,将建立复杂对象的责任放在一个地方,这遵循了单一责任原则和分离关注点原则。数据库

下面是“建立型”分组中的模式:设计模式

1.Abstract Factory(抽象工厂)模式:提供一个接口来建立一组相关的对象。缓存

2.Factory Method(工厂方法)模式:支持使用一个类来委托建立有效对象的责任。框架

3.Builder(生成器)模式:将对象自己的构造分离出来,从而可以构造对象的不一样版本。分布式

4.Prototype(原型)模式:可以从一个原型实例来复制或克隆类,而不是建立新实例。学习

5.Singleton(单例)模式:支持一个类只实例化一次,并只有一个可用来访问它的全局访问点。测试

结构型ui

结构型模式处理对象的组合与关系,以知足大型系统的须要。

下面是“结构型”分组中的模式:

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图示:

image

从图中能够看出,客户有一个对抽象(Target)的引用。在这里,该抽象就是ICacheStorage接口。Adapter是Target接口的一个实现,它只是将Operation方法委托给Adaptee类,这里的Adapter类就是指咱们的HttpCacheStorage类,而Adaptee类则是指HttpContext.Current.Cache提供的具体操做方法。

具体的描述以下:

image

这样,当咱们切换到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模式用于处理空对象。

相关文章
相关标签/搜索