• [Architecture Pattern] Repository


    动机

    Repository Pattern是一个在开发系统时,很常用的一个模式。在一些大师的著作:不管是在Martin Fowler所写的PoEAA或者是Eric Eban著作的DDD里,都有出现这个Pattern的身影。Repository Pattern最主要是定义如何切割BLL层跟DAL层之间的相依性,让BLL层不用依赖于DAL层的实做。并且在有需要更换DAL目标的时候,可以有抽换DAL层的能力。

    同时学习Repository Pattern,也为架构设计带入了边界的概念。在设计架构的时候,可以套用Repository Pattern来做为架构边界的封装。将外部的系统、模块、数据库…等等,隔离在目标架构之外,让目标架构有更高的内聚以及较少的耦合。

    本篇文章介绍一个Repository Pattern的实做,这个实做定义对象之间的职责跟互动,用来完成Repository Pattern应该提供的功能及职责。为自己做个纪录,也希望能帮助到有需要的开发人员。

    结构

    接下来采用一个计算人员数量的服务UserCountService,当作范例的内容。这个UserCountService,计算系统内所有人员的数量、男性人员的数量,提供给外部系统使用。并且实做Repository Pattern来当作BLL层的系统边界、切割BLL与DAL之间的相依。范例的结构如下:

    主要的参与者有:

    User
    -系统运作使用的数据对象。
    -UserID是这个对象的索引值。

    UserCountService
    -使用UserRepository加载User。
    -使用加载的User计算各种Count。

    UserRepository
    -使用IUserRepositoryProvider加载User。

    IUserRepositoryProvider
    -数据对象 User进出系统边界的接口。
    -只提供查询功能。

    SqlUserRepositoryProvider
    -继承IUserRepositoryProvider。
    -实做查询数据库产生User对象。

    透过下面的图片说明,可以了解相关对象之间的互动流程。

    实做

    范列下载 
    实做说明请参照范例程序内容。
    RepositorySample点此下载

    范列实做

    首先建立RepositorySample.BLL项目,并且建立一个系统运作使用的数据对象User。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    namespace RepositorySample.BLL
    {
        public class User
        {
            // Constructor
            public User(Guid userID)
            {
                #region Require
     
                if (userID == Guid.Empty) throw new ArgumentNullException();
     
                #endregion
            }
     
     
            // Properties
            public Guid UserID { get; set; }
     
            public string Name { get; set; }
     
            public bool IsMen { get; set; }
        }
    }

    接着建立提供BLL层数据进出的边界对象,UserRepository对象及IUserRepositoryProvider接口。(这边要特别说明的是,也可以直接建立IUserRepository接口当作BLL层数据进出的边界对象。之所以建立UserRepository对象及IUserRepositoryProvider接口,这种组合式的边界对象,只是架构设计的个人习惯,笔者比较不喜欢使用接口让架构内部直接使用。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    namespace RepositorySample.BLL
    {
        public class UserRepository : IUserRepositoryProvider
        {
            // Fields 
            private readonly IUserRepositoryProvider _userRepositoryProvider = null;
     
     
            // Constructor
            public UserRepository(IUserRepositoryProvider userRepositoryProvider)
            {
                #region Require
     
                if (userRepositoryProvider == null) throw new ArgumentNullException();
     
                #endregion
                _userRepositoryProvider = userRepositoryProvider;
            }
             
     
            // Methods
            public IEnumerable<User> QueryAll()
            {
                return _userRepositoryProvider.QueryAll();
            }
        }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    namespace RepositorySample.BLL
    {
        public interface IUserRepositoryProvider
        {
            // Methods
            IEnumerable<User> QueryAll();
        }
    }

    接着建立BLL层最后一个对象,也就是真正提供计算人员数量服务的对象UserCountService。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    namespace RepositorySample.BLL
    {
        public class UserCountService
        {
            // Fields 
            private readonly UserRepository _userRepository = null;
     
     
            // Constructor
            public UserCountService(UserRepository userRepository)
            {
                #region Require
     
                if (userRepository == null) throw new ArgumentNullException();
     
                #endregion
                _userRepository = userRepository;
            }
             
     
            // Methods
            public int GetAllCount()
            {
                return _userRepository.QueryAll().Count();
            }
     
            public int GetMenCount()
            {
                int menCount = 0;
                foreach (User user in _userRepository.QueryAll())
                {
                    if (user.IsMen == true)
                    {
                        menCount++;
                    }
                }
                return menCount;
            }       
        }
    }

    再来建立RepositorySample.DAL项目,以及存取Sql数据库的DAL对象SqlUserRepositoryProvider。(因为是仿真范例就不实做查询数据库,改用直接建立的方式来示意。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    namespace RepositorySample.DAL
    {
        public class SqlUserRepositoryProvider : IUserRepositoryProvider
        {
            // Methods
            public IEnumerable<User> QueryAll()
            {
                User user = null;
                List<User> userList = new List<User>();
     
                user = new User(Guid.NewGuid());
                user.Name = "Clark";
                user.IsMen = true;
                userList.Add(user);
     
                user = new User(Guid.NewGuid());
                user.Name = "Jane";
                user.IsMen = false;
                userList.Add(user);
     
                return userList;
            }
        }
    }

    最后剩下的就是建立一个Console项目,来使用UserCountService。这个Console项目里,会生成UserCountService,并且打印的人员数量。(因为是仿真范例,UserCountService的生成,采用直接建立的方式来示意。实际项目可以采用各种IoC Framework来做生成注入的动作,避免Console项目与DAL项目有高度的耦合。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    namespace RepositorySample
    {
        class Program
        {
            static void Main(string[] args)
            {
                // UserCountService
                UserCountService userCountService = CreateUserCountService();
     
                // Print
                Console.WriteLine("All Count : " + userCountService.GetAllCount());
                Console.WriteLine("Men Count : " + userCountService.GetMenCount());
     
                // End
                Console.ReadLine();
            }
     
            static UserCountService CreateUserCountService()
            {
                // UserRepositoryProvider
                SqlUserRepositoryProvider userRepositoryProvider = new SqlUserRepositoryProvider();
     
                // UserRepository
                UserRepository userRepository = new UserRepository(userRepositoryProvider);
     
                // UserCountService
                UserCountService userCountService = new UserCountService(userRepository);
     
                // Return
                return userCountService;
            }
        }
    }

    情景

    接着采用几个情景,来验证系统的重用性。并且说明如何利用Repository Pattern提供的弹性,来满足各种的需求。

    系统更换数据源

    卖系统给客户的时候,客户的企业环境内不允许装设SQL Server,必须要采用别种数据存放媒介(例如:CSV檔)。

    这时可以依照客户的需求,写一个新的CsvUserRepositoryProvider对象,用来取代SqlUserRepositoryProvider,系统透过这个新的CsvUserRepositoryProvider对象来查询CSV档案内的User数据。透过这样的方式,系统就可以更换数据源。范例的程序代码如下:

    首先在RepositorySample.DAL项目内,建立存取CSV档案的DAL对象CsvUserRepositoryProvider。(因为是仿真范例就不实做剖析档案内容,改用直接建立的方式来示意。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    namespace RepositorySample.DAL
    {
        public class CsvUserRepositoryProvider : IUserRepositoryProvider
        {
            // Methods
            public IEnumerable<User> QueryAll()
            {
                User user = null;
                List<User> userList = new List<User>();
     
                user = new User(Guid.NewGuid());
                user.Name = "Jeff";
                user.IsMen = true;
                userList.Add(user);
     
                return userList;
            }
        }
    }

    接着因为范例没有采用IoC Framework来做生成注入,所以必须手动修改生成注入这个工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    static UserCountService CreateUserCountService()
    {
        // UserRepositoryProvider
        CsvUserRepositoryProvider userRepositoryProvider = new CsvUserRepositoryProvider();
     
        // UserRepository
        UserRepository userRepository = new UserRepository(userRepositoryProvider);
     
        // UserCountService
        UserCountService userCountService = new UserCountService(userRepository);
     
        // Return
        return userCountService;
    }

    最后看看运行结果,可以发现计算出来的人员数量,是以CsvUserRepositoryProvider提供的资料来做计算。

    系统增加数据源

    另外在卖系统给客户过了一阵子之后,客户增加了一个外部的使用者数据源。这个新的使用者数据源,必须要可以跟原本系统内的用户数据一起使用。

    这时可以依照客户的需求,写一个UnionUserRepositoryProvider对象,用来合并新使用者数据源与旧用户数据源,系统透过这个UnionUserRepositoryProvider对象就可以取得两种数据源的User数据。透过这样的方式,系统就可以加入额外的数据源。范例的程序代码如下:

    首先在RepositorySample.DAL项目内,建立一个UnionUserRepositoryProvider对象。这个对象可以合并多个IUserRepositoryProvider提供的资料。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    namespace RepositorySample.DAL
    {
        public class UnionUserRepositoryProvider : IUserRepositoryProvider
        {
            // Fields 
            private readonly List< IUserRepositoryProvider> _userRepositoryProviderList = null;
     
     
            // Constructor
            public UnionUserRepositoryProvider(List<IUserRepositoryProvider> userRepositoryProviderList)
            {
                #region Require
     
                if (userRepositoryProviderList == null) throw new ArgumentNullException();
     
                #endregion
                _userRepositoryProviderList = userRepositoryProviderList;
            }
     
     
            // Methods
            public IEnumerable<User> QueryAll()
            {
                List<User> userList = new List<User>();
     
                foreach (IUserRepositoryProvider userRepositoryProvider in _userRepositoryProviderList)
                {
                    foreach (User user in userRepositoryProvider.QueryAll())
                    {
                        userList.Add(user);
                    }
                }
     
                return userList;
            }
        }
    }

    另外因为范例没有采用IoC Framework来做生成注入,所以必须手动修改生成注入这个工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    static UserCountService CreateUserCountService()
    {
        // UserRepositoryProvider 
        List<IUserRepositoryProvider> userRepositoryProviderList = new List<IUserRepositoryProvider>();
        userRepositoryProviderList.Add(new CsvUserRepositoryProvider());
        userRepositoryProviderList.Add(new SqlUserRepositoryProvider());
        UnionUserRepositoryProvider userRepositoryProvider = new UnionUserRepositoryProvider(userRepositoryProviderList);
                 
        // UserRepository
        UserRepository userRepository = new UserRepository(userRepositoryProvider);
     
        // UserCountService
        UserCountService userCountService = new UserCountService(userRepository);
     
        // Return
        return userCountService;
    }

    最后看看运行结果,可以发现计算出来的人员数量,是以合并CsvUserRepositoryProvider、SqlUserRepositoryProvider提供的数据来做计算。

    系统加入快取功能

    接着客户使用系统一阵子之后,发现数据较多的时候,系统就会变慢。经过各种效能工具的检查,发现是剖析CSV文件是整个系统效能的瓶颈。

    这时可以依照客户的需求,写一个CacheUserRepositoryProvider对象,用来快取CsvUserRepositoryProvider提供的数据,系统只有在第一次取数据的时候,才会去剖析CSV档案。透过这样的方式,系统就可以加入快取数据源的功能,提高系统的效能。范例的程序代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    namespace RepositorySample.DAL
    {
        public class CacheUserRepositoryProvider : IUserRepositoryProvider
        {
            // Fields 
            private readonly IUserRepositoryProvider _userRepositoryProvider = null;
     
            private IEnumerable<User> _cache = null;
     
     
            // Constructor
            public CacheUserRepositoryProvider(IUserRepositoryProvider userRepositoryProvider)
            {
                #region Require
     
                if (userRepositoryProvider == null) throw new ArgumentNullException();
     
                #endregion
                _userRepositoryProvider = userRepositoryProvider;
            }
             
     
            // Methods
            public IEnumerable<User> QueryAll()
            {
                if (_cache == null)
                {
                    _cache = _userRepositoryProvider.QueryAll();
                }
                return _cache;
            }
        }
    }

    当然啦,因为范例没有采用IoC Framework来做生成注入,所以手动修改生成注入这个工作还是要做。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    static UserCountService CreateUserCountService()
    {
        // UserRepositoryProvider
        CsvUserRepositoryProvider csvUserRepositoryProvider = new CsvUserRepositoryProvider();
        CacheUserRepositoryProvider userRepositoryProvider = new CacheUserRepositoryProvider(csvUserRepositoryProvider);
     
        // UserRepository
        UserRepository userRepository = new UserRepository(userRepositoryProvider);
     
        // UserCountService
        UserCountService userCountService = new UserCountService(userRepository);
     
        // Return
        return userCountService;
    }

    后记

    整个Repository Pattern实做看来下,眼尖的开发人员会发现,它跟IoC有异曲同工的意味。而Repository Pattern跟IoC的差异,主要是取决于设计时的颗粒度。IoC是从程序设计面,去看待切割相依性这件事情。而Repository Pattern则是从架构设计面,去看待切割相依性这件事情。

    在架构设计里加入Repository Pattern的设计,可以为系统架构提供了抽换DAL层的弹性。一个系统的成败,除了最基本的满足客户需求之外,这些额外的非功能需求也是很重要的一环。动手的时候多想一点点,未来维护的开发人员,会感激你的。


    期許自己~
    能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
    真正做到「以形寫神」的境界。



    摘要: 动机Repository Pattern是一个在开发系统时,很常用的一个模式。在一些大师的著作:不管是在Martin Fowler所写的PoEAA或者是Eric Eban著作的DDD里,都有出现这个Pattern的身影。Repository Pattern最主要是定义如何切割BLL层跟DAL层之间的相依性,让BLL层不用依赖于DAL层的实做。并且在有需要更换DAL目标的时候,可以有抽换DAL层的能力。同时学习Repository Pattern,也为架构设计带入了边界的概念。在设计架构的时候,可以套用Repository Pattern来做为架构边界的封装。将外部的系统、模块、数据库…等等,隔阅读全文
    posted @ 2012-04-29 20:38 Clark159 阅读(228) | 评论 (1) 编辑
     
    摘要: 动机 :Plugin是在软件系统内增加功能的功能。 如果在软件系统加入Plugin功能,能提高软件系统的重用性。加入Plugin功能的软件系统在开发完成之后。 如果需要额外加入功能,不用变更已完成的软件系统就能加入新功能。 并且因为不用变更已完成的软件系统,也就避免了修改软件系统会产生的风险。另外在系统里加入Plugin功能,其实会遇到一个问题。 当有多个Plugin内容加入系统的时候,该如何去识别与取得加入的Plugin内容。本文介绍一个『Service Plugin 模式』, 定义对象之间的职责跟互动,用来实现Plugin提供的功能,并且封装Plugin内容识别与取得的职责。 为自己做个纪阅读全文
    posted @ 2012-02-17 13:09 Clark159 阅读(814) | 评论 (0) 编辑
     
    摘要: 动机 :在开发与数据库沟通的系统时,因为建立数据库联机是比较昂贵的。 所以ADO.NET在背后帮开发人员,实做了 ConnectionPool的机制。 将系统内建立的数据库联机做快取,当系统要使用时就直接使用快取联机,避免了每次都建立新数据库联机的花费。 并且实际上在使用ADO.NET时,开发人员对于背后的ConnectionPool机制其实是无感的。 要让开发人员无感,可是又能完成快取的功能,这真的要花一点工夫去设计。本文介绍一个『Singleton Pool模式』。 定义对象之间的职责跟互动,用来建置类似ConnectionPool功能的对象池功能,并且提供开发人员无感的使用界面。 为自己阅读全文
    posted @ 2012-02-10 08:04 Clark159 阅读(905) | 评论 (2) 编辑
     
    摘要: 接续...[Architecture Pattern] Device Projection 模式 (上)实做 :范列下载 :DeviceProjectionSample点此下载范列逻辑 :下面图片是范例程序执行的结果。主要的参与者有:LightDevice.exe -仿真远程设备的程序,采用TCP联机连接LightMaster。 -窗体上灯号数据的图像,可透过右侧灯号按钮做开关。 -窗体上灯号数据的图像,接受LightMaster传送来的指令做开关。 -每300ms会将灯号数据传送到LightMaster。LightMaster.exe -映像远程设备的程序,采用TCP联机聆听LightDev阅读全文
    posted @ 2012-02-02 13:33 Clark159 阅读(695) | 评论 (0) 编辑
     
    摘要: 动机 : 开发实时系统的时候,常常需要建立一组对象,用来映像远程设备。 透过这组对象呈现远程设备的状态、通知。 使用这组对象封装远程设备的操作、管理。但在映像远程设备提供上述功能之前。 还需要提供探索远程设备的功能,建立远程设备列表。 这样才能完整的提供服务。本文介绍一个『Device Projection 模式』, 定义对象之间的职责跟互动,用来封装映射远程设备所需要提供的功能。 让开发人员在需要实做相关功能时,能有一个参考的架构。结构 : 下图是这个模式的示意图,整个看起来有点复杂。 我们将图拆解开来说明,会比较方便了解。 Device相关对象 : 首先是Device相关对象,这组对象主要阅读全文
    posted @ 2012-01-28 23:46 Clark159 阅读(954) | 评论 (2) 编辑
     
    摘要: 动机 : 开发应用程序的时候,针对用户接口开发。 业界有许多前辈提出了多种的设计模式,其中最为人所知的就是 MVC模式。MVC模式在实作上有许多种的方法, 不同的开发人员去理解它,都会有不同的理解。 不同的情景需求去套用它,也会有不同的实作。 但不论怎么理解跟实作,它最基本的观念依然都是: 「将系统职责拆解至 Model、View、XXX三种类别,并且定义它们之间的相依关系及沟通方式。」在微软.NET技术架构下,目前最为众人讨论的MVC延伸模式, 应该是适用 WPF、Silverlight、Windows phone平台的 MVVM模式 (Model-View-ViewModel)。 可以说近阅读全文
    posted @ 2011-10-10 10:13 Clark159 阅读(1022) | 评论 (5) 编辑
     
    摘要: 动机 : 在设计面向对象应用程序架构的时候,对象会包含相关的企业逻辑,而不是单纯的数据对象。但是当企业逻辑需要取得其他对象一起运算,如何「取得」是一件很复杂的事情。例如说:在系统内有一个「查询客户订单总金额」的企业逻辑,需要从系统取出客户的所有订单做金额加总。这个企业逻辑实作上可以分配到不同的对象,这边我们先定义这个企业逻辑是客户对象的职责。并用下列的程序代码,实作这个企业逻辑,这样的范例是可以正常的工作。但是换个场景会发现,在只是要编辑客户电话的时候,也需要取得订单查询接口。当系统越来越庞大,企业逻辑越来越多时,这个范例架构就会显得是个灾难。而且再细看的话会发现订单有参考到客户,这个范例有循阅读全文
    posted @ 2011-10-10 10:11 Clark159 阅读(49) | 评论 (0) 编辑
     
    摘要: 动机 :前一篇 [Application Architecture] : Entity Expansion模式,介绍了一种扩展对象属性数据的模式。本文延续上一篇的动机,介绍一个Entity Profile模式。Entity Profile模式主要是定义一组,数据对象(Entity)以及边界对象(Repository)的生成、结构、行为模式,用来扩展对象的属性数据。实作这个模式,可以为系统加入增加式程序代码累积的能力。基础平台 : 结构参与者Page Shell-页面的壳层,可以透过设定数据,动态挂载系统页面的系统。范例程序Page Shell依照开发平台的不同,会有不同的选择。例如以ASP.N阅读全文
    posted @ 2011-10-10 10:02 Clark159 阅读(27) | 评论 (0) 编辑
     
    摘要: 动机 :一个软件系统的生命周期,必然面临到系统改版的问题。而在系统改版的时候,最常遇到的问题之一是,用户希望增加系统对象的数据字段(例如 : 用户数据增加相片)。常见的做法是把相关的功能,从把整个系统从UI到DB重整(重写?)一遍,让用户希望增加至系统的字段,在系统里实现。这样的做法,笔者把它称作『修改式程序代码累积』。所谓的修改式程序代码累积是说,藉由修改经过验证、并且正常运作的程序代码与接口来扩充系统。理论上,程序代码经过修改之后,必须重新执行完整的测试。而接口经过修改,使用手册、教育训练等等,常常也必需做同步的更新。可以说是牵一发动全身。笔者比较喜爱『增加式程序代码累积』。所谓的增加式程阅读全文
    posted @ 2011-10-10 10:00 Clark159 阅读(21) | 评论 (0) 编辑
     
    摘要: 动机 :在设计面向对象应用程序的时候,简单的分层会将系统分为Presentation Layer(PL)、 Business Logic Layer(BLL)、Data Access Layer(DAL)。但是三层之间的对象生成、依赖注入等等的设计会是一件很复杂的事情。例如:PL对象该怎么去生成BLL的对象、DAL的对象该怎么注入BLL。本文介绍一个简单的Context模式,用来框出一个面向对象应用程序的架构,让每一层的对象设计有可循的规范。结构 :参与者 :UserReportPage-用户接口,呈现用户数据的报表。-使用ContextContainer的静态属性取得Context。-使用C阅读全文
    posted @ 2011-10-10 09:55 Clark159 阅读(39) | 评论 (0) 编辑
     
    摘要: 动机 :在设计面向对象应用程序架构的时候,关联的对象进出 Data Access Layer(DAL)是一件很复杂的事情。Entity Framework是一种解决方案的选择,但是它包装了太多用不习惯的功能。例如对象的变更追踪同步这类的功能,一般开发程序的时候主要是新增修改删除查询,很少去处理到追踪同步。本文介绍一个轻量级的边界模式,将它架构在Business Logic Layer(BLL)里。让DAL层,只专注处理永续储存的数据。让BLL层,能简单处理对象与对象关联,方便建立不贫血的对象。结构 :参与者 :User- 被关联的对象,UserID是这个对象的索引值。UserGroup- 包含阅读全文
    posted @ 2011-10-10 09:50 Clark159 阅读(26) | 评论 (0) 编辑
  • 相关阅读:
    intellij idea 修改web端口号
    intellij idea有时候有时候服务器报错500
    由于没有更新主分支的代码,总是报警
    intellij idea 快捷键
    intellij idea 修改文件名失败
    [Introduction to programming in Java 笔记] 1.3.8 Gambler's ruin simulation 赌徒破产模拟
    [Introduction to programming in Java 笔记] 1.3.7 Converting to binary 十进制到二进制的转换
    C++学习笔记-2-构造函数和析构函数
    python学习笔记--随时更新
    C++学习笔记-1-自增和自减运算符
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2476605.html
Copyright © 2020-2023  润新知