设计模式综述
1.设计模式是什么
1.百度百科定义
软件设计模式(Software Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
2.维基百科定义
软件设计模式是编程中一类问题的已知解决方案。(a software design pattern is a known solution to a class of problems in programming.)
3.模式的定义
模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案。(A pattern is a successful or efficient solution to a recurring problem within a contex)
4.LZ理解
设计模式是一种设计的解决方案,用于解决特定的软件架构问题.这种解决方案被广泛使用在软件架构设计中。jdk的IO包中使用的装饰器模式和适配器模式。Spring框架就使用的模板模式,代理模式,单例模式,工厂模式等等。
2.设计模式解决了什么问题
使用设计模式是为了帮助我们改善软件系统的设计。增强系统的健壮性(稳定性)、可扩展性(灵活性)。减少重复代码,让代码更容易的被他人理解,增强可读性。另一方面也有助于程序设计的标准化(降低协作成本),提高软件开发效率。
3.设计模式的来源
设计模式”这个术语最初被设计用于建筑学领。Christopher Alexander 在他1977的著作“A PatternLanguage:Towns/Building/Construction”里面描述了一些常见的建筑学设计问题,并解释了如何用这些已有的,著名的模式集合来开始全新 的有效的设计。Alexander的观点被很好的转化到软件开发上来,并且长期的合意的用原有的组件来构造新的解决方案。
4.设计模式六大原则
在学习设计模式之前,先了解下软件设计的六大原则。这些原则是设计模式的指导原则.下面我会编写一些例子用于解释六大原则。注意这些原则优势不一定要严格遵守,还要具体情况具体处理,能遵守尽量遵守。
1.单一职责原则
单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小。单一职责原则定义如下:
单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
单一职责原则告诉我们一个类不要肩负起太多的职责,肩负的职责越多他被复用的肯能性就越小。而且肩负的职责越多这些职责就耦合在一起,当一个功能发生变化时,修改一个职责可能会影响到其他职责。因此将不同的职责封装在不同的类中,多个职责总是同时发生改变的封装在同一个类中。
通俗的来讲就是高内聚、低耦合。他是最简单但有最难运用的原则,需要设计人员发现类的不同职责并将其分离。对设计人员的分析能力、业务能力、实践经验等有很高的要求。
下面举个栗子来说明下这个原则
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class Calculator {
public int add() throws NumberFormatException, IOException{
File file = new File("/Users/yanlong/Documents/data.txt");
BufferedReader br = new BufferedReader(new FileReader(file));
int a = Integer.valueOf(br.readLine());
int b = Integer.valueOf(br.readLine());
br.close();
return a+b;
}
public static void main(String[] args) throws NumberFormatException, IOException {
Calculator calculator = new Calculator();
System.out.println("result:" + calculator.add());
}
}
Calculator类中的add()方法就肩负了2个职责,读取数据和运算。
当我们需求需要添加一个减法时又需要复制一份读取代码然后在计算。当我们需要更多的计算方法的时候,读取代码将会有非常多的重复。这时我们需要更换读取文件方式的时候,代码修改几处地方。结果造成代码一片混乱。
为了解决这个问题我们将读取数据的方法和运算的方法分割开来。各自独立实现自己的职责
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class CalculatorModify {
//这里的返回值应当封装为对象 出于demo就简化此处改为int[]
public int[] readNum() throws NumberFormatException, IOException {
File file = new File("/Users/yanlong/Documents/data.txt");
BufferedReader br = new BufferedReader(new FileReader(file));
int a = Integer.valueOf(br.readLine());
int b = Integer.valueOf(br.readLine());
br.close();
return new int[] {a,b};
}
public int add(int a,int b) {
return a + b;
}
public int sub (int a,int b) {
return a - b;
}
public static void main(String[] args) throws NumberFormatException, IOException {
CalculatorModify calculatorModify = new CalculatorModify();
int[] num = calculatorModify.readNum();
System.out.println("addResult:" + calculatorModify.add(num[0],num[1]));
System.out.println("subResult:" + calculatorModify.sub(num[0],num[1]));
}
}
这样就将各自的职责拆分开了。无论是修改读取方式,还是添加删除运算功能都能很好的兼容。该原则是六大原则中最应该遵守的原则,该原则几乎不会对项目造成过多的复杂性,反而会让你的代码看起来井然有序。
2.里氏替换原则
里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。其严格表述如下:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。这个定义比较拗口且难以理解,因此我们一般使用它的另一个通俗版定义:
**里氏代换原则(Liskov Substitution Principle, LSP):所有引用父类(基类、超类)的地方必须能透明地使用其子类(派生类)的对象。
**
里氏代换原则告诉我们,在软件中将一个父类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用父类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。
public class Client {
public static void main(String[] args) {
SomeoneClass someoneClass = new SomeoneClass();
someoneClass.someoneMethod(new Parent());
someoneClass.someoneMethod(new SubClass());
}
}
class SomeoneClass {
// 有某一个方法,使用了一个父类类型
public void someoneMethod(Parent parent) {
parent.method();
}
}
class Parent {
public void method() {
System.out.println("parent method");
}
}
class SubClass extends Parent {
public void method() {
System.out.println("sub method");
}
}
当执行该方法时控制台输出的内容为:
parent method
sub method
这时子类重写了父类的方法,调用父类的方法失败。该原则是指即便用子类去调用也应当显示出parent method 才符合该原则。即为父类可以用子类替换,而功能不改变,反之则不行。
我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。
3.接口隔离原则
接口隔离原则(角色隔离原则)定义如下:
接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
这里补充下接口的知识。从逻辑上讲接口是一个类型所提供的所有方法特征的集合。而从业务的角度去理解每一个接口代表者一种角色。每一个角色都有他独立的功能(方法)。而接口隔离原则是将一个大的角色,分割成几个不同的小的角色。确保每个角色使用起来都能方便,并承担单一职责。接口应当尽量细化。为不同的需求产生宽窄不同的接口。这让我想到了论语中著名的那句知之为知之不知为不知是智也(该让他知道就让他知道,不该让他知道的就不让他知道,这是一种智慧。)。
下面我将举例说明:
public interface MobilePhone {
//打电话
public void call();
//发短信
public void sendMessage();
//浏览网页
public void visitWebsite();
//玩游戏
public void playGame();
}
很明显这些功能不是一个手机必须有的功能 那么我们要对这个接口进行拆分
public interface Mobile {
//打电话
public void call();
//发短信
public void sendMessage();
}
实现了手机的基本功能
public interface smartPhone extends Mobile {
//浏览网页
public void visitWebsite();
//玩游戏
public void playGame();
}
这样两个接口就都最小化了。
在使用接口隔离原则时,我们需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。一般而言,接口中仅包含为某一类用户定制的方法即可,不应该强迫客户依赖于那些它们不用的方法。
4.依赖倒置原则
依赖倒转原则是Robert C. Martin在1996年为“C++Reporter”所写的专栏Engineering Notebook的第三篇,后来加入到他在2002年出版的经典著作“Agile Software Development, Principles, Patterns, and Practices”一书中。依赖倒转原则定义如下:
依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。
spring框架中的依赖注入就是这个原理的很经典的实现。在spring中我们只是针对接口进行编程。接口的实现是通过注入方式解决。修改接口添加实现类,即扩展了该接口的功能而不影响其他功能对该接口的调用。在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能.
举个栗子:
我要读取文件里的一个值,DBReader,XMLReader,NETReader,StandardOutPutStreamReader
等等读法,非常不稳定。下面我只需要在中间增加一层抽象将具体实现和抽象隔离开。那么无论你实现那种方式的读取代码都不会发生改变。我只知道你能让我获得String这个值就行了。
public interface Reader {
public String read();
}
5.迪米特原则
迪米特法则来自于1987年美国东北大学(Northeastern University)一个名为“Demeter”的研究项目。迪米特法则又称为最少知识原则(LeastKnowledge Principle, LKP),其定义如下:
迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。
如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。
通常来说访问权限从严就是迪米特原则的最好诠释。
6.开闭原则
开闭原则是面向对象的可复用设计的第一块基石,它是最重要的面向对象设计原则。开闭原则由Bertrand Meyer于1988年提出,其定义如下:
** 开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。**
通俗来说就是在不修改原有代码的基础上,添加一些新的实现就可以达到软件拓展的目的。这是软件系统设计的理想境界。但是没有任何一个系统可以完全符合这个原则。凡是相对比较符合这个原则的系统,抽象程度都比较高。所以抽象化是开闭原则的关键。这个原则更像是对之前五大原则的总结,只要我们的设计尽量符合前五个原则。那么设计出来的系统也就应该相对比较符合开闭原则。
开闭原则是目标,其他五个原则都是围绕开闭原则做的拓展,它们相辅相成,相互补充,目标一致,只是分析问题时所站角度不同而已。
5.设计模式的分类
范围\目的 | 创建模式 | 结构模式 | 行为模式 |
---|---|---|---|
类模式 | 工厂方法模式 | 适配器模式(类) | 解释器模式 模板方法模式 |
对象模式 | 抽象工厂 建造者模式 原型模式 单例模式 |
适配器模式(对象) 桥接模式 组合模式 装饰模式 外观模式 享元模式 代理模式 |
职责链模式 命令模式 迭代器模式 中介者模式 备忘录模式 观察者模式 状态模式 策略模式 访问者模式 |
6.设计模式的问题
引入一种技术是为了解决某种特定的问题。每种设计模式的引入需要特别注意“场景”。
注意“问题”在什么样的情况下产生的.看看我们遇到的问题是否也出现在这样的场景下.如果我们也在同样的场景下遇到了同样的问题,那么我们就可以用这个模式了.离开了"场景"模式就不存在.其次,要注意每一个模式的结构(也就是类图).看看使用这种结构给我们带来了什么好处.离开了业务的技术不是好技术。
不要过于注重程序的“设计模式”。 有时候,写一个简单的算法,要比引入某种模式更容易。在多数情况下,程序代码应是简单易懂,甚至清洁工也能看懂。不过呢在大项目或者框架中,没有设计模式来组织代码,别人是不易理解的。到了什么阶段做什么阶段应该做的事,就不会陷入过度设计的陷阱。项目的设计架构和成本正相关,一个蚂蚁干不了大象的工作。艰难晦涩的代码终归不是好代码。
7.总结
俗话说:站在别人的肩膀上,我们会看得更远。设计模式的出现可以让我们站在前人的肩膀上,通过一些成熟的设计方案来指导新项目的开发和设计,以便于我们开发出具有更好的灵活性和可扩展性,也更易于复用的软件系统。
理解设计模式有助于锻炼自己的设计思维。做一个真正的程序员,而不是做一个从搜索引擎把代码搬到项目中的人。
引用
https://www.cnblogs.com/barrywxx/p/4399707.html
https://www.cnblogs.com/dolphin0520/p/3919839.html
http://www.cnblogs.com/zuoxiaolong/p/pattern1.html