铲除具体依赖的技术

前言:

以下职能在国庆期就做到并提前发布了,但到明日才有时光写文介绍,重固然国庆后依然选取就职了,悲催的是上班的地方全公司都能上网,唯独开发部竟不让上网,是个局域网。

也不是全无法上,房间里有三台能上网的对讲机(两台台式机+一台台式机),下载资料还得用公司的U盘再转到自己电脑,这种半封闭的环境,相当的令人不适于,有种欲仰天吐血的觉得。

这一周我都向四个带总的领导反映了上网问题,可是没啥响应,猜想是没戏。

于是乎我只有把内部一台能上网的记录簿拿到祥和桌子上去独自占用了,勉强也能上下网了,可是据悉安全题材,我也糟糕在这机子里登陆私人账号。 

透过一周的探讨,我发觉解决方案或者有些:因为商家的规定只是开发的对讲机不让上网而已,自己按理应该能够带台式机去上网,可是自己奇怪的是竟然整个单位都没人带台式机去,不晓得搞什么名头。

好了,废话不多说了,下边入文章的主题:

一个表面现实目的的引入,必然会给一个模块带来与外表模块之间的看重。而具体目的的创建始终是大家无法逃避的。尽管我们得以拔取设计格局的厂子方法格局或抽象工厂封装具体对象创造的逻辑,但却又再次引入了实际工厂对象的始建看重。固然在筹划上有所革新,但从未根本排除具体倚重,仍让我心有戚戚焉。

CYQ.Data V5 配置工具:

最新更新了布置工具,新的界面截图如下: 

电子商务 1

此次调整的效能如下:

1:编码情势:新扩大纯实体的变动:

至今,CYQ.Data 就有了二种编码模式,分别是:

A: 枚举型(MAction、MProc)- 性能最优的编码格局

B: 实体型(充血型的ORM操作 –
需要实体类继承CYQ.Data.Orm.OrmBase,也支撑CodeFirst情势)

C: 纯实体(贫血型的ORM操作 – 通过这次新增的CYQ.Data.Orm.DBFast静态类来操作)

2:生成的实体带表明文字。

3:默认名称空间扩大{0}来表示数据库名称。

4:多数据库格局下,默认的数据库链接,约定对应的Web.Config的布局为”数据库名称Conn“。

下边示例生成一个实体类如下:

using System;

namespace Web.Entity.Demo
{
    public class Users 
    {
        /// <summary>
        /// 标识ID
        /// </summary>
        public int? ID { get; set; }
        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get; set; }
        /// <summary>
        /// 创制日期
        /// </summary>
        public DateTime? CreateTime { get; set; }
    }
}

对于这样的实体,默认的数据库链接就是:

<add name=”DemoConn” connectionString=”server=.;database=demo;uid=sa;pwd=123456″/> 

倘使DemoConn不存在,则会取默认的Conn项。

工具的源码下载地址:http://www.cyqdata.com/download/article-detail-426

以一个电子商务网站的设计为例。在该品种中要求对客户的订单举办管制,例如插入订单。考虑到访问量的涉嫌,系统为订单管理提供了合伙和异步的章程。分明,在实质上采取中,我们需要依照现实的应用环境,决定动用那两种艺术的内部一种。由于变化卓殊频繁,因此我们使用了“封装变化”的宏图思想。譬如,考虑采取Strategy情势,因为插入订单的行事,实则就是一种插入订单的方针。大家得以为此政策建立抽象对象,如IOrderStrategy接口。

CYQ.Data.Orm.DBFast 类介绍 

这是我近年新扩大的静态类,重假若为着省去点代码,方便一些基础的操作。

以身作则代码(以上边的的Users实体为示范):

查询实体:

Users u=DB法斯特(Fast).Find<Users>(1);//一行查一个实体。

List<Users>
uList=DB法斯特(Fast).Select<Users>(2,10,”id>10″);//分页查询满意条件的列表。

日增多少:

DBFast.Insert<Users>(new Users{UserName=”a”;});

履新数据: 

DBFast.Update<Users>(new Users{UserName=”a”;},1);

去除数据: 

DBFast.Delete<Users>(1);

上述就是简的操作,扩大这一个静态类的意图,是为着简化一些健康的操作,让一行代码去解决,收缩代码量。

从而这多少个静态类并不是万能的,另外复杂性的的操作格局,
指出利用枚举型的正常操作。

