设计模式
什么是设计模式?
他是一套提高代码复用性,健壮性,可读性,安全性的方案
设计模式的分类
- 按目的
- 按作用范围
23中设计模式简介
-
Singleton-单例模式:某个类只能生成一个对象。
-
Prototype-原型模式:把某个对象作为原型,对其进行复制从而创建出多个与该原型类似新新对象
-
Factory Method-工厂方法模式:定义一个创建对象的接口,具体生成什么对象,由子类来实现
-
Abstract Factory-抽象工厂模式:提供一个创建对象族的接口,每个子类可以生成一系列的对象
-
Builder-建造者模式:将一个复杂的对象分解成多个相对简单的部分,根据不同的需求去创建他们,最后构成该复杂对象
-
Proxy-代理模式:为对象提供一个种代理来控制对该对象的访问
-
Bridge-桥接模式:将抽象与实现分离,使他们可以单独变化,降低抽象与实现的耦合度
-
Adapter-适配器模式:创建一个新类(即适配器类)使得原来接口不兼容不能一起工作的类能一起工作
-
Dectorator-装饰器模式:动态的为对象添加额外的功能
-
Factor-外观模式:为多个复杂的子系统提供一个一致的接口,使得这些子系统更加容易被访问
-
FlyWeight-享元模式:通过共享技术有效的支持大量细粒度对象的复用
-
Composite-组合模式:将对象组合成树状层次结构,使得单个对象和组合对象的访问具有一致性
-
TemplateMethod-模板方法模式:定义一个算法框架,将算法的一些步骤延迟到子类,子类可以在不改变该算法结构的前提下,重写算法的一些步骤
-
Strage-策略模式:定义一组算法,并将每个算法封装,使得他们可以自由切换。
-
Command-命令模式:将请求封装成一个对象,使得发出请求的责任与执行请求的责任分隔开。
-
Chain-of-Reposibility-责任链模式:将请求从一个对象传递到另一个对象,知道请求被响应,以此来降低对象间的耦合度。
-
State-状态模式:一个对象在内部状态改变时改变能够其行为。
-
Observer-观察者模式:多个对象间存在一对多的关系,当其中一种对象发生改变时,能够将改变通知给其他对象,从而影响其他对象的行为。
-
Mediator-中介者模式:通过定义一个中介对象来简化原有对象之间的交互关系,降低对象间的耦合度。
-
Iterator-迭代器模式:提供了一种方法来访问聚合对象中的一系列数据,而不暴露元素的内部表示
-
Visitor-访问者模式:在不访问集合元素的前提下,为元素提供多种访问方式,即每个元素有多个访问者对象访问。
-
Memento-备忘录模式:在不破坏对象封装性的前提下,访问并保存对象的内部状态,在需要的时候恢复对象。
-
Interpreter-解释器模式:提供了如何定义语言的方法,以及如何解释语言子句的方法。
范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法 | (类)适配器 | 模板方法 解释器 |
对象模式 | 单例 原型 抽象工厂 建造者 | 代理 (对象)适配器 桥接 装饰 外观 享元 组合 | 策略 命令 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 |
类之间的关系
依赖关系
是一种使用关系,是临时性的关联,耦合度最低的一种关系
表现形式
- 局部变量
- 方法参数
- 静态方法调用
表示方法:带箭头的虚线
关联关系
用来表示对象之间的联系,如丈夫-妻子,老师-学生,可以是单向也可以是双向的
表现形式:一个类的对象作为另一个类的成员变量,即成员对象的形式
- 一般关联关系
- 表示对象间的联系
- 表示方法:带箭头的实线
- 聚合关系
- 表示对象间是部分与整体的关系,是has-a关系,但是成员对象可以脱离整体对象而独立存在,如学校与老师的关系,学校停办了,老师依旧存在
- 表示方法:带空心菱形的实线
- 组合关系
- 也是表示对象间是部分与整体的关系,是cxmtains-a的关系,整体对象可以控制成员对象的生命周期,但是成员对象不可以脱离整体对象而独立存在,如头和嘴的关系,没有了头,嘴就不存在了
- 表示方法:带实心菱形的实线
泛化关系
类与类之间耦合度最高一种关系,表示一般与特殊的关系
-
表现形式:java的继承
-
表示方法:带空心三角的实线
实现关系
即接口与实现类之间的关系
- 表示方法:带空心三角的虚线
总结:没有带实心三角的,哈哈
7条常见的原则
口诀:开理依,单结(结)合体(迪)
开闭原则(Open Closed Principle,OCP)
定义:当应用需求发生改变时,在不修改源代码或二进制代码的前提下,扩展模块功能
核心内容:软件实体应该对扩展开放,对修改封闭
软件实体分为三部分
- 模块
- 类与接口
- 方法
实现方式:可以通过“抽象约束,封装变化”的方式来实现开闭原则,通过接口或抽象类为接口定义一个相对稳定的抽象层,而可变因素则是封装在具体的实现类中
作用:
- 对软件测试的影响:只需要测试拓展的代码即可
- 提高代码的可复用性:粒度越小,可复用性越大
- 提高软件的可维护性和稳定性
里氏替换原则(Liskov Substitution Principle,LSP,Liskov:里斯科夫)
定义:继承必须保证基类拥有的特性在派生类仍然成立(像不敢改革的领导)
核心内容:指明了什么时候应该使用继承,什么时候不该使用继承,子类可以扩展父类的功能,但是不能修改父类的功能,如企鹅,鸵鸟从生物学上讲他们都是鸟类,但是他们不能继承鸟类会“飞”的功能。
实现方式:
- 子类可以扩展父类的功能,但不能修改父类的功能
作用:
- 里氏替换是实现开闭原则的重要方式之一
- 克服了继承中重写父类造成可复用性变差的缺点
- 类的扩展不会给现有系统引入新的错误,即降低代码出错的可能性
依赖倒置原则(Dependence Inversion Principle,DIP)
定义:高层模块不应该依赖于底层模块,两者都应该依赖于其抽象,因为抽象不依赖于其细节,而细节却依赖于抽象。
核心内容:要面向接口编程,不应该面向实现编程,他是开闭原则的重要实现之一,降低了客户和实现模块之间的耦合度
实现方式:
- 每个类尽量提供接口或抽象类,或者两者都具备。
- 变量的声明类型尽量是接口或者是抽象类。
- 任何类都不应该从具体类派生。
- 使用继承时尽量遵循里氏替换原则。
作用:
- 降低类之间的耦合度
- 提高代码的可读性和可维护性
- 提高系统的稳定性
- 减少并行开发引起的风险(因为接口和抽象类写好了契约)
例子:
interface Shop {
void selling();
}
class BeiJingShop implements Shop{
@Override
public void selling() {
System.out.println("北京特产:北京烤鸭,北京酥糖");
}
}
class ShangHaiShop implements Shop {
@Override
public void selling() {
System.out.println("上海特产:上海水蜜桃,上海浦东鸡");
}
}
class Cumtomer {
public void shopping(Shop shop) {
shop.selling();
}
}
public class DIP {
public static void main(String[] args) {
BeiJingShop beiJingShop = new BeiJingShop();
ShangHaiShop shangHaiShop = new ShangHaiShop();
Cumtomer cumtomer = new Cumtomer();
System.out.println("顾客购买以下商品");
cumtomer.shopping(beiJingShop);
cumtomer.shopping(shangHaiShop);
}
}
//output
顾客购买以下商品
北京特产:北京烤鸭,北京酥糖
上海特产:上海水蜜桃,上海浦东鸡
单一职责原则(Single Responsibility Principle,SRP,又称单一功能原则)
定义:一个类应该有且只有一个引起他变化的原因,否则这个类应该被拆分(即拆分类)
如果一个类承担了太多职责的缺点:
- 某个职责的变化可能会抑制这个类实现其他职责的能力
- 客户端只需要某个职责时,不得不将其他职责包含进来,造成冗余代码
实现方式:
- 根据职责拆分类
作用:
- 降低代码的复杂度,一个类只负责一个职责
- 提高提高代码的可读性和可维护性:可读性提高了,可维护性也提高了
- 变更引起的风险降低,变更是必然的,当修改一个功能时,可以显著的降低对其他功能的影响
例子:学生工作中:生活由辅导员管理,而学业由指导老师管,而不是都丢给其中一个老师
接口隔离原则(Interface Segregation Principle,ISP)
定义:一个类对另一个类的依赖应该建立在最小的接口上(即尽量拆分接口)
实现方式:
- 接口尽量小,但要有限度,一个接口只服务于一个子模块
- 了解环境,拒绝盲从,环境不同,接口拆分的原则不同,需要深入了解业务逻辑
- 为依赖接口的类定制方法,只提供调用者需要的方法
作用:
- 将臃肿的接口拆分成粒度小的接口,提高系统的灵活性
- 降低代码的冗余度
- 提高系统的可读性和可维护性
例子:学生成绩管理程序,分三个接口:录入模块,计算模块,打印模块
合成复用原则(Composite Reuse Principle,CRP)
合成复用又称组合/聚合复用
通常类的复用分:继承复用与组合/聚合复用
继承复用的缺点:
- 父类子类耦合度高,限制了类的扩展性,父类的任何修改都会导致子类发生发生变化
- 降低了灵活度,父类继承来的实现都是编译时已确定
- 破坏了类的封装性,继承会把父类的实现细节暴露给子类
合成复用的优点:
- 维护了封装性,成员对象的内部细节是不可见的
- 耦合度降低,整体对象调用成员对象的唯一方法是通过成员对象提供的接口
- 提高了灵活度,动态的指定成员对象
例子:汽车分类管理程序
迪米特法则(Law of Demeter,LoD)
又名:最少知识原则
定义:如果两个软件实体不发生直接通信,则不应该产生直接相互调用,而应该通过第三方转发调用
作用:
- 降低耦合度(变成“一般关联”关系,而不是“泛化”关系),提高模块的独立性
- 提高了独立性意味着可复用性提高
强调的点:
- 从依赖者的角度,只依赖该依赖的对象
- 从被依赖者的角度,只暴露该暴露的方法
运用法则的注意事项:
- 应当创建弱耦合的类
- 降低成员变量的访问权限
- 需私有成员的提供访问方法
- 慎重使用序列化
例子:助理,明星,粉丝,影视公司
class Star {
private String name;
public Star(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Fan {
private String name;
public Fan(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Company {
private String name;
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Agent {
private Star star;
private Company company;
private Fan fan;
public void setStar(Star star) {
this.star = star;
}
public void setCompany(Company company) {
this.company = company;
}
public void setFan(Fan fan) {
this.fan = fan;
}
public void meeting() {
System.out.println(star.getName()+" 与 "+fan.getName()+" 见面");
}
public void business() {
System.out.println(star.getName()+" 与 "+company.getName()+" 合作");
}
}
public class LOD {
public static void main(String[] args) {
Agent agent = new Agent();
agent.setCompany(new Company("唐人影视有限公司"));
agent.setFan(new Fan("曾小哥"));
agent.setStar(new Star("唐仁"));
agent.business();
agent.meeting();
}
}
//output
唐仁 与 唐人影视有限公司 合作
唐仁 与 曾小哥 见面