??xml version="1.0" encoding="utf-8" standalone="yes"?>
使用多个专门的接口比使用单一的L口要好?span lang="EN-US">
一个类对另外一个类的依赖性应当是建立在最的接口上的?span lang="EN-US">
一个接口代表一个角Ԍ不应当将不同的角色都交给一个接口。没有关pȝ接口合ƈ在一P形成一个臃肿的大接口,q是对角色和接口的污染?span lang="EN-US">
?/span>不应该强q客户依赖于它们不用的方法。接口属于客P不属于它所在的cdơ结构?span lang="EN-US">?/span>q个说得很明白了Q再通俗点说Q不要强q客户用它们不用的ҎQ如果强q用户用它们不使用的方法,那么q些客户׃面׃q些不用的Ҏ的改变所带来的改变?span lang="EN-US">
二、D例说明:
参考下囄设计Q在q个设计里,取款、存ƾ、{帐都使用一个通用界面接口Q也是_每一个类都被依赖了另两个cȝ接口ҎQ那么每个类有可能因为另外两个类的方?span lang="EN-US">(跟自己无?span lang="EN-US">)而被影响。拿取款来说Q它Ҏ不关?span lang="EN-US">?/span>存款操作?/span>?span lang="EN-US">?/span>转帐操作?/span>Q可是它却要受到q两个方法的变化的媄响?span lang="EN-US">
那么我们该如何解册个问题呢Q参考下囄设计Qؓ每个c都单独设计专门的操作接口,使得它们只依赖于它们关系的方法,q样׃会互相媄了!
三、实现方法:
1、用委托分L?span lang="EN-US">
2、用多重承分L?span lang="EN-US">
单的_依赖倒置原则要求客户端依赖于抽象耦合。原则表qͼphpma开?|江渔R
抽象不应当依赖于l节Q细节应当依赖于抽象Q?span lang="EN-US">
要针Ҏ口编E,不针对实现编E?span lang="EN-US">
反面例子Q?span lang="EN-US">
~点Q耦合太紧密,Light发生变化媄?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的变化就不会波及ToggleSwitch?span lang="EN-US">
~点Q如果用ToggleSwitch控制一台电视就很困难了。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糕的,因ؓ{略受到l节改变的媄响。依赖倒置原则使细节和{略都依赖于抽象Q抽象的E_性决定了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ȝҎ都要在子cM实现或者重写?/font>
二、D例说明:
对于依赖倒置原则Q说的是父类不能依赖子类Q它们都要依赖抽象类。这U依赖是我们实现代码扩展和运行期内绑定(多态)的基。因Z旦类的用者依赖某个具体的c,那么对该依赖的扩展就无从谈vQ而依赖某个抽象类Q则只要实现了该抽象cȝ子类Q都可以被类的用者用,从而实Cpȝ的扩展?/font>phpma开?/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:phpma开?/span>
public class Cat : Animal
{
public Cat(string name)
{
}
public void Mew()
{
Console.WriteLine("The cat is saying like 'mew'");
}
}
//下面是它的子cȝc:phpma开?/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>phpma开?/span>
是什么原因呢Q其实就是因Z满里氏替换原则Q子cdCat?span lang="EN-US">Mew()Ҏ父类Ҏ没有Q?span lang="EN-US">DogcLBark()Ҏ父类也没有,两个子类都不能替换父cR这样导致了pȝ的扩展性不好和没有实现q行期内l定?/font>
现在看来Q一个系l或子系l要拥有良好的扩展性和实现q行期内l定Q有两个必要条gQ第一是依赖倒置原则Q第二是里氏替换原则。这两个原则~Z不可?/font>
我们知道Q在我们的大多数的模式中Q我们都有一个共同的接口Q然后子cd扩展c都d现该接口?/font>
下面是一D原始代码:
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>phpma开?/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
}
}
我们可以看到Q这样代码将各个行ؓ独立出来Q满了单一职责原则Q但q远q不够,因ؓ它不满依赖颠倒原则和里氏替换原则?/span>phpma开?/span>
下面我们来看看命令模式对该问题的解决ҎQ?/font>
public interface Action
{
public void doAction();
}
//然后是各个实玎ͼ
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的调用大概如下:
public void execute(Action action)
{
action.doAction();
}
看,上面的客L代码再也没有出现q?span lang="EN-US">typeofq样的语句,扩展性良好,也有了运行期内绑定的优点?
三?span lang="EN-US">LSP优点Q?/font>
1、保证系l或子系l有良好的扩展性。只有子c能够完全替换父c,才能保证pȝ或子pȝ在运行期内识别子cd可以了,因而得系l或子系l有了良好的扩展性?/font>
2、实现运行期内绑定,即保证了面向对象多态性的利q行。这节省了大量的代码重复或冗余。避免了cMinstanceofq样的语句,或?span lang="EN-US">getClass()q样的语句,q些语句是面向对象所忌讳的?/font>
3、有利于实现契约式编E。契U式~程有利于系l的分析和设计,指我们在分析和设计的时候,定义好系l的接口Q然后再~码的时候实现这些接口即可。在父类里定义好子类需要实现的功能Q而子cd要实现这些功能即可?/font>
四、?span lang="EN-US">LSP注意点:
1、此原则?span lang="EN-US">OCP的作用有点类|其实q些面向对象的基本原则就2条:1Q面向接口编E,而不是面向实玎ͼ2Q用l合而不d用?/font>
2?span lang="EN-US">LSP是保?span lang="EN-US">OCP的重要原?/font>
3、这些基本的原则在实现方法上也有个共同层ơ,是使用中间接口层,以此来达到类对象的低偶合Q也是抽象偶合Q?/font>
4、派生类的退化函敎ͼzcȝ某些函数退化(变得没有用处Q,Base的用者不知道不能调用fQ会D替换q规。在zcM存在退化函数ƈ不L表示q反?span lang="EN-US">LSPQ但是当存在q种情况Ӟ应该引v注意?/font>
5、从zcL出异常:如果在派生类的方法中d了其基类不会抛出的异常。如果基cȝ使用者不期望q些异常Q那么把他们d到派生类的方法中可以能会导致不可替换性?span lang="EN-US">
׃个类而言Q应该只专注于做一件事和仅有一个引起它变化的原因?/span>PHP开?span lang="EN-US">com
所谓职责,我们可以理解他ؓ功能Q就是设计的q个cd能应该只有一个,而不是两个或更多。也可以理解为引用变化的原因Q当你发现有两个变化会要求我们修改这个类Q那么你p考虑撤分q个cM。因责是变化的一个uU,当需求变化时Q该变化会反映类的职责的变化?/span>
?/span>像一个hw兼数职Q而这些事情相互关联不大,Q甚x冲突Q那他就无法很好的解册些职责,应该分到不同的hw上d才对?span lang="EN-US">?/span>
二、D例说明:
q反SRP原则代码: PHP开?span lang="EN-US">com
modem接口明显h两个职责Q连接管理和数据通讯Q?span lang="EN-US">
interface Modem
{
public void dial(string pno);
public void hangup();
public void send(char c);
public void recv();
}
如果应用E序变化影响q接函数Q那么就需要重构:
interface DataChannel
{
public void send(char c);
public void recv();
}
interface Connection
{
public void dial(string pno);
public void hangup();
}
三?span lang="EN-US">SRP优点Q?/span>PHP开?span lang="EN-US">com
消除耦合Q减因需求变化引起代码僵化性臭?
四、?span lang="EN-US">SRP注意点:
1、一个合理的c,应该仅有一个引起它变化的原因,卛_一职责Q?span lang="EN-US">
2、在没有变化征兆的情况下应用SRP或其他原则是不明智的Q?span lang="EN-US">
3、在需求实际发生变化时应该应?span lang="EN-US">SRP{原则来重构代码Q?span lang="EN-US">
4、用测试驱动开发会q我们在设计出现臭味之前分M合理代码Q?span lang="EN-US">
5、如果测试不能迫使职责分,僵化性和脆弱性的臭味会变得很强烈Q那应该用Facade?span lang="EN-US">Proxy模式对代码重构;
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。这实际上就变成了一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了.
Client client = new Client(ci);
client.GetMessage();
}
3、例子三
使用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()
{
//冒排序
}
}
class Bintreeimp : Policy
{
protected override bool SortImp()
{
//二分法排?span lang="EN-US">
}
}
//dC实现
static void Main(string[] args)
{
//如果要用冒泡排序,只要把下面的Bintreeimp改ؓ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发生变化的部分,而不会媄响其它部分;
四、?span lang="EN-US">OCP注意点:
1、实?span lang="EN-US">OCP原则的关键是抽象Q?span lang="EN-US">
2、两U安全的实现开闭原则的设计模式是:Strategy patternQ策略模式)Q?span lang="EN-US">Template MethordQ模版方法模式)Q?span lang="EN-US">
3、依据开闭原?span lang="EN-US">,我们量不要修改c?span lang="EN-US">,只扩展类,但在有些情况下会出现一些比较怪异的状况,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">,如果是基本不变的,则可以直接作为对象的Ҏ,否则考虑抽象或者封装这些行为;
5、在许多斚wQ?span lang="EN-US">OCP是面向对象设计的核心所在。遵循这个原则可带来面向对象技术所声称的巨大好处(灉|性、可重用性以及可l护性)。然而,对于应用E序的每个部分都肆意地进行抽象ƈ不是一个好L。应该仅仅对E序中呈现出频繁变化的那部分作出抽象。拒l不成熟的抽象和抽象本n一样重要;
基本原则
· 装变化Encapsulate what varies.
· 面向接口变成而不是实?span lang="EN-US"> Code to an interface rather than to an implementation.
· 优先使用l合而非l承 Favor Composition Over Inheritance
SRP: The single responsibility principle 单一职责
pȝ中的每一个对象都应该只有一个单独的职责Q而所有对象所x的就是自w职责的完成?span lang="EN-US">
Every object in your system should have a single responsibility ,and all the object s services should be focused on carrying out that single responsibility .
1. 每一个职责都是一个设计的变因Q需求变化的时候,需求变化反映ؓc职责的变化。当你系l里面的对象都只有一个变化的原因的时候,你就已经很好的遵循了SRP原则?
2. 如果一个类承担的职责过多,q于把q些职责耦合在了一赗一个职责的变化可能削弱或者抑制这个类其它职责的能力。这U设计会D脆弱的设计。当变化发生的时候,设计会遭到意想不到的破坏?
3. SRP 让这个系l更Ҏ理l护Q因Z是所有的问题都搅在一赗?
4. 内聚Cohesion 其实?span lang="EN-US">SRP原则的另外一个名?span lang="EN-US">.你写了高内聚的Y件其实就是说你很好的应用?span lang="EN-US">SRP原则?
5. 怎么判断一个职责是不是一个对象的呢?你试着让这个对象自己来完成q个职责Q比如:“书自己阅读内容”,阅读的职责显然不是书自己的?
6. 仅当变化发生Ӟ变化的uU才h实际的意义,如果没有征兆Q那么应?span lang="EN-US">SRP或者Q何其它的原则都是不明智的?
DRY : Don't repeat yourself Principle
通过抽取公共部分攄在一个地斚w免代码重?span lang="EN-US">.
Avoid duplicate code by abstracting out things that are common and placing those thing in a single location .
1. DRY 很简单,但却是确保我们代码容易维护和复用的关键?
2. 你尽力避免重复代码候实际上在做一件什么事情呢Q是在确保每一个需求和功能在你的系l中只实Cơ,否则存在浪费!pȝ用例不存在交集,所以我们的代码更不应该重复Q从q个角度?span lang="EN-US">DRY可就不只是在说代码了?
3. DRY x的是pȝ内的信息和行为都攑֜一个单一的,明显的位|。就像你可以猜到正则表达式在.net中的位置一P因ؓ合理所以可以猜到?
4. DRY 原则Q如何对pȝ职能q行良好的分Ԍ职责清晰的界限一定程度上保证了代码的单一性?span lang="EN-US">
OCP : Open-Close Principle开闭原?span lang="EN-US">
cd该对修改关闭Q对扩展打开Q?span lang="EN-US">
Classes should be open for extension ,and closed for modification .
1. OCP x的是灉|性,改动是通过增加代码q行的,而不是改动现有的代码Q?
2. OCP的应用限定在可能会发生的变化上,通过创徏抽象来隔M后发生的同类变化
3. OCP原则传递出来这样一个思想Q一旦你写出来了可以工作的代码,p努力保证q段代码一直可以工作。这可以说是一个底Uѝ稍微提高一点要?span lang="EN-US">,一旦我们的代码质量C一个水qI我们要尽最大努力保证代码质量不回退。这L要求使我们面对一个问题的时候不会用凑zȝҎ来解冻I或者说是放任自的方式来解决一个问题;比如代码d了无数对特定数据的处理,特化的代码越来越多,代码意图开始含混不清,开始退化?
4. OCP 背后的机Ӟ装和抽象;闭是徏立在抽象基础上的Q用抽象获得显C的闭Q承是OCP最单的例子。除了子cd和方法重载我们还有一些更优雅的方法来实现比如l合Q?
怎样在不改变源代码(关闭修改Q的情况下更改它的行为呢Q答案就是抽象,OCP背后的机制就是抽象和多?span lang="EN-US">
5. 没有一个可以适应所有情늚贴切的模型!一定会有变化,不可能完全封?span lang="EN-US">.对程序中的每一个部分都肆意的抽象不是一个好LQ正的做法是开发h员仅仅对频繁变化的部分做出抽象。拒l不成熟的抽象和抽象本n一样重要?
6. OCP?span lang="EN-US">OOD很多说法的核心,如果q个原则有效应用Q我们就可以h强的可维护性可重用 灉|?健壮?span lang="EN-US"> LSP?span lang="EN-US">OCP成ؓ可能的主要原则之一
LSP: The Liskov substitution principle
子类必须能够替换基类?span lang="EN-US">
Subtypes must be substitutable for their base types.
1. LSPx的是怎样良好的用?span lang="EN-US">.
2. 必须要清楚是使用一?span lang="EN-US">Methodq是要扩展它Q但是绝对不是改变它?
3. LSP清晰的指出,OOD?span lang="EN-US">IS-A关系是就行ؓ方式而言Q行为方式是可以q行合理假设的,是客L序所依赖的?
4. LSP让我们得Z个重要的l论Q一个模型如果孤立的看,q不h真正意义的有效性。模型的有效性只能通过它的客户E序来表现。必L据设计的使用者做出的合理假设来审视它。而假设是难以预测的,直到设计臭味出现的时候才处理它们?
5. 对于LSP的违反也潜在的违反了OCP
DIPQ依赖倒置原则
高层模块不应该依赖于底层模块二者都应该依赖于抽?span lang="EN-US">
抽象不应该依赖于l节l节应该依赖于抽?span lang="EN-US">
1. 什么是高层模块Q高层模块包含了应用E序中重要的{略选择和业务模型。这些高层模块其所在的应用E序区别于其它?
2. 如果高层模块依赖于底层模块,那么在不同的上下文中重用高层模块׃变得十分困难。然而,如果高层模块独立于底层模块,那么高层模块可以非常容易的被重用。该原则是框架设计的核心原则?
3. q里的倒置不仅仅是依赖关系的倒置也是接口所有权的倒置。应用了DIP我们会发现往往是客h有抽象的接口Q而服务者从q些抽象接口z?
4. q就是著名的Hollywood原则:"Don't call us we'll call you."底层模块实现了在高层模块声明q被高层模块调用的接口?
5. 通过倒置我们创徏了更灉| 更持久更Ҏ改变的结?
6. DIP的简单的启发规则Q依赖于抽象Q这是一个简单的陈述Q该规则不应该依赖于具体的类Q也是说程序汇L有的依赖都应该种植于抽象cL者接口?
7. 如果一个类很稳定,那么依赖于它不会造成伤害。然而我们自q具体cd多是不稳定的Q通过把他们隐藏在抽象接口后面可以隔离不稳定性?
8. 依赖倒置可以应用于Q何存在一个类向另一个类发送消息的地方
9. 依赖倒置原则是实现许多面向对象技术多宣称的好处的基本底层机制Q是面向对象的标志所在?
ISP:接口隔离原则
不应该强q客L序依赖它们不需要的使用的方法?span lang="EN-US">
1. 接口不是高内聚的Q一个接口可以分?span lang="EN-US">Nl方法,那么q个接口需要?span lang="EN-US">ISP处理一下?
2. 接口的划分是׃用它的客L序决定的Q客L序是分离的接口也应该是分ȝ?
3. 一个接口中包含太多行ؓ时候,D它们的客L序之间生不正常的依赖关p,我们要做的就是分L口,实现解耦?
4. 应用?span lang="EN-US">ISP之后Q客L序看到的是多个内聚的接口?
׃个类而言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原则
重用的粒度就是发布的_度
CCP 共同闭原则
包中的所有类对于同一cL质的变化应该是共同闭的。一个变化若对一个包产生影响Q则对该包中的所有类产生影响Q而对于其它的包不造成M影响?/span>
CRP 共同重用原则
一个包中的所有类应该是共同重用的。如果重用了包中的一个类Q那么就要重用包中的所有类?/span>
ADP 无环依赖原则
在包的依赖关pd中不允许存在环?/span>
SDP E_依赖原则
朝着E_的方向进行依?/span>
SAP E_抽象原则
包的抽象E度应该和其E_E度一致?/span>
里氏代换原则中说QQ何基cd以出现的地方Q子cM定可以出现?span lang=EN-US> LSP是承复用的基石Q只有当衍生cd以替换掉基类QY件单位的功能不受到媄响时Q基cL能真正被复用Q而衍生类也能够在基类的基上增加新的行为?span lang=EN-US>
里氏代换原则是对“开-?span lang=EN-US>”原则的补充。实?span lang=EN-US>“开-?span lang=EN-US>”原则的关键步骤就是抽象化。而基cM子类的承关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违反了LSP的设计,(假设是从B?span lang=EN-US>A的承关p?span lang=EN-US>)那么Ҏ具体的情况可以在下面的两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的承关pL为委zօpR?span lang=EN-US>
Z说明Q我们先用第一U方法来看一个例子,W二U办法在另外一个原则中说明。我们就看那个著名的长方形和正方形的例子。对于长方Ş的类Q如果它的长宽相{,那么它就是一个正方ŞQ因此,长方形类的对象中有一些正方Ş的对象。对于一个正方Ş的类Q它的方法有?span lang=EN-US>setSide?span lang=EN-US>getSideQ它不是长方形的子类Q和长方形也不会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会出现什么情况呢Q我们让正方形从长方形承,然后在它的内部设|?span lang=EN-US>width{于heightQ这P只要width或?span lang=EN-US>height被赋|那么width?span lang=EN-US>height会被同时赋|q样׃证了正方形类中,width?span lang=EN-US>heightL相等?span lang=EN-US>.现在我们假设有个客户c,其中有个ҎQ规则是q样的,试传入的长方Ş的宽度是否大于高度,如果满停止下来,否则增加宽度的倹{现在我们来看,如果传入的是基类长方形,q个q行的很好。根?span lang=EN-US>LSPQ我们把基类替换成它的子c,l果应该也是一LQ但是因为正方Şcȝwidth?span lang=EN-US>height会同时赋|q个Ҏ没有l束的时候,条gL不满I也就是说Q替换成子类后,E序的行为发生了变化Q它不满?span lang=EN-US>LSP?span lang=EN-US>
那么我们用第一U方案进行重构,我们构造一个抽象的四边形类Q把长方形和正方形共同的行ؓ攑ֈq个四边形类里面Q让长方形和正方形都是它的子c,问题?span lang=EN-US>OK了。对于长方Ş和正方ŞQ取width?span lang=EN-US>height是它们共同的行ؓQ但是给width?span lang=EN-US>height赋|两者行Z同,因此Q这个抽象的四边形的cd有取值方法,没有赋值方法。上面的例子中那个方法只会适用于不同的子类Q?span lang=EN-US>LSP也就不会被破坏?span lang=EN-US>
在进行设计的时候,我们量从抽象类l承Q而不是从具体cȝѝ如果从l承{树来看,所有叶子节点应当是具体c,而所有的树枝节点应当是抽象类或者接口。当然这个只是一个一般性的指导原则Q用的时候还要具体情况具体分析?/span>