下边提供这么些DBFast(Fast)静态类的源码,细看源码,会发觉这一个DB法斯特(Fast)类操作都仅是MAction类的二次封装形成的:

using System;
using System.Collections.Generic;
using System.Text;
using CYQ.Data.Table;

namespace CYQ.Data.Orm
{
    /// <summary>
    /// 快捷操作操作类。
    /// </summary>
    public static class DBFast
    {
        /// <summary>
        /// 查找单条记录
        /// </summary>
        /// <typeparam name=”T”>实体类型</typeparam>
        /// <param name=”where”>条件</param>
        /// <param name=”columns”>指定询问的列(可选)</param>
        /// <returns></returns>
        public static T Find<T>(object where, params string[] columns)
        {
            T result = default(T);
            MDataRow row = null;
            using (MAction action = GetMAction<T>())
            {
                if (columns != null && columns.Length > 0)
                {
                    action.SetSelectColumns(columns);
                }
                if (action.Fill(where))
                {
                    row = action.Data;
                }
            }
            if (row != null)
            {
                result = row.ToEntity<T>();
            }
            return result;
        }
        public static List<T> Select<T>()
        {
            int count;
            return Select<T>(0, 0, null, out count, null);
        }
        /// <summary>
        /// 列表查询
        /// </summary>
        /// <param name=”where”>查询条件[可附带 order by 语句]</param>
        /// <returns></returns>
        public static List<T> Select<T>(string where, params string[] columns)
        {
            int count;
            return Select<T>(0, 0, where, out count, columns);
        }
        /// <summary>
        /// 列表查询
        /// </summary>
        /// <param name=”topN”>查询几条</param>
        /// <param name=”where”>查询条件[可附带 order by 语句]</param>
        /// <returns></returns>
        public static List<T> Select<T>(int topN, string where, params string[] columns)
        {
            int count;
            return Select<T>(1, topN, where, out count, columns);
        }
        public static List<T> Select<T>(int pageIndex, int pageSize, params string[] columns)
        {
            int count;
            return Select<T>(pageIndex, pageSize, null, out count, columns);
        }
        public static List<T> Select<T>(int pageIndex, int pageSize, string where, params string[] columns)
        {
            int count;
            return Select<T>(pageIndex, pageSize, where, out count, columns);
        }
        /// <summary>
        /// 查找多条记下
        /// </summary>
        /// <typeparam name=”T”>实体类型</typeparam>
        /// <param name=”pageIndex”>第N页</param>
        /// <param name=”pageSize”>每页N条</param>
        /// <param name=”where”>条件</param>
        /// <param name=”count”>重临记录总数</param>
        /// <param name=”columns”>指定询问的列(可选)</param>
        /// <returns></returns>
        public static List<T> Select<T>(int pageIndex, int pageSize, object where, out int count, params string[] columns)
        {
            MDataTable dt = null;
            using (MAction action = GetMAction<T>())
            {
                if (columns != null && columns.Length > 0)
                {
                    action.SetSelectColumns(columns);
                }
                dt = action.Select(pageIndex, pageSize, where, out count);
            }
            return dt.ToList<T>();
        }

