对类来说,一个类应该只负责一项职责。如果一个类负责多项职责,可分解多个类来完成。
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小接口上。分解成多个专门的接口,而不是使用单一的总接口
高层模块不应该依赖低层模块,二者应该依赖抽象(抽象:接口和抽象类)。抽象不应该依赖细节,细节应该依赖抽象。此原则的中心**是面向接口编程。
依赖关系传送:接口传递、类构造方法传递、setter方式传递
定义:对于每一个S类型的对象O1,都有一个T类型的对象O2,使得以T定义的程序在使用O2替换O1时,行为不发生变化,则S是T的子类。
注意:子类可以扩展父类的功能,但是不能改变父类原有的功能。里氏代换原则是对开闭原则的补充
若不符合里氏原则,可通过聚合、组合、依赖来解决。
一个软件实体比如类、模块和函数应该对扩展开放,对修改关闭。对提供方扩展,对使用方闭关。用抽象构建框架,用实现扩展细节。
即最少知道原则,一个类对自己依赖的类知道的越少越好。对于被依赖的类,应尽量将其逻辑封装在其类内部,对外只需提供public方法。
更简单的定义:只与直接朋友通信。对出现在类中的成员变量、方法参数、方法返回值中的类称为直接朋友,而出现在局部变量中的类则不是直接朋友,也就是陌生的类最好不要以局部变量的形式出现在类中
尽量使用对象组合/聚合的方式,而不是使用继承。
UML即统一建模语言,是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果。
UML本身有一套符号的规定,用于描述软件模型中的各个元素和它们之间的关系,如类、接口、实现、泛化、依赖、组合、聚合等。常用的工具有Rational Rose,其他插件。
类之间的关系:依赖、泛化(继承)、实现、关联、聚合、组合。 各种关系的强弱顺序:泛化= 实现> 组合> 聚合> 关联> 依赖
-
依赖:是一种使用的关系,所以要尽量不使用双向的互相依赖。即只要在类中用到了对方,那么它们之间就存在依赖关系。
【代码表现】:局部变量、方法的参数或者对静态方法的调用
-
泛化:实际上就是继承关系,它是依赖关系的特例
-
实现:实际上就是B类实现A类,它是依赖关系的特例
-
关联:实际上就是类与类之间的联系,它是依赖关系的特例。单向一对一、双向一对一关系.老师与学生
【代码体现】:成员变量
-
聚合:表示整体和部分的关系,整体与部分可分离。它是关联关系的特例。车和轮胎是整体和部分关系。
【代码体现】:成员变量
-
组合:表示整体和部分的关系,整体与部分不可分离。没有公司就不存在部门
【代码体现】:成员变量
依赖 | 泛化 | 实现 | 关联 | 聚合 | 组合 |
---|---|---|---|---|---|
- - -> | ——▷ | - - -▷ | ——和——> | ——◇ | ——◆ |
例子:
设计模式分为三种类型,一共23种
- 创建型模式(5):
单例模式
、抽象工厂模式、原型模式、建造者模式、工厂方法模式
- 结构型模式(7):适配器模式、桥接模式、
装饰模式
、组合模式、外观模式、享元模式、代理模式
- 行为型模式(11):模版方法模式、命令模式、访问者模式、迭代器模式、
观察者模式
、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式
单例设计模式:就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
public class Singleton {
private static final Singleton instance= new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return instance;
}
}
优点:写法简单,在类加载时就完成实例化,避免线程同步问题。
缺点:在类加载时就完成实例化,没有达到懒加载的效果,若始终没用过这个实例,则造成内存浪费。
结论:可用,但可能造成内存浪费。
public class Singleton {
private static Singleton instance;
static {
instance= new Singleton();
}
private Singleton(){}
public static Singleton getInstance() {
return instance;
}
}
这种方式与方式1类似。
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if(null == instance) {
instance = new Singleton();
}
return instance;
}
}
优点:起到懒加载效果,但适合在单线程下使用
缺点:如果在多线程下,会产生多个实例,线程不安全
结论:在实际开发中,不要使用这种方式
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance() {
if(null == instance) {
instance = new Singleton();
}
return instance;
}
}
优点:起到懒加载效果,解决了线程不安全问题
缺点:执行效率变低,其实这个方法只执行一次实例化代码就够了
结论:在实际开发中,不推荐使用这种方式
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
与方式3类似,不要使用
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
优点:既起到懒加载效果,又解决线程不安全问题,更避免反复进行方法同步,效率较高
缺点:无
结论:在实际开发中,推荐使用这种方式
1)利用类加载机制保证实例初始化时只有一个线程
2)利用静态内部类方式在外部类被加载时并不会立即实例化,而在调用getInstance
时才会加载SingletonInstance
,从而完成Singleton的实例化
public class Singleton {
private Singleton(){}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
优点:既起到懒加载效果,又解决线程不安全问题,更避免反复进行方法同步,效率高
缺点:无
结论:在实际开发中,推荐使用这种方式
public enum Singleton {
INSTANCE;
public void functon(){
}
}
优点:不仅避免多线程同步问题,而且还能防止反序列化重新创建的对象
缺点:未知
结论:在实际开发中,推荐使用这种方式
抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象族,而无需指明具体的类。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。如华为不只有手机,还有笔记本电脑、路由器等。
用一个已经创建的实例作为原型,通过拷贝该原型对象来创建一个和原型相同或相似的新对象。
1.浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
2.深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
1)
2)
总之深浅克隆都会在堆中新分配一块区域,区别在于对象属性引用的对象是否需要进行克隆(递归性的)。此外,深拷贝相比于浅拷贝速度较慢并且花销较大。
建造者模式又叫创建者模式,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者(Builder)模式的主要角色如下:
产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个部件。
抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
1)简单工厂模式也叫静态工厂方法模式,属于创建型模式,是工厂模式的一种。它是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
2)简单工厂模式专门定义了一个类来负责创建其它类的实例,而这些类通常有共同的父类
3)在开发中,当用到大量的创建某种、某类或某批对象时,就会使用到工厂模式
定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
优点:在工厂方法模式中,工厂方法用来创建客户端所需要的产品,同时还向客户端隐藏了哪种具体产品类将被实例化这一细节,客户端只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
缺点:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
适配器模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种。
1.类适配器模式(使用继承方法)
优点:通过使用继承被适配的类,可根据需求重写被适配类的方法
缺点:使用继承方式有一定局限性;被适配类的方法都会在适配类中暴露出来,增加使用成本。
2.对象适配器模式(使用聚合关系)
解决类适配器必须继承被适配器类的局限性;使用成本更低,更灵活。
3.接口适配器模式(有选择实现接口)
当不需要全部实现接口提供的方法时,可先设计一个抽象类来实现接口,并为该接口中每个方法提供默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
适合于一个接口不想使用其他所有的方法的情况
桥接模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接模式通常适用于以下场景。
1.当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
2.当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
3.当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
装饰者模式:动态地将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也遵循了开闭原则。
主要优点:
1.采用装饰模式扩展对象的功能比采用继承方式更加灵活。
2.可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
主要缺点是:装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
组合模式:也叫部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。
主要优点有:
1.组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
2.更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
主要缺点是:
1.设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2.不容易限制容器中的构件;
3.不容易用继承的方法来增加构件的新功能;
外观模式(Facade):也叫过程模式,它是一种通过为多个复杂的子系统提供一个一致的接口,从而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观模式是“迪米特法则”的典型应用,它有以下主要优点:
1.降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
2.对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
3.降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
外观模式的主要缺点如下:
1.不能很好地限制客户使用子系统类。
2.增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量,避免大量相似类的开销,从而提高系统资源的利用率。
主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
主要缺点是:
1.为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
2.读取享元模式的外部状态会使得运行时间稍微变长。
代理模式:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式有三种:静态代理、动态代理(JDK代理或接口代理)和Cglib代理。
在使用静态代理时,需要定义接口或父类,被代理对象(目标对象)与代理对象一起实现相同的接口或者继承相同的父类。
1.设计动态代理类DynamicProxy
时,不需要显式实现与目标对象类RealSubject
相同的接口,而是将这种实现推迟到程序运行时由 JVM来实现。
2.通过Java反射机制的method.invoke()
,然后通过调用动态代理类对象方法,从而自动调用目标对象的方法。
主要优点有:
1.代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
2.代理对象可以扩展目标对象的功能;
3.代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
主要缺点:
1.在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
2.增加了系统的复杂度;
模板方法(Template Method)模式:定义一个操作中的算法骨架,而将算法的一些步骤的具体实现延迟到子类中,使得子类可以不改变该算法结构的情况下重写该算法的某些特定步骤。它是一种类行为型模式。
主要优点:
1.它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
2.它在父类中提取了公共的部分代码,便于代码复用。
3.部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
主要缺点:
1.对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
2.父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
命令模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
主要优点:
1.降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
2.增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
3.可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
4.方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
缺点:
可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
访问者模式:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下,可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
主要优点:
1.扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
2.复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
3.灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
4.符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
主要缺点:
1.增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
2.破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
3.违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
迭代器(Iterator)模式:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式。
主要优点:
1.访问一个聚合对象的内容而无须暴露它的内部表示。
2.遍历任务交由迭代器完成,这简化了聚合类。
3.它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
4.增加新的聚合类和迭代器类都很方便,无须修改原有代码。
5.封装性良好,为遍历不同的聚合结构提供一个统一的接口。
主要缺点:增加了类的个数,这在一定程度上增加了系统的复杂性。
观察者(Observer)模式:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
主要优点:
1.降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
2.目标与观察者之间建立了一套触发机制。
主要缺点:
1.目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
2.当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
中介者(Mediator)模式:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
主要优点:
1.降低了对象之间的耦合性,使得对象易于独立地被复用。
2.将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
主要缺点:当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式,备忘录模式是一种对象行为型模式.
主要优点:
1.提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
2.实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
3.简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
主要缺点:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
解释器(Interpreter)模式:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。解释器模式是一种类行为型模式.
主要优点:
1.扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
2.容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
主要缺点:
1.执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
2.会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
3.可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。状态模式是一种对象行为型模式。
主要优点:
1.状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
2.减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
3.有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
主要缺点:
1.状态模式的使用必然会增加系统的类与对象的个数。
2.状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
策略(Strategy)模式:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
主要优点:
1.多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
2.策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
3.策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
4.策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
5.策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
主要缺点:
1.客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
2.策略模式造成很多的策略类。
责任链(Chain of Responsibility)模式:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
注意:责任链模式也叫职责链模式。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式是一种对象行为型模式。
其主要优点:
1.降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
2.增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
3.增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
4.责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
5.责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
主要缺点:
1.不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
2.对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
3.职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。