设计模式入门(一)

本内容将研究如何使用模式和设计原则来改进一段简单的asp.net 代码。其实,这段代码比较常见,或者已经看过或者是用过N次了。

以下内容高手略过。。。欢迎吐槽。

首先来看一段在电子商务应用程序中典型使用的代码部分。代码的功能是获取指定类别中的商品列表。

代码已经共享在GIT 上。 htt://git.oschina.net/ 

Product.cs 表示数据中的一个商品数据,ProductRepository.cs 表示从数据库中获取商品列表,ProductService.cs 类是一个服务类,用于向下从ProductRepository 中获取数据,向上 提供 Product 数据。

Product.cs

    public class Product
    {
        public int Id { get; set; }

        public string Name { get; set; }

        // 更多其他属性
    }

ProductRepository.cs

    public class ProductRepository
    {
        public IList<Product> GetProductsByCategoryId(int categoryId)
        {
            IList<Product> products = new List<Product>();

            // TODO. 从数据库中获取商品

            return products;
        }
    }

ProductService.cs

    public class ProductService
    {
        protected const string CACHE_KEY = "products.all.{0}";

        protected ProductRepository _repository;

        public ProductService()
        {
            this._repository = new ProductRepository();
        }

        public IList<Product> GetProductsByCategoryId(int categoryId)
        {
            IList<Product> products;

            string cache_key = string.Format(CACHE_KEY, categoryId);

            products = (List<Product>)HttpContext.Current.Cache.Get(cache_key);

            if (products == null)
            {
                products = _repository.GetProductsByCategoryId(categoryId);

                HttpContext.Current.Cache.Insert(cache_key, products);
            }

            return products;
        }
    }


ProductService 的GetProductsByCategoryId 方法很简单,就是负责从缓存中获取商品数据,如果缓存中的数据为空,那就从 ProductRepository 中获取并且保存带缓存中 。

如果当前的项目到此为止那是没有什么问题的。当然,我们的重点不再这里。我们重新审查下代码,有什么问题呢?

  • ProductService 类 依赖于 ProductRepository 类。如果ProductRepository  类中的API 发生 改变,ProductService 类中 引用的API 也需要相应改变。
  • 代码不可测试。如果ProductRepository 类 不与真正的数据库链接,就不能测试 ProductService 类 中的方法,因为这两个类之间存在紧密耦合。还有一个与测试有关的问题是,ProductService  类依赖使用 HttpContext.Current.Cache 来缓存数据,很难测试这种与 HTTP 上下文紧密耦合的代码。
  • 被迫使用 HTTP 上下文来缓存。在当前状态下,如果使用其他缓存提供者,比如 memcached 或者是 velocity , 则需要修改 ProductService 类以及其他使用了缓存的类。

当前代码位置:http://git.oschina.net/PassingWind/SampleCode/tree/PartternsStart1/


一,使用设计原则进行重构代码

ProductService 类 依赖于 ProductRepository 类 的问题。如果修改了 ProductRepository  类中的API ,ProductService 类也要做相应修改。这破坏了分离关注点和单一责任原则。

1,依赖倒置原则

依赖抽象而不要依赖具体。

这里可以实施依赖倒置原则来解耦 ProductService 类 和 ProductRepository 类,让他们都依赖于抽象——【接口】。

将  ProductService 类 和 ProductRepository 类 都重构下,提取出接口,IProductRepository 接口和 IProductService 接口。并 实现接口。


修改  ProductService 类 ,改为引用 接口而不是具体实现的类。

    public class ProductService : IProductService
    {
        protected const string CACHE_KEY = "products.all.{0}";

        //protected ProductRepository _repository;
        protected IProductRepository _repository;

        public ProductService()
        {
            this._repository = new ProductRepository();
        }

        public IList<Product> GetProductsByCategoryId(int categoryId)
        {
            IList<Product> products;

            string cache_key = string.Format(CACHE_KEY, categoryId);

            products = (List<Product>)HttpContext.Current.Cache.Get(cache_key);

            if (products == null)
            {
                products = _repository.GetProductsByCategoryId(categoryId);

                HttpContext.Current.Cache.Insert(cache_key, products);
            }

            return products;
        }
    }

通过引入接口有什么作用呢? ProductService 类 现在之依赖于抽象而不是具体的实现,意味着 ProductService 类 完全不知道任何实现,从而确定它不会那么容易被破坏,而且代码基在整体上对变化更有弹性。

但是,这里还有一个小问题, ProductService 类 仍然负责创建具体的实现,而且在目前还没有有效的 ProductRepository 类的情况下,无法测试代码。

当前代码位置:http://git.oschina.net/PassingWind/SampleCode/tree/PartternsStart2/

2,依赖注入原则

ProductService 类 仍然与 ProductRepository 类的具体实现关联在一起,因为目前 ProductService 类 目前的任务是创建实例。这一点可以从 ProductService 类的构造方法中可以看出来。

依赖注入 (Dependency Injection ) 可以将创建  ProductRepository 类的具体实现 的责任移到 ProductService 类 外面去,并且通过 ProductService 类 的构造器将其注入。

    public class ProductService : IProductService
    {
        protected const string CACHE_KEY = "products.all.{0}";

        //protected ProductRepository _repository;
        protected IProductRepository _repository;

        public ProductService(IProductRepository repository)
        {
            //this._repository = new ProductRepository();
            this._repository = repository;
        }

        public IList<Product> GetProductsByCategoryId(int categoryId)
        {
            IList<Product> products;

            string cache_key = string.Format(CACHE_KEY, categoryId);

            products = (List<Product>)HttpContext.Current.Cache.Get(cache_key);

            if (products == null)
            {
                products = _repository.GetProductsByCategoryId(categoryId);

                HttpContext.Current.Cache.Insert(cache_key, products);
            }

            return products;
        }
    }

这样就可以在测试期间 向 ProductService 类 中传递 代替者,从而能孤立的测试 ProductService 类 。通过把获取依赖的责任从 ProductService 类中移除,使  ProductService 类 从而遵循单一责任原则。现在它只关心如何从缓存或者是资源库中获取数据,而不是创建具体的 IProductRepository 类 的实现。

最后要做的事情是 解决 缓存需求对 HTTPContext 的依赖。 可以使用一种简单的设计模式提供的服务。