        /// <summary>
        /// 删除记录
        /// </summary>
        /// <typeparam name=”T”>实体类型</typeparam>
        /// <param name=”where”>条件</param>
        /// <returns></returns>
        public static bool Delete<T>(object where)
        {
            bool result = false;
            using (MAction action = GetMAction<T>())
            {
                result = action.Delete(where);
            }
            return result;
        }
        public static bool Insert<T>(T t)
        {
            return Insert<T>(t, InsertOp.ID);
        }
        /// <summary>
        /// 添加一条记下
        /// </summary>
        /// <typeparam name=”T”>实体类型</typeparam>
        /// <param name=”t”>实体对象</param>
        /// <returns></returns>
        public static bool Insert<T>(T t, InsertOp op)
        {
            bool result = false;
            MDataRow row = null;
            using (MAction action = GetMAction<T>())
            {
                action.Data.SetFromEntity(t);
                result = action.Insert(op);
                if (op != InsertOp.None)
                {
                    row = action.Data;
                }
            }
            if (row != null)
            {
                row.SetToEntity(t);
            }
            return result;
        }
        public static bool Update<T>(T t)
        {
            return Update<T>(t, null);
        }
        /// <summary>
        /// 更新记录
        /// </summary>
        /// <typeparam name=”T”>实体类型</typeparam>
        /// <param name=”t”>实体对象</param>
        /// <param name=”where”>条件</param>
        /// <returns></returns>
        public static bool Update<T>(T t, object where)
        {
            bool result = false;
            using (MAction action = GetMAction<T>())
            {
                action.Data.SetFromEntity(t);
                result = action.Update(where);
            }
            return result;
        }
        private static MAction GetMAction<T>()
        {
            string conn = string.Empty;
            MAction action = new MAction(GetTableName<T>(out conn), conn);
            action.SetNoAop();
            return action;
        }
        private static string GetTableName<T>(out string conn)
        {
            conn = string.Empty;
            Type t = typeof(T);
           
            string[] items = t.FullName.Split(‘.’);
            if (items.Length > 1)
            {
                conn = items[items.Length – 2] + “Conn”;
                items = null;
            }
            string tName = t.Name;
            t = null;
            return tName;
        }
    }

public interface IOrderStrategy

结束语:

新进的铺面,一开端觉得是开发电子商务类的网站,所以自己经三设想的架构选型不是WebForm也不是MVC,而是打算拔取QBlog那一套的框架,还特意思立异了CYQ.Data里的XHtmlAction模板引擎,增添了CMS的标签效应,以为要上战场,结果进去一天就发现,是付出的分销商订单系统,数据是和ERP对接,于是架构最近选型就变更为EasyUI+CYQ.Data+WebForm了。

这周应当会招一五个战友,人在特拉维夫的有趣味的可以给自己发私信。 

{

   void Insert(OrderInfo order);

}

下一场分别定义五个类OrderSynchronous和OrderAsynchronous实现IOrderStrategy接口。类社团如图1所示。

电子商务 2 图1  订单策略的计划性

当世界对象Order类需要插入订单时,将按照IOrderStrategy接口的周转期类型,执行有关的订单插入策略,如下代码所示。

public class Order

{

    private IOrderStrategy m_orderStrategy;

    public Order(IOrderStrategy orderStrategy)

   {

          m_orderStrategy = orderStrategy;

   }

     public void Insert(OrderInfo order)

   {

          m_orderStrategy.Insert(order);

   }

}

是因为用户随时都可能会改变插入订单的政策,由此对此业务层的订单领域对象而言,绝不可能与现实的订单策略对象发生耦合关系。也就是说,在世界对象Order类中,不可能new一个具体的订单策略对象,如下边的代码:

IOrderStrategy orderStrategy = new OrderSynchronous();

即使如此在头里的落实中,我们通过世界对象的构造函数传递了IOrderStrategy接口对象。但这么的兑现仅仅是将切实订单策略对象的创导推迟到了世界对象的调用者这里而已,调用者无法制止具体订单策略对象的开创。分明,这是一种“治标不治本”的做法。我们本来也期望可以有一种优质的事态,就是现实对象的创制永远都休想在代码中冒出。事实上,模块与模块间之所以暴发依赖关系,正是因为有实际目的的留存。一旦在一个模块中开创了另一个模块中的具体目标,依赖就发出了。现在,我们的目标就是要将这个倚重消除。

1、配置文件与反射技术

拔取硬编码情势创建一个目的,必然会带动对象期间的切实可行依赖。一种最简易的艺术是将反射技术与布局文件相结合,在切切实实对象拥有一头抽象的前提下,通过安排文件拿到具体目标的类型音信,然后使用反射创制相应的对象。例如,在天地对象Order类中,可以这么实现:

public class Order

{

   private static readonly IOrderStrategy orderInsertStrategy =

        LoadInsertStrategy();

   private static IOrderStrategy LoadInsertStrategy()

   {

           //通过配备文件找到切实可行的订单策略对象

               string path =
ConfigurationManager.AppSettings[“OrderStrategyAssembly”];

               string className =
ConfigurationManager.AppSettings[“OrderStrategyClass”];

            //通过反射创制对象实例

            return
(IOrderStrategy)Assembly.Load(path).CreateInstance(className);

   }

}

在布置文件web.config中,配置如下的节:

<add key=”OrderStrategyAssembly” value=”AgileDon.BLL”/>

<add key=”OrderStrategyClass”     value=”BLL.OrderSynchronous”/>

透过引入泛型,我们得以对眼前的逻辑举行中用的包裹,例如定义如下的工厂匡助类。

public static class FactoryHelper<T>

