软件设计模式(Design pattern)是一套被反复使用的代码设计经验总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。好的设计,成就好的做品。但在软件设计的过程当中,如有一些设计原则(Design Principle)的约束,那咱们的软件会重构得更好。设计模式和设计原则博大精深,须要咱们长时间的实践和总结才能真正领悟到其真谛,本章首先以“观察者模式”为例,介绍设计模式在Windows Forms中的应用(其余经常使用设计模式略),以后详细介绍五大设计原则(简称Solid原则)。数据库
程序的运行意味着模块与模块之间、对象与对象之间不停地有数据交换,观察者模式强调的就是,当一个目标自己的状态发生改变时(或者知足某一条件),它会主动发出通知,通知对该变化感兴趣的其余对象,若是将通知者称为“Subject”(主体),将被通知者称为“Observer”(观察者),具体结构图以下:编程
图9-1 观察者模式中类关系图设计模式
如上图9-1所示,图中将主体和观察者的逻辑抽象出来两个接口,分别为:ISubject和IObserver,ISubject接口中包含一个通知观察者的NotifyObservers方法、一个添加观察者的AddObserver方法和一个RemoveObserver方法,IObserver接口中则只包含一个接受通知的Notify方法,ISubject和IObserver接口的关系为:一对多,一个主体能够通知多个观察者。框架
具体代码实现以下:ide
1 //Code 9-1 2 3 interface ISubject //NO.1 4 5 { 6 7 void NotifyObservers(string msg); 8 9 void AddObserver(IObserver observer); 10 11 void RemoveObserver(IObserver observer); 12 13 } 14 15 interface IObserver //NO.2 16 17 { 18 19 void Notify(string msg); 20 21 } 22 23 class MySubject:ISubject 24 25 { 26 27 //… 28 29 ArrayList _observers_list = new ArrayList(); 30 31 public void AddObserver(IObserver observer) //NO.3 32 33 { 34 35 if(!_observers_list.Contains(observer)) 36 37 { 38 39 _observers_list.Add(observer); 40 41 } 42 43 } 44 45 public void RemoveObserver(IObserver observer) //NO.4 46 47 { 48 49 if(_observers_list.Contains(observer)) 50 51 { 52 53 _observers_list.Remove(observer); 54 55 } 56 57 } 58 59 public void NotifyObservers(string msg) //NO.5 60 61 { 62 63 foreach(IObserver observer in _observers_list) 64 65 { 66 67 observer.Notify(msg); 68 69 } 70 71 } 72 73 public void DoSomething() 74 75 { 76 77 //… 78 79 if(…) //NO.6 80 81 { 82 83 NotifyObservers(…); 84 85 } 86 87 } 88 89 } 90 91 class MyObserver:IObserver 92 93 { 94 95 public void Notify(string msg) 96 97 { 98 99 Console.WriteLine(“receive msg :” + msg); //NO.7 100 101 } 102 103 } 104 105 class YourObserver:IObserver 106 107 { 108 109 public void Notify(string msg) 110 111 { 112 113 //send email to others NO.8 114 115 } 116 117 }
如上代码Code 9-1中所示,NO.1和NO.2处分别定义了ISubject和IObserver接口,接着定义了一个具体的主体类MySubject,该类实现了ISubject接口,在AddObserver、RemoveObserver分别将观察者加入或者移除集合_observers_list(NO.3和NO.4处),最后在NotifyObservers方法中,遍历_observers_list集合,将通知发送到每一个观察者(NO.5处),注意咱们能够在DoSomething方法中当知足某一条件时,通知观察者(NO.6处)。咱们使用IObserver接口定义了两个具体的观察者MyObserver和YourObserver,在二者的Notify方法中分别按照本身的逻辑去处理通知信息(一个直接将msg打印出来,一个将msg以邮件形式发送给别人)(NO.7和NO.8处)。工具
如今咱们能够将MySubject类对象看成一个具体的主体,将MyObserver类对象和YourObserver类对象当作具体的观察者,那么代码中能够这样去使用:测试
1 //Code 9-2 2 3 ISubject subject = new MySubject(); 4 5 subject.AddObserver(new MyObserver()); //NO.1 6 7 subject.AddObserver(new YourObserver()); //NO.2 8 9 10 11 subject.NotifyObservers(“it's a test!”); //NO.3 12 13 (subject as MySubject).DoSomething(); //NO.4
如上代码Code 9-2所示,咱们向主体subject中添加两个观察者(NO.1和NO.2处),以后使用ISubject.NotifyObservers方法通知观察者(NO.3),另外,咱们还可使用MySubject.DoSomething方法去通知观察者(当某一条件知足时),两个观察者分别会作不一样的处理,一个直接将“it's a test”字符串打印输出,而另外一个则将字符串以邮件的形式发送给别人。this
注:Code 9-2中,咱们不能使用ISubject接口去调用DoSomething方法,而必须先将ISubject类型转换成MySubject类型,由于DoSomething不属于ISubject接口。spa
观察者模式中,整个流程见下图9-2:设计
图9-2 观察者模式中的运行流程
如上图9-2所示,在有些状况中,NO.2处会作一些筛选,换句话说,主体有可能根据条件通知部分观察者,NO.4处虚线框表示可选,若是主体关心观察者的处理结果,那么观察者就应该将本身的处理结果返回给主体。“观察者模式”是全部框架使用得最频繁的设计模式之一,缘由很简单,“观察者模式”分隔开了框架代码和框架使用者编写的代码,它是“好莱坞原则”(Hollywood Principle,don't call us,we will call you)的具体实现手段,而“好莱坞原则”是全部框架都严格遵照的。
Windows Forms框架中的“观察者模式”主要不是经过“接口-具体”这种方式去实现的,更多的是使用.NET中的“委托-事件”去实现,详见下一小节。
在Windows Forms框架中,能够说“观察者模式”无处不在,在第四章讲Winform程序结构时已经有所说明,好比控件处理Windows消息时,最终是以“事件”的形式去通知事件注册者的,那么这里的事件注册者就是观察者模式中的“观察者”,控件就是观察者模式中的“主体”。咱们回忆一下第四章中有关System.Windows.Forms.Control类的代码(部分):
1 //Code 9-3 2 3 class Control:Component 4 5 { 6 7 //… 8 9 public event EventHandler Event1; 10 11 public event EventHandler Event2; 12 13 protected virtual void WndProc(ref Message m) 14 15 { 16 17 switch(m.Msg) 18 19 { 20 21 case 1: //NO.1 22 23 { 24 25 //… 26 27 OnEvent1(…); 28 29 break; 30 31 } 32 33 case 2: //NO.2 34 35 { 36 37 OnEvent2(…); 38 39 break; 40 41 } 42 43 //… 44 45 } 46 47 } 48 49 protected virtual void OnEvent1(EventArgs e) 50 51 { 52 53 if(Event1 != null) 54 55 { 56 57 Event1(this,e); //NO.3 58 59 } 60 61 } 62 63 protected virtual void OnEvent2(EventArgs e) 64 65 { 66 67 if(Event2 != null) 68 69 { 70 71 Event2(this,e); //NO.4 72 73 } 74 75 } 76 }
如上代码Code 9-3所示,在Control类的WndProc窗口过程当中的switch/case块中,会根据不一样的Windows消息去激发不一样的事件(NO.1和NO.2处),因为WndProc是一个虚方法,全部在任何一个Control的派生类中,都可以重写WndProc虚方法,处理Windows消息,而后以“事件”的形式去通知事件注册者。
若是咱们在Form1中注册了一个Button类对象btn1的Click事件,那么btn1就是观察者模式中的“主体”,Form1(的实例)就是观察者模式中的“观察者”,以下代码:
1 //Code 9-4 2 3 class Form1:Form 4 5 { 6 7 //… 8 9 public Form1() 10 11 { 12 13 InitializeComponent(); 14 15 btn1.Click += new EventHandler(btn1_Click); //NO.1 16 17 } 18 19 private void btn1_Click(object sender,EventArgs e) //NO.2 20 21 { 22 23 //… 24 25 } 26 27 }
如上图Code 9-4代码所示,咱们在Form1的构造方法中注册了btn1的Click事件(NO.1处),那么btn1就是“主体”,Form1(的实例)就是“观察者”,当btn1须要处理Windows消息时,就会激发事件,通知Form1(的实例)。
Windows Forms框架正是使用“观察者模式”实现了框架代码与框架使用者编写的代码相分离。
注:咱们能够认为,事件的发布者等于观察者模式中的“主体”(Subject),而事件的注册者等于观察者模式中的“观察者”,有关“事件编程”,请参考第六章。
“Solid原则”表明软件设计过程当中常见的五大原则,分别为:
(1)S:单一职责原则(Single Responsibility Principle):
一个类应该只负责一个(种)事情;
(2)O:开闭原则(Open Closed Principle):
优先选择在已有的类型基础上扩展新的类型,避免修改已有类型(已有代码);
(3)L:里氏替换原则(Liskov Substitution Principle):
任何基类出现的地方,派生类必定能够代替基类出现,言下之意就是,派生类必定要具有基类的全部特性;
(4)I:接口隔离原则(Interface Segregation Principle):
一个类型不该该去实现它不须要的接口,换句话说,接口应该只包含同一类方法或属性等;
(5)D:依赖倒置原则(Dependency Inversion Principle):
高层模块不该该依赖于低层模块,高层模块和低层模块应该同时依赖于一个抽象层(接口层)。
设计模式相对来说更具体,每种设计模式几乎都能解决现实生活中某一具体问题,而设计原则相对来说更抽象,它是咱们在软件设计过程当中的行为准则,并不能用在某一具体情景之中。以上五大原则单从字面上理解起来不太直观,下面依次举例说明之。
“一个类应该只负责一个(种)事情”,缘由很简单,负责的事情越多,那么这个类型出错或者须要修改的几率越大,假如如今有一个超市购物的会员类VIP:
1 //Code 9-5 2 3 class VIP:IData 4 5 { 6 7 public void Read() 8 9 { 10 11 try 12 13 { 14 15 //read db here… 16 17 } 18 19 catch(Exception ex) 20 21 { 22 23 System.IO.File.WriteAllText(@"c:\errorlog.txt", ex.ToString()); //NO.1 24 25 } 26 27 } 28 29 } 30 31 interface IData //NO.2 32 33 { 34 35 void Read(); 36 37 }
如上代码Code 9-5所示,定义了一个访问数据库的IData接口(NO.2处),该接口包含一个Read方法,用来读取会员信息,会员类VIP实现了IData接口,在编写Read方法时,咱们捕获访问数据库的异常后,直接将错误信息写入到了日志文件(NO.1处)。这段代码看似没有任何问题,可是后期确会暴露出设计不合理的现象,若是咱们如今不想把日志文件输出到本地C盘(NO.1处),而是输出到D盘,那咱们须要修改VIP的源码,没错,原本咱们只是想修改日志部分的逻辑,如今却不得不更改VIP类的代码。出现这种现象的缘由就是VIP类干了本不该该它干的事情:记录日志。就像下面这张图描述的:
图9-3 一个负责了太多事情的工具
如上图9-3所示,一把包含太多功能的刀,若是哪天某个功能坏掉,咱们不得不将整把刀送去维修。正确解决以上问题的作法就是将日志逻辑与VIP类分开,代码以下:
1 //Code 9-6 2 3 class Logger //NO.1 4 5 { 6 7 public void WriteLog(string error) 8 9 { 10 11 System.IO.File.WriteAllText(@"c:\errorlog.txt", error); 12 13 } 14 15 } 16 17 class VIP:IData 18 19 { 20 21 private Logger _logger = new Logger(); //NO.2 22 23 public void Read() 24 25 { 26 27 try 28 29 { 30 31 //read db here… 32 33 } 34 35 catch (Exception ex) 36 37 { 38 39 _logger.WriteLog(ex.ToString()); //NO.3 40 41 } 42 43 } 44 }
如上代码Code 9-6所示,咱们定义了一个类型Logger专门负责记录日志(NO.1处),在VIP类中经过Logger类型来记录错误信息(NO.2和NO.3处),这样一来,当咱们须要修改日志部分的逻辑时,不须要再动VIP类的代码。
单一职责原则提倡咱们将复杂的功能拆分开来,分配到每一个单独的类型当中,至于什么是复杂的功能,到底将功能拆分到什么程度,这个是没有标准的,若是记录日志是一个繁琐的过程(本小节示例代码相对简单),你还能够将日志类Logger的功能再继续拆分。
“优先选择在已有的类型基础上扩展新的类型,避免修改已有类型(已有代码)”,修改已有代码就意味着须要从新测试原有的功能,由于任何一次修改均可能影响已有功能。若是在普通VIP顾客的基础之上,多了白银会员(silver vip)顾客,这两种顾客在购物时的折扣不同,若是VIP类定义以下(不全):
1 //Code 9-7 2 3 class VIP:IData 4 5 { 6 7 private int _viptype; //vip type NO.1 8 9 //… 10 11 public virtual void Read() 12 13 { 14 15 //… 16 17 } 18 19 public double GetDiscount(double totalSales) 20 21 { 22 23 if(_viptype == 1) //vip 24 25 { 26 27 return totalSales – 10; //NO.2 28 29 } 30 31 else //silver vip 32 33 { 34 35 return totalSales – 50; //NO.3 36 37 } 38 39 } 40 41 }
如上代码Code 9-7所示,咱们在定义VIP类的时候,使用_viptype字段来区分当前顾客是普通VIP仍是白银VIP(NO.1处),在打折方法GetDiscount中,根据不一样的VIP种类返回不一样打折后的价格(NO.2和NO.3处),这段代码的确也能够运行的很好,可是后期仍是会暴露出设计不合理的地方,若是如今不止增长一个白银会员,还增长了一个黄金会员(gold vip),那么咱们不得再也不去修改GetDiscount方法中的if/else块,修改意味着原有功能可能会出现bug,所以咱们不得再也不去测试以前全部使用到了VIP这个类型代码。出现这个问题的主要缘由就是咱们从一开始设计VIP类的时候就不合理:没有考虑到未来可能会有普通会员的衍生体出现。
若是咱们一开始在设计VIP类的时候就应用了面向对象思想,咱们的VIP类能够这样定义:
1 //Code 9-8 2 3 interface IDiscount //NO.1 4 5 { 6 7 double GetDiscount(double totalSales); 8 9 } 10 11 class VIP:IData,IDiscount 12 13 { 14 15 //… 16 17 public virtual void Read() 18 19 { 20 21 //… 22 23 } 24 25 public virtual double GetDiscount(double totalSales) //NO.2 26 27 { 28 29 return totalSales – 10; 30 31 } 32 33 } 34 35 class SilverVIP:VIP 36 37 { 38 39 //… 40 41 public override double GetDiscount(double totalSales) 42 43 { 44 45 return totalSales – 50; //NO.3 46 47 } 48 49 } 50 51 class GoldVIP:SilverVIP 52 53 { 54 55 //… 56 57 public override double GetDiscount(double totalSales) 58 59 { 60 61 return totalSales – 100; //NO.4 62 63 } 64 65 }
如上代码Code 9-8所示,咱们定义了一个IDiscount的接口(NO.1处),包含一个打折的GetDiscount方法,接下来让VIP类实现了IDiscount接口,将接口中的GetDiscount方法定义为虚方法(NO.2处),后面的白银会员(SilverVIP)继承自VIP类、黄金会员(GoldVIP)继承自SilverVIP类,并分别重写GetDiscount虚方法,返回相应的打折以后的总价格(NO.3和NO.4处)。这样一来,新增长会员类型不须要去修改VIP类,也不影响以前使用了VIP类的代码。
下图9-4显示了从新设计VIP类的先后区别:
图9-4 继承发生以后
如上图9-4所示,图中左边部分表示不采用继承的方式去实现普通VIP、白银VIP和黄金VIP的打折逻辑,能够看出,每次须要增长一种会员时,都必须去修改VIP类的代码,图中右边部分表示采用继承方式以后,每种会员均定义成一个类型,每一个类型都可以负责本身的打折逻辑,之后无论新增多少种会员,都可以定义新的派生类,在派生类中定义新的打折逻辑。
注:派生类中只须要重写打折的逻辑,不须要从新去定义读取数据库的逻辑,由于这个逻辑在基类和派生类中并无发生变化。
“任何基类出现的地方,派生类必定能够代替基类出现,言下之意就是,派生类必定要具有基类的全部特性”,意思就是说,若是B是A的儿子,那么B必定能够代替A去作任何事情,不然,B就不该该是A的儿子。咱们在设计类型的时候,每每不去注意一个类型是否真的应该去继承另一个类型,不少时候咱们只是为了听从所谓的“OO”思想。若是如今有一个管理员类Manager,由于管理员也须要读取数据库,因此咱们让它继承自VIP类,代码以下:
1 //Code 9-9 2 3 class Manager:VIP 4 5 { 6 7 //… 8 9 public override void Read() 10 11 { 12 13 //… 14 15 } 16 17 public override double GetDiscount(double totalSales) 18 19 { 20 21 throw new Exception(“don't have this function!”); //NO.1 22 23 } 24 25 }
如上代码Code 9-9所示,咱们定义Manager类,让其继承自VIP类,因为Manager类并无“打折扣”的逻辑,所以咱们重写GetDiscount方法时,抛出“don't have this function!”这样的异常(NO.1处),接下来咱们可能编写出以下这样的代码:
1 //Code 9-10 2 3 List<VIP> vips = new List<VIP>(); //NO.1 4 5 vips.Add(new VIP()); 6 7 vips.Add(new SilverVIP()); 8 9 vips.Add(new GoldVIP()); 10 11 vips.Add(new Manager()); 12 13 //… 14 15 foreach(VIP v in vips) 16 17 { 18 19 /… 20 21 double d = v.GetDiscount(…); //NO.2 22 23 //… 24 25 }
如上代码Code 9-10所示,咱们定义了一个VIP类型的容器(NO.1处),依次将VIP、SilverVIP、GoldVIP以及Manager类型对象加入容器,最后经过foreach遍历该容器,调用容器中每一个元素的GetDiscount方法(NO.2处),此段代码一切正常经过编译,由于编译器认可“基类出现的地方,派生类必定可以代替其出现”,但事实上,程序运行以后,在调用Manager类对象的GetDiscount虚方法时会抛出异常,形成这个现象的主要缘由就是,咱们根本没搞清楚类的继承关系,Manager类虽然也要访问数据库,可是它并不是属于VIP的一种,也就是说,Manager类不该该是VIP类的儿子,以下图9-5:
图9-5Manager错误的继承关系
如上图9-5所示,Manager类虽然须要读取数据库,可是它并不须要有与“折扣”相关的操做,并且它根本不属于一种VIP的衍生物,正确的作法是让Manager类直接实现IData接口便可,以下代码:
1 //Code 9-11 2 3 class Manager:IData 4 5 { 6 7 //… 8 9 public void Read() 10 11 { 12 13 //… 14 15 } 16 17 }
如上代码Code 9-11所示,Manager实现了IData接口以后,再也不跟VIP类有关联,这样一来,前面Code 9-10代码在编译时,就会通不过,
1 //Code 9-12 2 3 List<VIP> vips = new List<VIP>(); //NO.1 4 5 vips.Add(new VIP()); 6 7 vips.Add(new SilverVIP()); 8 9 vips.Add(new GoldVIP()); 10 11 vips.Add(new Manager()); //NO.2
如上代码Code 9-12所示,编译器会在NO.2处报错,缘由很简单,Manager既然不是VIP的派生类了,就不能代替VIP出现。
若是两个类从逻辑上就没有衍生的关系,就不该该有相互继承出现,见下图9-6:
图9-6 没有衍生关系的两个物体
如上图9-6所示,狗跟猫两种动物没有衍生关系,狗类(Dog)不能继承自猫类(Cat),猫类也不能继承自狗类,可是他们均可以同时继承自动物类(Animal)。
“一个类型不该该去实现它不须要的接口,换句话说,接口应该只包含同一类方法或属性等”,若是把全部的方法都放在一个接口中,那么实现了该接口的类型必须实现接口中的所有方法(即便不须要),同理,在一个已经很稳定的系统中,不该该再去修改已经存在的接口,由于这会影响到以前全部实现该接口的类型。如今若是须要新增长一种VIP顾客(SuperVIP),容许它修改数据库,咱们可能这样去修改IData接口:
1 //Code 9-13 2 3 interface IData 4 5 { 6 7 void Read(); 8 9 void Write(); //NO.1 10 11 }
如上代码Code 9-13所示,咱们修改已经存在的IData接口,使其包含一个写数据库的Write方法(NO.1处),知足SuperVIP类的须要,这个方法看似能够,可是它要求咱们修改其余已经实现了IData接口的类型,好比前面的VIP类,只要涉及到VIP类的更改,那么其余全部使用到了VIP类的地方都得从新测试,能够看出,这会影响整个已经存在的系统。正确的作法应该是,新增长一个接口IData2,将数据库的写入方法放在该接口中,让SuperVIP类实现该接口,代码以下:
1 Code 9-14 2 3 interface IData2:IData //NO.1 4 5 { 6 7 void Write(); 8 9 } 10 11 class SuperVIP:IData2,IData 12 13 { 14 15 public void Read() 16 17 { 18 19 //… 20 21 } 22 23 public void Write() 24 25 { 26 27 //… 28 29 } 30 31 }
如上代码Code 9-14所示,咱们定义了一个新的接口IData2(NO.1处),该接口包含一个Write方法,让SuperVIP类实现该接口,这样一来,整个过程不会影响已经存在的VIP类。
“高层模块不该该依赖于低层模块,高层模块和低层模块应该同时依赖于一个抽象层(接口层)”,本原则目的很明确,就是为了下降模块之间的耦合度,咱们观察一下9.2.2小节示例代码中的VIP类和Logger类,很明显,VIP类直接依赖于Logger类,若是咱们想换种方式记录日志的话(也就是改变记录日志的逻辑),必须得从新修改Logger类中的代码,如今若是让VIP类依赖于一个抽象接口ILog,其余全部记录日志的类型同时也依赖于ILog接口,那么整个系统就会更加灵活,
1 Code 9-15 2 3 interface ILog //NO.1 4 5 { 6 7 void Log(string error); 8 9 } 10 11 class FileLogger:ILog //NO.2 12 13 { 14 15 public void Log(string error) 16 17 { 18 19 //write error log to local file 20 21 } 22 23 } 24 25 class EmailLogger:ILog //NO.3 26 27 { 28 29 public void Log(string error) 30 31 { 32 33 //send error log as email 34 35 } 36 37 } 38 39 class NotifyLogger:ILog //NO.4 40 41 { 42 43 public void Log(string error) 44 45 { 46 47 //notify other modules 48 49 } 50 51 } 52 53 class VIP:IData,IDiscount //NO.5 54 55 { 56 57 //… 58 59 ILog _logger; 60 61 public VIP(ILog logger) //NO.6 62 63 { 64 65 _logger = logger; 66 67 } 68 69 public virtual void Read() 70 71 { 72 73 try 74 75 { 76 77 //…read db here 78 79 } 80 81 catch(Exception ex) 82 83 { 84 85 _logger.Log(ex.ToString()); //NO.7 86 87 } 88 89 } 90 91 public virtual double GetDiscount(double totalSales) 92 93 { 94 95 return totalSales – 10; 96 97 } 98 99 }
如上代码Code 9-15所示,咱们定义了一个日志接口ILog做为抽象层(NO.1处),以后定义了各类各样的低层日志模块(NO.二、NO.3和NO.4处),这些记录日志的类均依赖(实现)ILog这个抽象接口,以后咱们在定义VIP类时,再也不让它具体依赖于某个日志类,换句话说,再也不让高层模块直接依赖低层模块,取而代之的是,让VIP类依赖于ILog这个抽象接口(NO.6处),咱们在使用VIP类的时候,能够根据须要给它传递不一样的日志类对象(也能够是除了示例代码中的三个之外自定义类型,只要实现了ILog接口),程序运行后,会将错误日志记录到相应位置(NO.7处)。咱们能够这样使用VIP类:
1 Code 9-16 2 3 IData v = new VIP(new FileLogger()); 4 5 v.Read(); //NO.1 6 7 8 9 IData v2 = new VIP(new EMailLogger()); 10 11 v2.Read(); //NO.2 12 13 14 15 IData v3 = new VIP(new NotifyLogger()); 16 17 v3.Read(); //NO.3
如上代码Code 9-16所示,NO.1处若是出现异常,错误日志会保存到文件,NO.2处若是出现异常,错误日志将会经过邮件发送给别人,NO.3处若是出现异常,VIP对象会自动把错误信息通知给别的模块。
依赖倒置原则提倡模块与模块之间不该该有直接的依赖关系,见下图9-7:
图9-7 依赖倒置发生先后
如上图9-7所示,图中左边部分表示依赖倒置以前高层模块与低层模块之间的依赖关系,图中右边部分表示依赖倒置发生以后,高层模块与低层模块之间的依赖关系,很明显,依赖倒置发生后,高层模块再也不直接受低层模块控制,高层模块与低层模块没有具体的对应关系,灵活性增长,耦合度下降。
图9-8 接力赛跑中的接力棒
如上图9-8所示,接力过程当中先后两人没有具体对应关系。
注:依赖倒置原则是每一个框架都必须遵循的,框架不可能受框架的使用者控制,换句话说,框架做为“高层模块”,不该该依赖于框架使用者编写的代码(低层模块),而应该均依赖于一个抽象层,因此咱们在使用框架编写代码时,大部分时候均以框架库做为基础,从已有的类型或者接口(抽象层)派生出新的类型。
“IT语境中的框架,特指为解决一个开放性问题而设计的具备必定约束性的支撑结构。在此结构上能够根据具体问题扩展、安插更多的组成部分,从而更迅速和方便地构建完整的解决问题的方案。”——摘自互联网
上面是一段摘自互联网上描述“框架”的话,从这段话中咱们了解到,首先,每一个框架解决问题的范围是有限的,好比Windows Forms框架只会帮助咱们完成Windows桌面应用程序的开发,这就是它的“约束性”,其次,框架自己解决不了什么特定的问题,它只给了解决特定问题的相关模块(或者组件)一个可插接、可组合的底子,这个底子为咱们解决实际具体问题提供了支持,这就是框架的“支撑性”,见下图9-9:
图9-9 框架使用先后
如上图9-9所示,图中左边部分表示使用框架以前,整个系统均由开发者编写代码的结构图,咱们能够看见,不管系统的“系统运行逻辑”仍是“业务处理逻辑”均由开发者负责,开发者本身调用本身的代码,整个系统的运行流程由开发者控制;图中右边部分表示使用了框架以后,“系统运行逻辑”由框架接管了,开发者只须要把精力集中在“业务逻辑处理”之上(Windows Forms框架接管了消息循环、消息处理等,负责了整个Winform程序的运转),除此以外,还有一个很是大并且很是重要的改变:开发者再也不(几乎不)本身调用本身的代码了,本身编写的代码均由框架调用,系统运行的控制权交给了框架。这就是全部框架所必须知足的“好莱坞原则”(Hollywood Principle,don't call us,we will call you),“好莱坞原则”跟“控制转换原则”(IoC,Inversion of Control)相似,参见前面章节,能够了解框架是怎样反过来控制程序的运行。
咱们在使用框架开发应用程序去解决实际具体的问题时,框架避免不了会与咱们开发者编写的代码进行交互,这就会产生一个问题,那就是怎样去把握框架代码和框架使用者编写代码二者之间的关联性,也就是咱们常说的“高内聚,低耦合”。“高内聚,低耦合”在框架中要求更高,由于框架的使用人群和范围比通常普通系统更大更普遍,优秀的框架要想使用寿命更长口碑更好,就要求框架能在使用后期可以更容易升级、更方便扩展新的功能来知足使用者的各类须要,而这些大部分取决于框架最开始的设计好坏,正确地使用各类“设计模式”以及严格地遵照各类“设计原则”是决定框架后期可否应付各类变动、升级扩展的重要因素。