设计模式:
前言:
推荐几本相关的书:
(1)Head First Design Patterns
曾经买Head First系列的时候买的一本书,是java语言的案例,但是完全不影响你了解设计模式。这系列的书就是有很多图,做快速了解建议买。
(2)大话设计模式
1个月前买的,看作者简介是名老师,里面就是菜鸟和大鸟的对话举出很多例子,案例也相当不错。这本书最起码让我感觉特别不错。
(3)重构与模式
这本是必须要看的一本书,前几张讲了什么是重构,什么是模式。然后两者之间的关系。后边是是讲设计模式的动机,做法,实例,变体。也不分什么创建,行为,结构什么的。最后一章是重构的实现。
一.设计原则:
单一职责原则告诉我们实现类要职责单一;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
1.开闭原则OCP(Open-Close Principle)
【开指的是对扩展开放,关指的对修改关闭。】
我把它理解为“一国两制”原则。一国两制怎么说:香港澳门继承了中国这个类,表示说:一个中国不可改变,但针对与港澳实际情况,他们实行的是资本主义经济。
下面看一个简单的例子:
如果要开始编程开发的话显然你要学一门语言,假如你先学了语言是c#。
//为了编程我学习了c public class Clanguage { public void doStudent(int years) { Console.WriteLine("我学习了" + years + "年"); } } //服务端调用 public class CService { private Clanguage C_language = null; public CService() { C_language = new Clanguage(); } public void doStudent(int yesrs) { C_language.doStudent(yesrs); }
}
//客户端调用
CService server = new CService();
server.doStudent(2);
假如过一段时间我又想进行web开发,这时候是不是我就要在加一个类,接着服务类也要再写个调用。随着开发的过程我有想学些动态语言以加快开发效率是不是要依次类推下去。
这样是不是要来回增加学习语言类class netlanguage,class pythonlanguage,还要修个server。。。。。
//为了编程我学习了c public class Clanguage { public void doStudent(int years) { Console.WriteLine("我学习了" + years + "年"); } } public class Netlanguage { public void doStudent(int years) { Console.WriteLine("我学习了" + years + "年"); } }
........ //服务端调用 public class Service
{
string otype = "";
private Clanguage C_language = null;
private Netlanguage net_language = null;
public Service(string type)
{
otype = type;
if (type == "c")
C_language = new Clanguage();
else if (type == "net")
net_language = new Netlanguage();
}
public void doStudent(int yesrs,)
{
if (otpye == "c")
C_language.doStudent(yesrs);
else if (otype == "net")
net_language.doStudent(yesrs);
}
}
.........
//客户端调用
CService server = new CService(“c”);
server.doStudent(2);
CService server = new CService(“net”);
server.doStudent(2);
..........
这个时候你想到了什么?对的接口,这也是开闭原则的主要思想,面向抽象接口编程而非类编程
public interface Ilanguage { void doStudent(int years); } //为了编程我学习了c public class Clanguage:Ilanguage { public void doStudent(int years) { Console.WriteLine("我学习了" + years + "年"); } } public class netlanguage : Ilanguage { public void doStudent(int years) { Console.WriteLine("我学习了" + years + "年"); } } //服务端调用 public class Service { private Ilanguage language = null; public Service(Ilanguage I_language) { language = I_language; } //节日问候 public void doStudent(int yesrs) { language.doStudent(yesrs); } }
//客户端调用
IClanguage server = new Service(“Clanguage”);
server.doStudent(2);
IClanguage server = new Service(“netlanguage”);
server.doStudent(2);
对比可得:遵循面向接口编程【即遵循开闭原则】的编程我们只需要扩展我们的语言类型,无需改变我们的server。开:扩展开发,闭:修改关闭。
2.单一职责原则RRP(Single Responsibility Principle)
【一个类应该只有一个发生变化的原因,职责分离】
高内聚低耦合这就是我们写程序的目标,但是很多时候高耦合会在不经意间就产生了,这大多是因为职责扩散造成的。这个原则最好理解,又最容易违背这个原则。原因就是职责这个家伙不好确认。
继续看一个例子:
开发过程其实不是跑到工地上干的工人瓦工电工木工什么都会,就会获得老板同事的爱戴{ps:想到辍学那两年水电工的生活。}一个类让它什么都干会有什么问题呢?看下面的例子。
好吧就以工人为例。那时候的生活无非就是吃完饭上班,上完班回去玩手机打牌。。。。【ps:首先接口的名称我想不起来命名为什么。暂且定义为istrive】
public interface istrive { //睡觉 void Shuijiao(); //玩手机 void WanShouji(); //打牌 void DaPao(); //购物 void GouWu(); //刷漆 void Shuaqi(); //布线 void Buxian(); //粉墙 void Fenqiang(); //吊顶 void Diaoding(); }
、、、、、、、、、
//接口的实现
从接口上你发现了什么,奋斗这个接口是把休息的内容和工作的内容放到了一起这个时候,显然不管是工作的内容还是休息的内容都有可能发生改变,这样显然是不合理的,两者会互相影响。
所以就可以改成两个接口
public interface iwork { //刷漆 void Shuaqi(); //布线 void Buxian(); //粉墙 void Fenqiang(); //吊顶 void Diaoding(); } public interface irest { //睡觉 void Shuijiao(); //玩手机 void WanShouji(); //打牌 void DaPao(); //购物 void GouWu(); } 、、、、、、、、、 //接口的实现
这样休息就是休息,工作就是工作互不影响,也不用担心我修改工作类的时候休息类受到影响。这个职责划分有时确实不好划分。
3.依赖倒转原则DIP(Dependency Inversion Principle)
【抽象不应当依赖于细节,细节应当依赖于抽象;高层实现不依赖底层实现。】 想想让你封装一个类的时候你首先会做什么。会先封装接口,再写实现。{#总工说这样处理才是合理的。原因就在这#}。面向接口编程而非实现。这个原则在我看来也是面向对象设计的标志。
举个例子:usb是不是所有的的电脑都能通过usb接口连接。如果联想的usb接口和苹果的usb接口不一样,那么你买了一个200多的USB键盘,结果是不是就不能公用了。
依照上面的例子我们先看一个没有问题又存在问题的例子:
我们来用tankpad下载资料传递到u盘:
public class tankpad { public void download() { Console.WriteLine("下载资料"); } } public class usb { public void insert(tankpad tp) { tp.download(); } }
完全没问题,但是现在我们如果在用usb传递mac下载的资料传递到p盘的时候是不是就没那么顺利了,。因为我们的usb定义了insert的方法只能用作传递tankpad的下载内容。但显然usb是不分电脑型号的。那么这个时候我们就要遵循dip原则。
下面是针对usb传递各中电脑下载资料的方法:
public interface IPC { void download(); } public interface IUsb { void insert(IPC pc); } public class usb : IUsb { public void insert(IPC pc) { pc.download(); } }
这个时候不管是tankpad,还是mac还是其他电脑都可以继承ipc接口,然后一切搞定。抽象不依靠细节,抽象值得就是IPC细节就是各种电脑。当然我们如果把usb也同样抽象,因为也有不同的usb,这样的话两者可以并行开发互补影响,这就是tdd(测试驱动开发,这是后话,会有专门的一个篇章叙述它。)OK下一个原则走起。
4.里氏代换原则Liskov Subsitution Principle(LSP)
【子类可以扩展父类的功能,但不能改变父类原有的功能】
里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
有这么一句话:里氏代换原则是继承复用的一个基础。
检验你是否遵循了里氏代换原则的方法:如果调用的是父类的话,那么换成子类也完全可以运行。
注:我在网上看过一个“企鹅不是鸟”的例子,企鹅是鸟吗?企鹅显然是鸟没时间翻百科全书,在网上搜一下也能发现企鹅是鸟,因为恒温动物分两种鸟类和哺乳类,企鹅是卵生,不哺乳所以他是鸟类。那么我们变成的过程当中是不是就会这样写。
看代码了解这个小栗子:
public interface IBird { //有羽毛 void feather(); //卵生 void ovipara(); //不哺乳 void nolactation(); //会飞 void fly(); }
以上是不是我们在生活当中对鸟类的理解,这个应该没有异议吧,那么接下来我们让企鹅继承这个接口会怎么样?????
发现不能继承他为什么【企鹅不会飞】,继承这个鸟类的接口就会出现问题。这就是违背了LSP原则,为此我们应该是理解了LSP了吧。
5.接口隔离原则Interface Segregation Principle(ISP)
从字面上来讲就是一个不要把接口写的太臃肿。查资料大致说的就是有两种分离方式一种是“定制服务”和“角色隔离”。定制服务:大致来讲就是我针对一个客户端,我的一些方法放到一个接口里,另一个客户端我的一个类放在另一个接口里面。角色隔离:是指一个客户端有多个方法,多个方法写多个接口。
单一职责与接口隔离的异同点:
到了这里,有些人可能觉得接口隔离原则与单一职责原则很相似,其实不然。
第一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。
第二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
【友情提醒:接口也不要分的太细,要不然结果就是接口太多。】
上班是什么:工作就是我努力工作然后领工资。所以就有下面的一个接口
public interface IJob { //工作 void Work(); //领工资 void Getpaid(); }
在你的公司里面同为上班的有一种人事不领工资的,他还发工资。他就是万恶的资本家你的老板:)。
所以针对这一块就要接口隔离。
public interface IWork() { //工作 void Work(); } public interface IGetpaid() { //领工资 void Getpaid(); } //员工 public class staff:IWork, IGetpaid { public void Work() { } public void Getpaid() { } //老板 public class boss:IWork { public void Work() } }
好好理解一下他与单一职责的异同
6.迪米特原则Law of Demeter 又称Least Knowledge Principle(LKP)最少知识原则
【我的理解就是:这个原则不希望类与类之间建立直接联系。】简单来说就是不和陌生人说话。就像北京的租房,你找到的是中介,往外租房的房东也是找到的是中介,你与房东不交流。
降低类本身和成员的访问权限,达到【低耦合,高内聚】是其目的。
【和ISP接口隔离原则一样,限制类与类之间的通信。ISP限制的是宽度,而LoD迪米特原则限制的是通信的广度和深度。】。
外观模式(Facade Pattern)和中介者模式(Mediator Pattern)就使用了迪米特法则。