    where T:class

{

    private static T instance = null;

    public static T Create(string typeNameKey,

                                 string nameSpace,

                                 string assemblyPath)

    {

        if (instance == null)

        {

            string typeName = ConfigurationManager.AppSettings[typeNameKey];

            string className = nameSpace + “.” + typeName;

 

 

            instance = (T)Assembly.Load(assemblyPath).

                                CreateInstance(className);

        }

        return instance;

    }

}

注意,
Create()帮助方法中的typeNameKey,是指向现实目的类型的键值。平日指出将其键值赋值为切实对象类型的抽象接口类型名,而相应的值则是目的创设对象的门类名。例如:

<add key=”IOrderStrategy”   
value=”OrderSynchronous”/>

然后,我们得以为属于同一命名空间的类统一定义工厂类,并在里边调用工厂襄助类FactoryHelper的Create()匡助方法。例如,为作业逻辑层的靶子定义工厂类BLLFactory。

    public static class BLLFactory<T>

        where T:class

    {

        public static T Create(string typeNameKey)

        {          

            string nameSpace = ConfigurationManager.AppSettings[“BLLAssembly”];

            string assemblyPath = ConfigurationManager.AppSettings[“BLLPath”];

            return BaseFactory<T>.CreateT(

                          typeNameKey, nameSpace, assemblyPath);

        }

}

针对订单策略对象,对应的配备文件为:

<add key=”BLLAssembly”
     value=”AgileDon.BLL”/>

<add key=”BLLPath”
         value=”AgileDon.BLL”/>

<add key=”IOrderStrategy”
 value=”OrderSynchronous”/>

昨天,大家就足以调用BLLFactory类的Create ()方法,传入类型名以得到实际的靶子。例如:

IOrderStrategy orderInsertStrategy =
BLLFactory<IOrderStrategy>.Create(

    “IOrderStrategy”);

假如急需将订单插入策略从协同修改为异步形式,只需将配置文件中IOrderStrategy键对应的值修改为”OrderAsynchronous”即可。

2、表驱动法

以史为鉴表驱动法【注:参见Steve McConnell作品《代码大全》第18章】的沉思,我们可以行使一个Dictionary集合来保安目的对象与键值之间的照耀关系。当我们需要得到对象时,可以接纳键值对表举办询问,这样就可以使得地消除if语句。例如,可以在Strategy格局中应用表驱动法,将其当作格局的上下文对象,而毋庸执行对政策对象类型的逻辑判断。利用表驱动法,我们也可以裁撤对象期间的现实依赖。

依旧以订单的管理为例。我为订单的管理特别定义了一个OrderManager类,它承受开头化并保持对象表。

public static class OrderManager

{

    private static IDictionary<string,IOrderStrategy>
m_strategyTable;

    static OrderManager()

    {

        Init();

    }

    private static void Init()

    {

        m_strategyTable = new
Dictionary<string,IOrderStrategy>();

        m_strategyTable.Add(“sync”,new OrderSynchronous());

        m_strategyTable.Add(“async”,new OrderAsynchronous());

    }

    public static IOrderStrategy GetOrderStrategy(string strategyKey)

    {

电子商务,        IOrderStrategy strategy;

        if (m_strategyTable.TryGetValue(strategyKey, out strategy))

        {

            return strategy;

        }

        else

        {

            throw new Exception(“不能找到正确的订单策略对象”);

        }

    }

}

在调用OrderManager的GetOrderStrategy()方法时,为提供更好的八面玲珑,寻找政策对象的键值应该置身配置文件中,以避免修改源代码。

string strategyKey =
ConfigurationManager.AppSettings[“StrategyKey”];

IOrderStrategy strategy =
OrderManager.GetOrderStrategy(strategyKey);
咱俩依旧足以提供一个登记格局RegisterStrategy(),用以应对未来或者的扩充。

public static class OrderManager

{

    //其它实现略

    public static void RegisterStrategy(

          string strategyKey,

          IOrderStrategy strategy)

    {

        if (String.IsNullOrEmpty(strategyKey))

        {

            throw new ArgumentNullException(strategyKey);

        }

        if (strategy == null)

        {

            throw new ArgumentNullException(“策略对象无法为null”);

        }

        if (m_strategyTable.ContainsKey(strategyKey))

        {

            throw new ArgumentException(“已经存在键值” +
strategyKey);

        }

        m_strategyTable.Add(strategyKey,strategy);

    }

}

3、依赖注入

