23种设计模式模式详解 Java UML类图小知识(一)
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式的好处:代码的可重用性、可扩展性,可阅读性,保证代码的可靠。(代码要优雅)
关于相关源码可以在GitHub上下载:
https://github.com/rickey17/design
- 设计模式的6大原则
- 设计模式分类
- 创造型设计模式
- 工厂模式
- *UML小知识
- 抽象工厂模式
- 单例模式
- 原型模式
- 建造者模式
设计模式的六大原则
1、单一职责原则 SRP(Single Responsibility Principle)
不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
*一个类只负责一件事
2、开闭原则 OCP (Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
*程序需要扩展时,也不能修改原有的代码,程序设计时就要注意接口及抽象类的使用
3、里氏代换原则 LSP (Liskov Substitution Principle)
里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
*子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
*子类中可以增加自己特有的方法。
*当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
*当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
*一句话总结:尽量不要重写父类的已经实现了的方法,可以用接口等其他方法绕过
4、依赖倒转原则 DIP (Dependence Inversion Principle)
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。这个是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
*面向接口的编程,多用抽象的接口来描述相同的动作,参考Spring
5、接口隔离原则 ISP (Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
*使用多个隔离的接口,比使用单个接口要好,接口也需要解耦
6、迪米特法则(最少知道原则) DP (Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
*解耦,被依赖的类应该封装好自己的业务逻辑
设计模式分类
23种设计模式大概分为三大类:
- 5种 创建型模式:工厂方法模式、抽象工厂模式、单例模式、原型模式、建造者模式。
- 7种 结构型模式:适配器模式(类的适配,接口的适配,对象的适配)、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
11种 行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
行为型又可以通过类与类之间的关系进行划分
这些划分方式都是为了让大家更加容易理解和记忆
创造型设计模式
- 工厂方法模式
- 抽象工厂模式
- 单例模式
- 原型模式
- 建造者模式
工厂方法模式
简介:通过一个工厂创建一系列的产品
UML 类图:
根据类图生成的源码:
public interface ISender {
public void send();
}
public class SmsSender implements ISender {
public void send(){
}
}
public class MailSender implements ISender {
public void send(){
}
}
public class SmsSender implements ISender {
public void send(){
}
}
解析:
定义一个抽象的产品,即ISender 这个接口;
定义一个工厂类SendFactory,用于生产这个抽象的产品;
定义MailSender和SmsSender两个产品,实现抽象产品;
Client需要各个产品的时候,就会通过SendFactory这个工厂类来生成产品
缺点:
如果我们要新增产品WeChatSender的时候,需要修改工厂类SendFactory;这样违背上面讲的原则“开闭原则”
UML小知识
本节以Enterprise Architect为例,对以上设计模式进行分析。通过类图表示类相互之间的关系。
上面我们通过UML的类图讲解了工厂方法模式,博主也在重新学习UML,所以我们一起来看下UML类图的一些小知识吧。
一、类图的3个基本组件:类名、属性、方法。
接口会在接口名上面加上《interface》的标记
所有的抽象abstract(类,方法)都是用斜体表示
所有的静态static(方法,属性)都是用下划线表示
属性表示为:属性名:类型
方法表示为:方法名(入参类型):返回值类型
在属性和方法的前面有一个字符用来表示属性或方法的作用域,它们的意义如下:
1、“-”表示属性或方法是私有的(private);
2、“#”表示属性或方法是保护的(protected);
3、“+”表示属性或方法是公用的(public)。
下面就是最容易弄混淆的类之间的关系
1、泛化(generalization)(继承)
表示类与类的继承关系,是对象之间耦合度最大的一种关系,子类继承父类的所有细节。直接使用语言中的继承表达。在类图中使用带三角箭头的实线表示,箭头从子类指向父类。如图:
2、实现(Realization)
在类图中就是接口和实现的关系。这个没什么好讲的。在类图中使用带三角箭头的虚线表示,箭头从实现类指向接口。如图:
3、依赖(Dependency)
类A要完成某个功能必须引用类B,则A与B存在依赖关系,依赖关系是弱的关联关系。对象之间最弱的一种关联方式,是临时性的关联。代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系。一个类调用被依赖类中的某些方法而得以完成这个类的一些职责。在类图使用带箭头的虚线表示,箭头从使用类指向被依赖的类。
4、关联(Association)
是一种拥有的关系, 它使一个类知道另一个类的属性和方法。对象之间一种引用关系,比如客户类与订单类之间的关系。这种关系通常使用类的属性表达。关联又分为一般关联、聚合关联与组合关联。后两种在后面分析。在类图使用带箭头的实线表示,箭头从使用类指向被关联的类。可以是单向和双向。
(1)、单向关联
A1->A2:表示A1认识A2,A1知道A2的存在,A1可以调用A2中的方法和属性
场景:订单和商品,订单中包括商品,但是商品并不了解订单的存在。
类与类之间的单向关联图
(2)、双向关联图
(3)、自身关联
同一个类对象之间的关联
例如,类与类之间自身关联图
也可以通过基数表示,拥有者和被拥有者的应用关系
1-1
1-n
n-1
n-n
5、聚合(Aggregation) :
当对象A被加入到对象B中,成为对象B的组成部分时,对象B和对象A之间为聚合关系。聚合是关联关系的一种,是较强的关联关系,强调的是整体与部分之间的关系。表示has-a的关系,是一种不稳定的包含关系。较强于一般关联,有整体与局部的关系,并且没有了整体,局部也可单独存在。如公司和员工的关系,公司包含员工,但如果公司倒闭,员工依然可以换公司。在类图使用空心的菱形表示,菱形从局部指向整体。
6、组合(Composite)
对象A包含对象B,对象B离开对象A没有实际意义。是一种更强的关联关系。
7、聚合和组合的区别
(1)、聚合关系是“has-a”关系,组合关系是“contains-a”关系;
(2)、聚合关系表示整体与部分的关系比较弱,而组合比较强;
(3)、聚合关系中代表部分事物的对象与代表聚合事物的对象的生存期无关,一旦删除了聚合对象不一定就删除了代表部分事物的对象。
(4)、组合中一旦删除了组合对象,同时也就删除了代表部分事物的对象。
抽象工厂模式
由于工厂模式违背了设计原则。所以在抽象工厂的基础上,我们做如下改进。
client依赖工厂类生成产品,抽象工厂模式就是在工厂上再加入一层抽象,client不在依赖于工厂类,而是依赖于工厂接口。依赖工厂接口接口,我们可以创建新的工厂,完成新产品的生成,而不用修改原来的代码。
UML 类图:
根据类图生成的源码:
public interface Factory {
public Isender create();
}
public class MailFactory implements Factory {
public Isender create(){
return null;
}
}
public class SmsFacorty implements Factory {
public Isender create(){
return null;
}
}
有点:之后新增产品的时候,只需要多增加一个对应的工厂类就可以,不用修改原来的代码。
单例模式
在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
- 懒汉(类初始化的时候不创建实例,用的时候在创建)(线程安全,锁)
- 恶汉(类初始化的时候创建实例)
注意:
1、private 隐藏构造方法
2、防止反序列化
3、不同的类装载器装入(JVM保障)
4、并发,锁,volatile
经典的方法展示
//懒汉双层校验
public class Singleton {
/* 一定要用volatile的happen before规则,才能保证双重验证的正确性 */
private volatile static Singleton singleton;
/* 隐藏构造方法 */
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
//内部静态类
/* 这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance */
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE =
new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
/* Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象 */
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
/** 需要注意的地方
/** 1、private 隐藏构造方法
/** 2、防止反序列化
/** 3、不同的类装载器装入(JVM保障)
/** 以上是解决方法
*/
private Singleton (){}
private Object readResolve() {
return INSTANCE;
}
private static Class getClass(String classname) throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}
原型模式
原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。
重点:浅复制,深复制
一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的。
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
public class Prototype implements Cloneable, Serializable {
private String string;
private SerializableObject obj;
/* 浅复制 */
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
/* 深复制 */
public Object deepClone() throws IOException, ClassNotFoundException {
/* 写入当前对象的二进制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 读出二进制流产生的新对象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
}
class SerializableObject implements Serializable {
private String name = "deep clone";
}
建造者模式
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和Client结合起来得到的。
UML 类图:
源码:
public class Builder {
private List<ISender> list;
/**
*
* @param num
*/
public void produceMailSender(int num){
}
/**
*
* @param num
*/
public void produceSmsSender(int num){
}
}
还有没讲完的,下一篇继续…