设计模式学习总结(一)——设计原则与UML统一建模语言
一、概要
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。可复用、可扩展、可维护
设计模式是GOF(Group Of Four Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides )所著的《设计模式:可复用面向对象软件的基础》一书中所描述的23种经典设计模式,此书奠定了模式在软件行业中的地位,从此人们提到“设计模式”就是默认指“面向对象设计模式”
1.1、设计模式定义
每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心
特定的问题,特定的解决套路
1.2、设计模式分类
设计模式分为三大类共23种:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
1.3、设计模式书籍
Head First设计模式(中文版)HEAD FIRST Jolt震撼大奖headfirst设计模式深入浅出讲清 java设计模式计算机编程自学入门
大话设计模式
二、UML统一建模语言
统一建模语言(UML,Unified Modeling Language)是面向对象软件的标准化建模语言。UML因其简单、统一的特点,而且能表达软件设计中的动态和静态信息,目前已成为可视化建模语言的工业标准。在软件无线电系统的开发过程中,统一建模语言可以在整个设计周期中使用,帮助设计者缩短设计时间,减少改进的成本,使软硬件分割最优。
2.1、UML分类
2.2、类图
建模工具:Rose
COCOMO,英文全称为constructive cost model,中文为构造性成本模型。它是一种精确、易于使用的,基于模型的成本估算方法。
2.2.1、关联
在IDEA中查看类图:
显示细节
双向关联:
C1-C2:指双方都知道对方的存在,都可以调用对方的公共属性和方法。
在GOF的设计模式书上是这样描述的:虽然在分析阶段这种关系是适用的,但我们觉得它对于描述设计模式内的类关系来说显得太抽象了,因为在设计阶段关联关系必须被映射为对象引用或指针。对象引用本身就是有向的,更适合表达我们所讨论的那种关系。所以这种关系在设计的时候比较少用到,关联一般都是有向的。
使用ROSE 生成的代码是这样的:
双向关联在代码的表现为双方都拥有对方的一个指针,当然也可以是引用或者是值。
单向关联:
/**学生累*/ public class Student { }
/**学校类*/ public class School { public Student tom; }
C3->C4:表示相识关系,指C3知道C4,C3可以调用C4的公共属性和方法。没有生命期的依赖。一般是表示为一种引用。
生成代码如下:
单向关联的代码就表现为C3有C4的指针,而C4对C3一无所知。
自身关联(反身关联):
自己引用自己,带着一个自己的引用。
代码如下:
public class Node { //数据 public int data; public Node next; public Node prev; }
就是在自己的内部有着一个自身的引用。
2.2.2、聚合/组合
当类之间有整体-部分关系的时候,我们就可以使用组合或者聚合。
聚合:是一种弱偶合的关联关系。
/**人*/ public class Person { }
/**引擎*/ public class Engine { }
/**车*/ public class Car { /**组合*/ private Engine engine; public Car(Engine engine){ this.engine=engine; } /**聚合*/ public Person driver; }
组合(也有人称为包容):一般是实心菱形加实线箭头表示,如上图所示,表示的是C8被C7包容,而且C8不能离开C7而独立存在。但这是视问题域而定的,例如在关心汽车的领域里,轮胎是一定要组合在汽车类中的,因为它离开了汽车就没有意义了。但是在卖轮胎的店铺业务里,就算轮胎离开了汽车,它也是有意义的,这就可以用聚合了。在《敏捷开发》中还说到,A组合B,则A需要知道B的生存周期,即可能A负责生成或者释放B,或者A通过某种途径知道B的生成和释放。
2.2.3、依赖
/**人*/ public class Person { /**吃食物*/ public void eat(Food food){ System.out.println("正在吃"+food.name); } }
那依赖和聚合\组合、关联等有什么不同呢?
关联是类之间的一种关系,例如老师教学生,老公和老婆,水壶装水等就是一种关系。这种关系是非常明显的,在问题领域中通过分析直接就能得出。
依赖是一种弱关联,只要一个类用到另一个类,但是和另一个类的关系不是太明显的时候(可以说是“uses”了那个类),就可以把这种关系看成是依赖,依赖也可说是一种偶然的关系,而不是必然的关系,就是“我在某个方法中偶然用到了它,但在现实中我和它并没多大关系”。例如我和锤子,我和锤子本来是没关系的,但在有一次要钉钉子的时候,我用到了它,这就是一种依赖,依赖锤子完成钉钉子这件事情。
组合是一种整体-部分的关系,在问题域中这种关系很明显,直接分析就可以得出的。例如轮胎是车的一部分,树叶是树的一部分,手脚是身体的一部分这种的关系,非常明显的整体-部分关系。
上述的几种关系(关联、聚合/组合、依赖)在代码中可能以指针、引用、值等的方式在另一个类中出现,不拘于形式,但在逻辑上他们就有以上的区别。
这里还要说明一下,所谓的这些关系只是在某个问题域才有效,离开了这个问题域,可能这些关系就不成立了,例如可能在某个问题域中,我是一个木匠,需要拿着锤子去干活,可能整个问题的描述就是我拿着锤子怎么钉桌子,钉椅子,钉柜子;既然整个问题就是描述这个,我和锤子就不仅是偶然的依赖关系了,我和锤子的关系变得非常的紧密,可能就上升为组合关系(让我突然想起武侠小说的剑不离身,剑亡人亡...)。这个例子可能有点荒谬,但也是为了说明一个道理,就是关系和类一样,它们都是在一个问题领域中才成立的,离开了这个问题域,他们可能就不复存在了。
2.2.4、泛化(继承)
泛化关系:如果两个类存在泛化的关系时就使用,例如父和子,动物和老虎,植物和花等。
ROSE生成的代码很简单,如下:
/**学生累*/ public class Student extends Person { }
引用地址:http://www.cnblogs.com/riky/archive/2007/04/07/704298.html
三、设计原则
2.1、单一职责原则(SRP)
一个类,只有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的分离。
一个类只担任一种角色。
下面这个jsp页面就不符合SRP原则,它要担任展示UI与访问数据库两个角色。分层是一种解决办法。
<%@ page contentType="text/html; charset=gb2312" %> <%@ page language="java" %> <%@ page import="com.mysql.jdbc.Driver" %> <%@ page import="java.sql.*" %> <% //加载驱动程序 String driverName="com.mysql.jdbc.Driver"; //数据库信息 String userName="root"; //密码 String userPasswd="123"; //数据库名 String dbName="Student"; //表名 String tableName="stu_info"; //将数据库信息字符串连接成为一个完整的url(也可以直接写成url,分开写是明了可维护性强) String url="jdbc:mysql://localhost/"+dbName+"?user="+userName+"&password="+userPasswd; Class.forName("com.mysql.jdbc.Driver").newInstance(); Connection conn=DriverManager.getConnection(url); Statement stmt = conn.createStatement(); String sql="SELECT * FROM "+tableName; ResultSet rs = stmt.executeQuery(sql); out.print("id"); out.print("|"); out.print("name"); out.print("|"); out.print("phone"); out.print("<br>"); while(rs.next()) { out.print(rs.getString(1)+" "); out.print("|"); out.print(rs.getString(2)+" "); out.print("|"); out.print(rs.getString(3)); out.print("<br>"); } out.print("<br>"); out.print("ok, Database Query Successd!"); rs.close(); stmt.close(); conn.close(); %>
2.2、开闭原则(Open Close Principle OCP)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
美猴王说:"'皇帝轮流做,明年到我家。'只教他搬出去,将天宫让于我!"对于这项挑战,太白金星给玉皇大帝提出的建议是:"降一道招安圣旨,宣上界来…,一则不劳师动众,二则收仙有道也。
不要随意修改顶层接口,可以通过继承或其它办法扩展出新的内容。
功能实现应该对扩展开放,对修改关闭,应该通过扩展来实现(应对)变化,而不是通过修改已有的代码来实现变化的内容。
2.3、里氏代换原则(Liskov Substitution Principle LSP)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。老鼠的儿子会打洞。
Person tom=new Student(); Object mark=new Teacher();
子类可以覆盖父类的抽象方法,但不能覆盖非抽象方法。
如果需要覆盖父类的非抽象方法,参数的类型必须要比父类的宽松,返回值类型必须要比父类严格。
子类可以拥有自己的成员方法。
在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有了“个性”,这个子类和父类之间的关系就难调和,把子类当做父类使用,子类的“个性”被抺杀了,把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离–缺乏类替换的标准。
2.4、依赖倒转原则(Dependence Inversion Principle DIP)
所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。 细节应该依赖抽象,抽象应该依赖抽象,抽象不应该依赖细节
/**人*/ public class Person { /**吃食物*/ public void eat(Food food){ //依赖具体 System.out.println("正在吃"+food.name); Person tom=new Student(); Object mark=new Teacher(); } //Student继承Person //依赖抽象 public void Show(Person person){ } //依赖具体 public void Hello(Student student){ } }
2.5、接口隔离原则(Interface Segregation Principle ISP)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
/**可飞的*/ interface IFlyable{ public void fly(); } /**下蛋*/ interface ILayeggAble{ public void Layegg(); } interface IBirdable{ public void fly(); public void Layegg(); }
IBirdable如果被超人(SuperMan)实现则除了要实现fly方法飞还要实现下蛋接方法,超人下蛋不太合适。
2.6、合成复用原则(Composite Reuse Principle)
合成复用原则就是指在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。简言之:要尽量使用组合/聚合关系,少用继承。
2.7、迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。也就是说一个软件实体应当尽可能少的与其他实体发生相互作用。这样,当一个模块修改时,就会尽量少的影响其他的模块,扩展会相对容易,这是对软件实体之间通信的限制,它要求限制软件实体之间通信的宽度和深度。
小结:
1、单一职责原则
二层含义,一是避免相同的职责分散到不同的类中,二是避免一个类承担太多职责。减少类的耦合,提高类的复用性。
2、接口隔离原则
表明客户端不应该被强迫实现一些他们不会使用的接口,应该把胖接口中额方法分组,然后用多个接口代替它,每个接口服务于一个子模块。简单说,就是使用多个专门的接口比使用单个接口好很多。
该原则观点如下:
a,一个类对另外一个类的依赖性应当是建立在最小的接口上
b,客户端程序不应该依赖它不需要的接口方法。
3、开放-关闭原则
open模块的行为必须是开放的、支持扩展的,而不是僵化的。
closed在对模块的功能进行扩展时,不应该影响或大规模影响已有的程序模块。一句话概括:一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。
核心思想就是对抽象编程,而不对具体编程。
4、里氏替换原则
子类型必须能够替换掉他们的父类型、并出现在父类能够出现的任何地方。
主要针对继承的设计原则
1,父类的方法都要在子类中实现或者重写,并且派生类只实现其抽象类中生命的方法,而不应当给出多余的,方法定义或实现。
2,在客户端程序中只应该使用父类对象而不应当直接使用子类对象,这样可以实现运行期间绑定。
5、依赖倒置原则
上层模块不应该依赖于下层模块,他们共同依赖于一个抽象(父类不能依赖子类,他们都要依赖抽象类)
抽象不能依赖于具体,具体应该要依赖于抽象。
四、示例与资料
示例:https://coding.net/u/zhangguo5/p/DP01/git
视频:https://www.bilibili.com/video/av15867320/
五、视频与作业
5.1、作业
1、使用java代码实现下图,理解他们之间的关系
2、请记住类之间的关系
3、请记住5大设计原则并作简单描述