 依赖注入(Dependency Injection)是一个特出的隐喻。依赖关系就像是被注入的液体,大家可以在此外时候将借助关系注入到模块中,而不只限于在编译时绑定。既然这种倚重关系是通过注入的艺术成功,就象征我们得以每一天更新,因为注入的液体与模块并无直接关乎。实现依靠注入的前提是面向接口编程,而襄助的技术则是行使反射技术。

看重注入是眼前多数轻量级IoC(控制反转,Inversion of
Control)容器用于破除外部服务与容器服务期间依赖关系的一把利刃。首先,容器服务包含了外部服务接口的定义。然后,看重注入通过动用一个专门的装配器对象,提供外部服务的切切实实贯彻,并其赋值给相应的器皿服务对象。MartinFowler将依靠注入的样式分为两种:构造函数注入(Constructor
Injection)、设置方法注入(Setter Injection)和接口注入(Interface
Injection)。其中,接口注入通过定义接口约束的不二法门实现依靠注入,会给容器带来设计的界定。而构造函数注入与安装方法注入则表现了发出依赖的多少个连接点:构造函数与性能。倘诺构造函数参数或性能对象的花色为架空的接口类型,则发出实际看重的源流在于具体目的的创导。将开创具体对象的职责转移到IoC容器,就足以在运行时为构造函数参数或性能对象传递倚重。

时下,实现了依靠注入的轻量级容器已经应用在诸多框架产品中,如Java平台下的Spring、PicoContainer等。在.NET平台下,常见的借助注入框架包括AutoFac,Ninject,Spring.NET,StructureMap和温泽等。

以Ninject框架为例,我们可以定义那样的Order类:

public class Order

{

    private IOrderStrategy m_strategy;

 

 

    public Order(IOrderStrategy strategy)

    {

        m_strategy = strategy;

    }

    public void Insert(OrderInfo order)

    {

        m_strategy.Insert(order);

    }

}

下一场,我们需要自定义一个OrderModule类,它派生自Ninject.Core.StandardModule类。那是Ninject实现依靠注入的一个特点,它放弃了传统的xml映射文件,而是采取项目绑定的点子,并依照要开创的求实对象分组建立相应的Module类。注意,它不同于从前的解耦方法,因为它对作业逻辑代码没有导致其他入侵与污染。如上定义的Order类,保留了世界对象的自然面貌。使得世界层的开发人员可以直视着力于工作逻辑的兑现。OrderModule类的概念如下所示:

using Ninject.Core;

public class OrderModule:StandardModule

{

    public override void Load()

    {

        Bind<IOrderStrategy>().To<OrderSynchronous>();

    }

}

客户端调用的代码可以经过Ninject提供的IKernel对象拿到Order对象:

OrderModule module = new OrderModule();

IKernel kernal = new StandardKernel(module);

 

Order order = kernal.Get<Order>();

order.Insert(new OrderInfo());

4、惯例优于配备

