??xml version="1.0" encoding="utf-8" standalone="yes"?>
一个类对另外一个类的依赖性应当是建立在最的接口上的?span lang="EN-US">
一个接口代表一个角Ԍ不应当将不同的角色都交给一个接口。没有关pȝ接口合ƈ在一P形成一个臃肿的大接口,q是对角色和接口的污染?span lang="EN-US">
不应该强q客户依赖于它们不用的方法。接口属于客P不属于它所在的cdơ结构?span lang="EN-US">?/span>q个说得很明白了(jin)Q再通俗点说Q不要强q客户用它们不用的Ҏ(gu)Q如果强q用户用它们不使用的方法,那么q些客户׃(x)面(f)׃q些不用的Ҏ(gu)的改变所带来的改变?span lang="EN-US">
(跟自己无?span lang="EN-US">)而被影响。拿取款来说Q它Ҏ(gu)不关?span lang="EN-US">?/span>存款操作?/span>?span lang="EN-US">?/span>转帐操作?/span>Q可是它却要受到q两个方法的变化的媄(jing)响?span lang="EN-US">
那么我们该如何解册个问题呢Q参考下囄设计Qؓ(f)每个c都单独设计专门的操作接口,使得它们只依赖于它们关系的方法,q样׃?x)互相?jing)?jin)?span lang="EN-US">
三、实现方法:(x)、用委托分L?span lang="EN-US">
2、用多重(h)承分L?span lang="EN-US">
单的_(d)依赖倒置原则要求客户端依赖于抽象耦合。原则表qͼ(x)phpma开?|江渔R
抽象不应当依赖于l节Q细节应当依赖于抽象Q?span lang="EN-US">
要针Ҏ(gu)口编E,不针对实现编E?span lang="EN-US">
反面例子Q?span lang="EN-US">
~点Q耦合太紧密,Light发生变化媄(jing)?span lang="EN-US">ToggleSwitch?span lang="EN-US">
解决办法一Q?span lang="EN-US">
?span lang="EN-US">Light作成AbstractQ然后具体类l承?span lang="EN-US">Light?span lang="EN-US">
优点Q?span lang="EN-US">ToggleSwitch依赖于抽象类LightQ具有更高的E_性,?span lang="EN-US">BulbLight?span lang="EN-US">TubeLightl承?span lang="EN-US">LightQ可以根?span lang="EN-US">"开放-闭"原则q行扩展。只?span lang="EN-US">Light不发生变化,BulbLight?span lang="EN-US">TubeLight的变化就不会(x)波及(qing)ToggleSwitch?span lang="EN-US">
~点Q如果用ToggleSwitch控制一台电(sh)视就很困难了(jin)。M能让TVl承?span lang="EN-US">Light吧?span lang="EN-US">
优点Q更为通用、更为稳定?span lang="EN-US">
l论Q?span lang="EN-US">
使用传统q程化程序设计所创徏的依赖关p,{略依赖于细节,q是p糕的,因ؓ(f){略受到l节改变的媄(jing)响。依赖倒置原则使细节和{略都依赖于抽象Q抽象的E_性决定了(jin)pȝ的稳定性?span lang="EN-US">
定义Q如果对于类?span lang="EN-US">S的每一个对?span lang="EN-US">o1Q都有一个类?span lang="EN-US">T的对?span lang="EN-US">o2Q对于L用类?span lang="EN-US">T定义的程?span lang="EN-US">PQ将o2替换?span lang="EN-US">o1Q?span lang="EN-US">P的行Z持不变,则称S?span lang="EN-US">T的一个子cd?/font>
子类型必能够替换它的基cd?span lang="EN-US">LSP又称里氏替换原则?/font>
对于q个原则Q通俗一些的理解是Q父cȝҎ(gu)都要在子cM实现或者重写?/font>
二、D例说明:(x)
对于依赖倒置原则Q说的是父类不能依赖子类Q它们都要依赖抽象类。这U依赖是我们实现代码扩展和运行期内绑定(多态)(j)的基。因Z旦类的用者依赖某个具体的c,那么对该依赖的扩展就无从谈vQ而依赖某个抽象类Q则只要实现?jin)该抽象cȝ子类Q都可以被类的用者用,从而实C(jin)pȝ的扩展?/font>开?/span>但是Q光有依赖倒置原则Qƈ不一定就使我们的代码真正h良好的扩展性和q行期内l定。请看下面的代码Q?/font>
public class Animal
{
private string name;
public Animal(string name)
{
this.name = name;
}
public void Description()
{
Console.WriteLine("This is a(an) " + name);
}
}
//下面是它的子cȝc:(x)开?/span>public class Cat : Animal
{
public Cat(string name)
{
}
public void Mew()
{
Console.WriteLine("The cat is saying like 'mew'");
}
}
//开?/span>public class Dog : Animal
{
public Dog(string name)
{
}
public void Bark()
{
Console.WriteLine("The dog is saying like 'bark'");
}
}
//最后,我们来看客户端的调用Q?/font>
public void DecriptionTheAnimal(Animal animal)
{
if (typeof(animal) is Cat)
{
Cat cat = (Cat)animal;
Cat.Decription();
Cat.Mew();
}
else if (typeof(animal) is Dog)
{
Dog dog = (Dog)animal;
Dog.Decription();
Dog.Bark();
}
}
通过上面的代码,我们可以看到虽然客户端的依赖是对抽象的依赖,但依然这个设计的扩展性不好,q行期绑定没有实现?/font>开?/span>是什么原因呢Q其实就是因Z满里氏替换原则Q子cdCat?span lang="EN-US">Mew()Ҏ(gu)父类Ҏ(gu)没有Q?span lang="EN-US">DogcLBark()Ҏ(gu)父类也没有,两个子类都不能替换父cR这样导致了(jin)pȝ的扩展性不好和没有实现q行期内l定?/font>
现在看来Q一个系l或子系l要拥有良好的扩展性和实现q行期内l定Q有两个必要条gQ第一是依赖倒置原则Q第二是里氏替换原则。这两个原则~Z不可?/font>
我们知道Q在我们的大多数的模式中Q我们都有一个共同的接口Q然后子cd扩展c都d现该接口?/font>
下面是一D原始代码:(x)
if(action.Equals(“add?)
{
//do add action
}
else if(action.Equals(“view?)
{
//do view action
}
else if(action.Equals(“delete?)
{
//do delete action
}
else if(action.Equals(“modify?)
{
//do modify action
}
我们首先惛_的是把这些动作分d来,可能写出如下的代码Q?/font>开?/span>public class AddAction
{
public void add()
{
//do add action
}
}
public class ViewAction
{
public void view()
{
//do view action
}
}
public class deleteAction
{
public void delete()
{
//do delete action
}
}
public class ModifyAction
{
public void modify()
{
//do modify action
}
}
开?/span>下面我们来看看命令模式对该问题的解决Ҏ(gu)Q?/font>
public interface Action
{
public void doAction();
}
//然后是各个实玎ͼ(x)
public class AddAction : Action
{
public void doAction()
{
//do add action
}
}
public class ViewAction : Action
{
public void doAction()
{
//do view action
}
}
public class deleteAction : Action
{
public void doAction()
{
//do delete action
}
}
public class ModifyAction : Action
{
public void doAction()
{
//do modify action
}
}
//q样Q客L(fng)的调用大概如下:(x)
public void execute(Action action)
{
action.doAction();
}
看,上面的客L(fng)代码再也没有出现q?span lang="EN-US">typeofq样的语句,扩展性良好,也有?jin)运行期内绑定的优点?
三?span lang="EN-US">LSP优点Q?/font>
1、保证系l或子系l有良好的扩展性。只有子c能够完全替换父c,才能保证pȝ或子pȝ在运行期内识别子cd可以?jin),因而得系l或子系l有?jin)良好的扩展性?/font>
2、实现运行期内绑定,即保证了(jin)面向对象多态性的利q行。这节省?jin)大量的代码重复或冗余。避免了(jin)cMinstanceofq样的语句,或?span lang="EN-US">getClass()q样的语句,q些语句是面向对象所忌讳的?/font>
3、有利于实现契约式编E。契U式~程有利于系l的分析和设计,指我们在分析和设计的时候,定义好系l的接口Q然后再~码的时候实现这些接口即可。在父类里定义好子类需要实现的功能Q而子cd要实现这些功能即可?/font>
四、?span lang="EN-US">LSP注意点:(x)
1、此原则?span lang="EN-US">OCP的作用有点类|其实q些面向对象的基本原则就2条:(x)1Q面向接口编E,而不是面向实玎ͼ2Q用l合而不d用(h)?/font>
2?span lang="EN-US">LSP是保?span lang="EN-US">OCP的重要原?/font>
3、这些基本的原则在实现方法上也有个共同层ơ,是使用中间接口层,以此来达到类对象的低偶合Q也是抽象偶合Q?/font>
4、派生类的退化函敎ͼ(x)zcȝ某些函数退化(变得没有用处Q,Base的用者不知道不能调用fQ会(x)D替换q规。在zcM存在退化函数ƈ不L表示q反?span lang="EN-US">LSPQ但是当存在q种情况Ӟ应该引v注意?/font>
5、从zcL出异常:(x)如果在派生类的方法中d?jin)其基类不?x)抛出的异常。如果基cȝ使用者不期望q些异常Q那么把他们d到派生类的方法中可以能?x)导致不可替换性?span lang="EN-US">
SRP优点Q?/span>开?span lang="EN-US">com
SRP注意点:(x)、一个合理的c,应该仅有一个引起它变化的原因,卛_一职责Q?span lang="EN-US">
2、在没有变化征兆的情况下应用SRP或其他原则是不明智的Q?span lang="EN-US">
3、在需求实际发生变化时应该应?span lang="EN-US">SRP
class Client
{
Server server;
void GetMessage()
{
server.Message();
}
}
class Server
{
void Message();
}
下面Z改后W合OCP原则的实玎ͼ我们看到ServercL?span lang="EN-US">ClientInterfacel承的,不过ClientInterface却不?span lang="EN-US">ServerInterfaceQ原因是我们希望?span lang="EN-US">Client来说ClientInterface是固定下来的Q变化的只是Server。这实际上就变成?jin)一U策略模?span lang="EN-US">(Gof StrategyQ?span lang="EN-US">
interface ClientInterface
{
public void Message();
//Other functions
}
class Server:ClientInterface
{
public void Message();
}
class Client
{
ClientInterface ci;
public void GetMessage()
{
ci.Message();
}
public void Client(ClientInterface paramCi)
{
ci=paramCi;
}
}
//那么在主函数(或主控端)?span lang="EN-US">
public static void Main()
{
ClientInterface ci = new Server();
//在上面如果有新的Servercd要替?span lang="EN-US">Server()p?jin)?span lang="EN-US">
Client client = new Client(ci);
client.GetMessage();
}
、例子三
使用Template Method实现OCPQ?span lang="EN-US">
public abstract class Policy
{
private int[] i ={ 1, 1234, 1234, 1234, 132 };
public bool Sort()
{
SortImp();
}
protected virtual bool SortImp()
{
}
}
class Bubbleimp : Policy
{
protected override bool SortImp()
{
//冒(chng)排序
}
}
class Bintreeimp : Policy
{
protected override bool SortImp()
{
//二分法排?span lang="EN-US">
}
}
//dC实现
static void Main(string[] args)
{
//如果要用冒泡排序,只要把下面的Bintreeimp改ؓ(f)Bubbleimp
Policy sort = new Bintreeimp();
sort.Sort();
}
三?span lang="EN-US">OCP优点Q?/b>
1、降低程序各部分之间的耦合性,使程序模块互换成为可能;
2、软g各部分便于单元测试,通过~制与接口一致的模拟c(MockQ,可以很容易地实现软g各部分的单元试Q?span lang="EN-US">
3、利于实现Y件的模块的呼唤,软g升时可以只部v发生变化的部分,而不?x)?jing)响其它部分;
四、?span lang="EN-US">OCP注意点:(x)
1、实?span lang="EN-US">OCP原则的关键是抽象Q?span lang="EN-US">
2、两U安全的实现开闭原则的设计模式是:(x)Strategy patternQ策略模式)(j)Q?span lang="EN-US">Template MethordQ模版方法模式)(j)Q?span lang="EN-US">
3、依据开闭原?span lang="EN-US">,我们量不要修改c?span lang="EN-US">,只扩展类,但在有些情况下会(x)出现一些比较怪异的状况,q时可以采用几个c进行组合来完成Q?span lang="EN-US">
4、将可能发生变化的部分封装成一个对?span lang="EN-US">,?span lang="EN-US">: 状?span lang="EN-US">, 消息,,法,数据l构{等 , 装变化是实?span lang="EN-US">"开闭原?span lang="EN-US">"的一个重要手D,如经常发生变化的状态?span lang="EN-US">,如温?span lang="EN-US">,气压,颜色,U分,排名{等,可以这些作为独立的属?span lang="EN-US">,如果参数之间有关p?span lang="EN-US">,有必要进行抽象。对于行?span lang="EN-US">,如果是基本不变的,则可以直接作为对象的Ҏ(gu),否则考虑抽象或者封装这些行为;
5、在许多斚wQ?span lang="EN-US">OCP是面向对象设计的核心(j)所在。遵循这个原则可带来面向对象技术所声称的巨大好处(灉|性、可重用性以?qing)可l护性)(j)。然而,对于应用E序的每个部分都肆意地进行抽象ƈ不是一个好L。应该仅仅对E序中呈现出频繁变化的那部分作出抽象。拒l不成熟的抽象和抽象本n一样重要;
· Encapsulate what varies.
· Code to an interface rather than to an implementation.
· Favor Composition Over Inheritance单一职责
be focused on carrying out that single responsibility .
1. SRP原则?
2.
3. 让这个系l更Ҏ(gu)理l护Q因Z是所有的问题都搅在一赗?
4. Cohesion 其实?span lang="EN-US">SRP原则的另外一个名?span lang="EN-US">.你写?jin)高内聚的Y件其实就是说你很好的应用?span lang="EN-US">SRP原则?
5.
6. SRP或者Q何其它的原则都是不明智的?
.
1. 很简单,但却是确保我们代码容易维护和复用的关键?
2. DRY可就不只是在说代码了(jin)?
3. x(chng)的是pȝ内的信息和行为都攑֜一个单一的,明显的位|。就像你可以猜到正则表达式在.net中的位置一P因ؓ(f)合理所以可以猜到?
4. 原则Q如何对pȝ职能q行良好的分Ԍ职责清晰的界限一定程度上保证?jin)代码的单一性?span lang="EN-US">
开闭原?span lang="EN-US">
for modification .
1. x(chng)的是灉|性,改动是通过增加代码q行的,而不是改动现有的代码Q?
2. 的应用限定在可能?x)发生的变化上,通过创徏抽象来隔M后发生的同类变化
3. 原则传递出来这样一个思想Q一旦你写出来了(jin)可以工作的代码,p努力保证q段代码一直可以工作。这可以说是一个底Uѝ稍微提高(sh)点要?span lang="EN-US">,一旦我们的代码质量C(jin)一个水qI我们要尽最大努力保证代码质量不回退。这L(fng)要求使我们面对一个问题的时候不?x)用凑zȝҎ(gu)来解冻I或者说是放任自的方式来解决一个问题;比如代码d?jin)无数对特定数据的处理,特化的代码越来越多,代码意图开始含混不清,开始退化?
4. 背后的机Ӟ(x)装和抽象;闭是徏立在抽象基础上的Q用抽象获得显C的闭Q(h)承是OCP最单的例子。除?jin)子cd和方法重载我们还有一些更优雅的方法来实现比如l合Q?
OCP背后的机制就是抽象和多?span lang="EN-US">
5. .对程序中的每一个部分都肆意的抽象不是一个好LQ正的做法是开发h员(sh)仅对频繁变化的部分做出抽象。拒l不成熟的抽象和抽象本n一样重要?
6. ?span lang="EN-US">OOD很多说法的核?j),如果q个原则有效应用Q我们就可以h强的可维护性可重用 灉|?健壮?span lang="EN-US"> LSP?span lang="EN-US">OCP成ؓ(f)可能的主要原则之一
for their base types.
1. x(chng)的是怎样良好的用(h)?span lang="EN-US">.
2. Methodq是要扩展它Q但是绝对不是改变它?
3. 清晰的指出,OOD?span lang="EN-US">IS-A关系是就行ؓ(f)方式而言Q行为方式是可以q行合理假设的,是客L(fng)序所依赖的?
4. 让我们得Z个重要的l论Q一个模型如果孤立的看,q不h真正意义的有效性。模型的有效性只能通过它的客户E序来表现。必L据设计的使用者做出的合理假设来审视它。而假设是难以预测的,直到设计臭味出现的时候才处理它们?
5. LSP的违反也潜在的违反了(jin)OCP
Q依赖倒置原则
1.
2.
3. DIP我们?x)发现往(xin)往(xin)是客h有抽象的接口Q而服务者从q些抽象接口z?
4. Hollywood原则:"Don't call us we'll call you."底层模块实现?jin)在高层模块声明q被高层模块调用的接口?
5.
6. 的简单的启发规则Q依赖于抽象Q这是一个简单的陈述Q该规则不应该依赖于具体的类Q也是说程序汇L有的依赖都应该种植于抽象cL者接口?
7.
8.
9.
接口隔离原则
1. Nl方法,那么q个接口需要?span lang="EN-US">ISP处理一下?
2.
3.
4. ISP之后Q客L(fng)序看到的是多个内聚的接口?
׃个类而言Q应该仅有一个引起它变化的原因?/span>
OCP 开?/span>———?/span>闭原则
软g实体Q类、模块、函数等Q应该是可以开展的Q但是不可修攏V?/span>
LSP Liskov 替换原则
子类型必能够替换掉它们的父cd?/span>
DIP 依赖倒置原则
抽象不应该依赖于l节。细节应该依赖于抽象?/span>
ISP 接口隔离原则
不应该强q客户依赖于它们不用的方法。接口属于客P不属于它所在的cdơ结?/span>
REP 重用发布{h(hun)原则
重用的粒度就是发布的_度
CCP 共同闭原则
包中的所有类对于同一cL质的变化应该是共同闭的。一个变化若对一个包产生影响Q则对该包中的所有类产生影响Q而对于其它的包不造成M影响?/span>
CRP 共同重用原则
一个包中的所有类应该是共同重用的。如果重用了(jin)包中的一个类Q那么就要重用包中的所有类?/span>
ADP 无环依赖原则
在包的依赖关pd中不允许存在环?/span>
SDP E_依赖原则
朝着E_的方向进行依?/span>
SAP E_抽象原则
包的抽象E度应该和其E_E度一致?/span>
里氏代换原则中说QQ何基cd以出现的地方Q子cM定可以出现?span lang=EN-US> LSP是(h)承复用的基石Q只有当衍生cd以替换掉基类QY件单位的功能不受到媄(jing)响时Q基cL能真正被复用Q而衍生类也能够在基类的基上增加新的行为?span lang=EN-US>
里氏代换原则是对“开-?span lang=EN-US>”原则的补充。实?span lang=EN-US>“开-?span lang=EN-US>”原则的关键步骤就是抽象化。而基cM子类的(h)承关pd是抽象化的具体实玎ͼ所以里氏代换原则是对实现抽象化的具体步骤的规范?span lang=EN-US>
一般而言Q违反里氏代换原则的Q也q背“开-?span lang=EN-US>”原则Q反q来不一定成立?span lang=EN-US>
LSP讲的是基cd子类的关pR只有当q种关系存在Ӟ里氏代换关系才存在。如果两个具体的c?span lang=EN-US>AQ?span lang=EN-US>B之间的关p违反了(jin)LSP的设计,(假设是从B?span lang=EN-US>A的(h)承关p?span lang=EN-US>)那么Ҏ(gu)具体的情况可以在下面的两U重构方案中选择一U?span lang=EN-US>
-----创徏一个新的抽象类CQ作Z个具体类的超c,?span lang=EN-US>AQ?span lang=EN-US>B的共同行为移动到C中来解决问题?span lang=EN-US>
-----?span lang=EN-US>B?span lang=EN-US>A的(h)承关pL为委zօpR?span lang=EN-US>
Z(jin)说明Q我们先用第一U方法来看一个例子,W二U办法在另外一个原则中说明。我们就看那个著名的长方形和正方形的例子。对于长方Ş的类Q如果它的长宽相{,那么它就是一个正方ŞQ因此,长方形类的对象中有一些正方Ş的对象。对于一个正方Ş的类Q它的方法有?span lang=EN-US>setSide?span lang=EN-US>getSideQ它不是长方形的子类Q和长方形也不会(x)W合LSP?span lang=EN-US>
eg:
长方形类Q?span lang=EN-US>
public class Rectangle{
...
setWidth(int width){
this.width=width;
}
setHeight(int height){
this.height=height
}
}
正方形类Q?span lang=EN-US>
public class Square{
...
setWidth(int width){
this.width=width;
this. height=width;
}
setHeight(int height){
this.setWidth(height);
}
}
例子中改变边长的函数Q?span lang=EN-US>
public void resize(Rectangle r){
while(r.getHeight()<r.getWidth){
r.setHeight(r.getWidth+1);
}
}
那么Q如果让正方形当做是长方形的子类Q会(x)出现什么情况呢Q我们让正方形从长方形(h)承,然后在它的内部设|?span lang=EN-US>width{于heightQ这P只要width或?span lang=EN-US>height被赋|那么width?span lang=EN-US>height?x)被同时赋|q样׃证了(jin)正方形类中,width?span lang=EN-US>heightL相等?span lang=EN-US>.现在我们假设有个客户c,其中有个Ҏ(gu)Q规则是q样的,试传入的长方Ş的宽度是否大于高度,如果满停止下来,否则增加宽度的倹{现在我们来看,如果传入的是基类长方形,q个q行的很好。根?span lang=EN-US>LSPQ我们把基类替换成它的子c,l果应该也是一L(fng)Q但是因为正方Şcȝwidth?span lang=EN-US>height?x)同时赋|q个Ҏ(gu)没有l束的时候,条gL不满I也就是说Q替换成子类后,E序的行为发生了(jin)变化Q它不满?span lang=EN-US>LSP?span lang=EN-US>
那么我们用第一U方案进行重构,我们构造一个抽象的四边形类Q把长方形和正方形共同的行ؓ(f)攑ֈq个四边形类里面Q让长方形和正方形都是它的子c,问题?span lang=EN-US>OK?jin)。对于长方Ş和正方ŞQ取width?span lang=EN-US>height是它们共同的行ؓ(f)Q但是给width?span lang=EN-US>height赋|两者行Z同,因此Q这个抽象的四边形的cd有取值方法,没有赋值方法。上面的例子中那个方法只?x)适用于不同的子类Q?span lang=EN-US>LSP也就不会(x)被破坏?span lang=EN-US>
在进行设计的时候,我们量从抽象类l承Q而不是从具体cȝ(h)ѝ如果从l承{?wi)来看,所有叶子节点应当是具体c,而所有的?wi)枝节点应当是抽象类或者接口。当然这个只是一个一般性的指导原则Q用的时候还要具体情况具体分析?/span>