 使用安排文件尽管可以免去与具体对象期间的看重性,不过,它带动的脍炙人口可扩张性,却是以牺牲系统的可维护性乃至于可靠性为代价的。配置文件很难管理,尤其是在安排信息相对较多的情形下。不管是集中管理依旧分散管理,都留存有的与生俱来的缺点。倘诺利用集中管理,则配备文件过大,既影响属性,也不可能很好地显现配置信息的分类与层次。在.NET中,尽管可以采纳<section></section>对部署文件举办分节,但毕竟不够直观。选取分散管理,则不同大小的布置文件千头万绪,既会给维护者带来管理的阻碍,也不便于部署与应用。使用安排文件越来越不便利调试。开发环境提供的编译期检查,对于配置文件只可以是“望洋兴叹”。所谓“差之毫厘,谬以千里”,小小的一个布局项错误,可能会造成麻烦弥补的巨大损失。为了弥补这么些老毛病,许多产品或框架都提供了专门的配置或管理工具,使用直观的UI界面对配置文件举行操作,但混乱的安排项如故有可能让使用者望而却步。

惯例优于配备(Convention over Configuration)来源于Ruby On
Rails框架的规划意见,也被认为是Rails大获成功的关键因素之一。这里所谓的常规,可以清楚为框架对编程的部分约束,我们得以依照贯彻制订的默认规则,通过反射技术形成目的的创导,对象的搭档,甚至是应用程序的组装。例如在Rails中对MVC情势的实现中,就先行确立了Model、View和Controller的目录结构与命名规范。在这种气象下,大家不需要对元数据举办其他配置。ASP.NET
MVC框架同样选拔了常规优于配备的沉思。接纳常规,尽管在一定水平上损失了系统的油滑,带来的却是非凡的可维护性。同时,它仍是可以够免去系统与具象目的之间的强耦合关系。

常规优于配备的技术并不是相当适合于本文中的订单策略示例。然则,在.NET框架中,有关WebRequest对象的创制,却可以改用惯例优于配备的研究来兑现。图2是WebRequest对象的存续体系:

电子商务 3

图2 WebRequest的类协会

在.NET框架中,创设一个WebRequest实例的措施是调用WebRequest的静态方法Create()

WebRequest myRequest = WebRequest.Create(“http://www.agiledon.com“);

由于,传入的Uri地址其前缀为”http”,由此成立的myRequest对象应该为HttpWebRequest具体对象。即使急需基于不同的Request协议,扩大不同的WebRequest对象,就需要引入一些企划技术,来解除与现实目的创立的依靠。.NET框架的贯彻可以达标如此的目的,但分外复杂,这里不提。我想要介绍的是怎么运用常规优于配备来落实WebRequest对象的扩大。利用“惯例优于配备”的考虑有一个前提,就是大家要对WebRequest对象的命名规范开展常规约束。例如,我们规定具备的WebRequest子类对象均由协和名加上“WebRequest”后缀构成。通过分析传入的Uri,能够收获传输协议的称呼,之后将它与“WebRequest”连接起来,得到WebRequest子类对象的类名,再使用反射技术创制该对象。在WebRequest类中定义如下的Create()静态方法:

public static WebRequest Create(Uri requestUri)

{

   if (requestUri == null)

   {

                 throw new ArgumentNullException(“requestUri”);

   }

   string prefix = requestUri.Scheme.ToLower();

              if (prefix == null)

   {

                 throw new ArgumentNullException(“requestUri”);

   }

   if (prefix.Contains(“”))

   {

          prefix = prefix.Replace(“.”,””);

   }

   StringBuilder typeName = new StringBuilder();

   typeName.Append(“System.Net.”);

   typeName.Append(prefix.Substring(0,1).ToUpper());

   typeName.Append(prefix.ToLower().Substring(1,prefix.Length – 1));

   typeName.Append(“WebRequest”);

 

   return (WebRequest)Activitor.CreateInstance(

          System.Type.GetType(typeName));

}

万一WebRequest的子类对象可以遵守我们的常规,即该类的类型名符合事先制定的正规,立异后的Create()方法就可知运转杰出。以新增Tcp协议的WebRequest对象为例。该协议的Schema为“net.tcp”,由此其类名必须为“NettcpWebRequest”,并放在“System.Net”命名空间下。如若客户端调用WebRequest.Create()方法,并传到“net.tcp://www.agiledon.com”值,则Create()方法就会对该Uri地址举行解析,得到完全的序列名为“System.Net.NettcpWebRequest”,然后,利用反射技术创制该对象。接纳“惯例优于配备”的艺术,可以极大地简化工厂方法的贯彻代码,放弃了繁琐的宏图理念,具有非凡灵活的扩充性以及特出的代码可读性。或许,唯一的遗憾是出于反射技术带来的性质损耗。

利用抽象的方法封装变化,即便是应对急需变化的德政,但它也唯有能排除调用者与被调用者之间的耦合关系。只要还提到具体对象的创始,即便引入了创设型格局,例如Factory
Method形式,具体工厂对象的创造依然是不可或缺的。不要小看这点点劳累,需知“千里之堤,溃于蚁穴”,牵一发而动全身,小麻烦可能会酿成大灾难。对于这些已经被卷入变化的目的,我们还应有学会运用诸如“倚重注入”、“表驱动法”等技巧,彻底排除两者之间的耦合;至于采用何种技术,则需要遵照现实的行使场景做出判断。当然,模块或对象解耦的重中之重前提,则出自封装变化,要求大家针对接口编程,而不是促成。这也是GOF提出的面向对象设计标准。

Leave a Comment.