设计模式笔记
markdown笔记+github代码
代码 github链接
创建型设计模式
简单工厂模式
定义
简单工厂模式有一个具体的工厂类,在产品种类相对较小的情况下,考虑使用简单工厂模式可以很方便的创建所需产品。
角色
简单工厂 抽象产品 具体产品
简单工厂模式在框架源码中的应用
-
在
Logback
源码中的应用public class JDKCalendar { private static final Logger log= LoggerFactory.getLogger(JDKCalendar.class); } public static Logger getLogger(Class<?> clazz) { return new Logger(clazz.getName()); } public Logger(String name) { impl = java.util.logging.Logger.getLogger(name); }
简单工厂模式的优点
结构简单,调用方便,对于外界给定的信息,我们能够很方便的创建出对应的产品。
简单工厂模式的缺点
工厂类单一,当产品技术过多,工厂类代码十方臃肿,考虑进一步的划分。
工厂方法模式
介绍
指定义一个创建对象的接口,但是由实现这个接口的类来决定实例化哪个类,工厂方法把类的实例化推迟到子类中进行。
当增加一个产品时,只需增加一个相应的工厂类的子类,实现生成这种产品,便可解决简单工厂生产太多产品导致其内部代码臃肿的问题,符合开闭原则(增加类,而不是修改之前的代码)。
角色
-
抽象工厂
-
具体工厂
-
抽象产品
-
具体产品
使用工厂方法模式实现产品拓展
工厂方法模式主要解决产品拓展的问题,在简单工厂中,随着产品链的丰富,如果每个课程的创建逻辑都有区别,则工厂的职责会变得越来越多,有点像万能工厂,不符合单一职责原则,不利于维护,于是我们继续拆分,专人干专事,Java课程由Java工厂创建,Python课程由Python工厂创建,对工厂本身也做抽。
在Logback
源码中的应用
缺点
类的个数过多,增加复杂度
抽象产品只能生产一种产品
增加了系统的抽象性和理解难度
抽象工厂模式
介绍
抽象工厂模式指提供一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类。
在抽象工厂模式中,客户端强调一系列相关的产品对象(同一个产品族)一起创建,需要大量重复的代码。
需要一个提供产品类的库,所有产品以同样的接口出现,一种产品一个接口,抽象工厂返回的是所有的抽象接口。
抽象工厂也可以使用abstract
+extends
的方式具体化。
概念
产品族 一家工厂提供的所有产品 假设 m种
产品等级结构 不同工厂提供的同一种产品 假设 n种
产品 由产品族和产品等级结构划分 m*n种 需要大量重复的代码
单例模式
定义
确保一个类在任何情况下都绝对都只有一个实例,并且提供一个全局访问点,属于创建型设计模式。
应用场景
- 对于一些频繁创建的类,减少内存压力,减少GC
- 某些类创建实例时占用资源较多,或者实例化耗时较长,且经常使用
- 频繁访问数据库和文件的对象
实现方式
通过隐藏构造方法,在内部实例化一次,并且提供一个全局的访问点。
使用单例模式解决实际问题
饿汉式 单例写法的弊端
饿汉式单例模式的标准写法
public class Client {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
}
static class Singleton{
private static final Singleton instance=new Singleton();
private Singleton(){
};
public static Singleton getInstance(){
return instance;
}
}
}
饿汉式单例写法在类的加载的时候立即进行初始化,并且创建单例对象。在线程还没出现之前就实例化了,不可能存在访问安全问题。
饿汉式的另外一种写法,使用静态块的机制。
public class Client2 {
static class HungryStaticsSingleton{
private static final HungryStaticsSingleton hungrySingleton;
static {
hungrySingleton=new HungryStaticsSingleton();
}
private HungryStaticsSingleton(){
};
public static HungryStaticsSingleton getInstance(){
return hungrySingleton;
}
}
}
饿汉式单例写法适用于单例对象较少的情况。这样写可以保证绝对的线程安全(在类加载的时候就进行初始化,并且创建单例对象),但是缺点也比较明显,就是所有对象累在类加载的时候就实例化。这样一来,如果系统中有大批量的单例对象存在,那么初始化时会造成大量的内存浪费,从而导致系统内存不可控。也就是说,不管对象用或者不用,都占着空间,浪费了内存,有可能占着内存又不使用,那么这个时候有没有更优的写法呢?
懒汉式写法
在使用的时候才会初始化
public class Client3 {
static class Singleton{
private Singleton(){
};
private static Singleton instance=null;
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
}
但是这样写又带来了一个新的问题,如果在多线程环境下,则会出现线程安全问题,存在线程安全隐患。
这里我简单的使用同步方法解决,加锁对性能有一定的影响。
public class Client4 {
static class Singleton{
private Singleton(){
};
private static Singleton singleton=null;
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
}
原型模式
定义
不是通过new
关键字而是通过对象复制来实现创建对象的模式被称为原型模式。
原型模式 指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象,属于创建型设计模式。
原型模式的核心在于复制原型对象。以系统中已经存在的一个对象为原型,直接基于内存二进制流进行复制,不需要再经历耗时的对象初始化过程(不调用构造函数),性能提升很多。
这为我们创建对象另辟蹊径,当对象的构建过程比较耗时的时候,我们可以躲避初始化过程,使得新对象的创建时间大大缩短。
应用场景
- 创建对象成本较大 (例如:初始化时间长渣,占用CPU多,占用网络资源太多)
- 创建一个对象需要繁琐的数据准备和访问权限等等,需要提高性能和提高安全性。
- 该对象在系统中大量使用,并且各个调用者都需要给它的属性重新赋值。
角色
- 客户
client
:客户提出创建对象的请求 - 抽象原型
IPrototype
:规定复制接口 - 具体原型
ConcretePrototype
:被复制的对象
使用原型模式解决实际问题
在Java提供的API中,不需要手动创建抽象原型接口,因为Java已经内置了Cloneable
抽象原型接口,自定义的类型只需要实现该接口并且重写Object.clone()
即可完成本类的复制。
实现该接口唯一的意思是告知Java虚拟机可以安全的在本类中使用clone()方法,而如果没有实现该接口,调用clone()就会抛出 CloneNotSupportedException
异常。
复制的结果有如下性质:
o.clone() != o
o.clone().getClass() = o.getClass()
o.clone().equals(o)
如果对象o的equals()
方法定义正确
public class Client2 {
public static void main(String[] args) {
Prototype prototype=new Prototype("origin");
Prototype clone = prototype.clone();
System.out.println(clone.toString());
}
static class Prototype implements Cloneable{
private String desc;
public Prototype(String desc){
this.desc=desc;
}
@Override
public Prototype clone(){
Prototype prototype=null;
try{
prototype=(Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
@Override
public String toString(){
return this.getClass().getSimpleName() +".desc= "+desc;
}
}
}
super.clone()
方法直接从堆内存中以二进制流的方式进行复制,重新分配一个内存块,因此效率很高滴。
也就是说该方法基于内存复制,不会调用对象的构造方法,也就是Java字节码层面的<init>
方法,不需要经历初始化过程。
存在的问题
问题1
在日常开发中,使用super.clone()
方法并不能满足所有需求,如果类中存在引用对象属性,那么原型对象与克隆对象的该属性会指向同一对象的应用。例如属性List<String> strs
,这显然不是我们想要的结果,那么如何解决呢?
序列化
解决方法
使用序列化实现深克隆
我对输入输出流的了解有限,现在了解一下ByteArrayOutputStream
,ObjectOutputStream
…
对象操作流 ObjectOutputStream
ObjectInputStream
public class Demo1_ObjectOutputStream {
public static void main(String[] args) throws IOException{
Student s1=new Student("张三",23);
Student s2=new Student("李四",24);
ObjectOutputStream os=new ObjectOutputStream(new FileOutputStream("record.txt"));
os.writeObject(s1);
os.writeObject(s2);
os.close();
}
}
public class Demo2_ObjectInputStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("record.txt"));
Student s1=(Student) ois.readObject();
Student s2=(Student) ois.readObject();
System.out.println(s1);
System.out.println(s2);
ois.close();
}
}
public class Demo3 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ArrayList<Student> list1=new ArrayList<Student>();
list1.add(new Student("张三",23));
list1.add(new Student("李四",24));
list1.add(new Student("王五",25));
list1.add(new Student("赵六",26));
ObjectOutputStream os=new ObjectOutputStream(new FileOutputStream("record.txt"));
os.writeObject(list1);
System.out.println("集合对象写入到文件成功!");
os.close();
ObjectInputStream osi=new ObjectInputStream(new FileInputStream("record.txt"));
ArrayList<Student> list2=(ArrayList<Student>) osi.readObject();
osi.close();
System.out.println("从磁盘读取到的集合遍历结果");
for (Student student : list2) {
System.out.println(student);
}
}
}
深复制的实现
public class Client3{
static class Prototype implements Cloneable{
private String desc;
public Prototype(String desc){
this.desc=desc;
}
@Override
public Prototype clone(){
Prototype prototype=null;
try{
prototype=(Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
@Override
public String toString(){
return this.getClass().getSimpleName() +".desc= "+desc;
}
public Prototype deepClone(){
try {
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(this);
ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(outputStream.toByteArray()));
Object o = objectInputStream.readObject();
return (Prototype) o;
}catch (IOException | ClassNotFoundException e){
e.printStackTrace();
return null;
}
}
}
}
问题2
破坏了克隆怎么办?
解决方法
-
单例类不实现
cloneable
接口,自然就不可以克隆了,不然会抛出异常 -
重写clone()方法,返回单例对象即可。
@Override pubilc Prototype clone(){ return instance; }
原型模式的优点
- 基于内存二进制流的方式,在性能上比直接new更加的优良。
- 深克隆的方式可以做到完全复制,有助于保存对象的状态,简化创建对象的过程,也可以用来还原到某一个历史状态,可辅助实现撤销操作。
原型模式的缺点
-
需要为类配置一个
clone()
方法 -
clone()
位于类的内部,对方法改造的话,不符合开闭原则。 -
实现深克隆的话,需要进行序列化和反序列化,操作很复杂
建造者模式
属于创建型设计模式,将一个复杂对象的构建过程与表示分离,实现构建与表示分离,使得同样的构建行为根据不同的过程来创建不同的表示,其中构建的行为顺序有时候也会导致不同的表示。
应用场景
创建简单而又多类的对象,使用工厂模式,而如果是创建一个复杂的对象,就可以考虑使用建造者模式。
需求:
相同的方法,不同的执行顺序,产生不同的结果。
多个部件或零件,都可以装配到一个对象中,但是产生的结果不同。
初始化一个复杂对象的很多参数都具有默认值。
角色
-
产品
-
抽象建造者:建造者的抽象类,规范产品对象的各个组成部分的创建,一般由子类实现具体的创建过程。
-
建造者 :具体的
Builder
类,根据不同的业务逻辑,具体化对象的各个组成部分的创建 -
调用者:调用具体的建造者来创建对象的各个组成部分,在指导者中不涉及具体产品的信息,只负责保证对象个部分完整创建或者按照某个顺序创建。
拓展
建造者模式的优点
- 封装好,构建和表示分离
- 拓展性好,构建类之间独立,在一定程度上解耦。
- 便于控制细节,建造者可以对创建过程逐步细化,而不对其他模块产生影响
建造者模式的缺点
-
多创建一个
builder
-
如果产品内部发生变化,建造者也要发生变化,后期维护困难。
结构型模式
代理模式
代理模式是指为其他对象提供一种代理,以控制对这个对象的访问,数据结构型设计模式。
代理对象可以在客户端与目标对象之间起到中介的作用。
应用场景
当无法或者不想直接引用某个对象或者访问某个对象存在困难的时候,可以通过代理对象来间接访问,使用代理模式主要有两个目的,一是保护目标对象,二是增强目标对象。
角色
抽象主题角色:声明真实主题与代理的共同接口方法,该类可以是接口,也可以是抽象类。
真实主题角色:
代理主题角色:也被称为代理类,其内部持有真实主题角色的应用,具备对真实主题角色的完全代理权。
客户端调用代理对象的方法,也调用被代理对象的方法,代码增强体现在在原代码逻辑前后增加一些代码逻辑,而使得调用者无感知。
分类
静态代理
动态代理
使用代理模式解决实际问题
比如从静态代理到动态代理。
从一对父母帮自己孩子说媒到开办婚介所。
Java动态代理
public class Client2 {
public static void main(String[] args) {
JdkMeipo meipo=new JdkMeipo();
IPerson instance = meipo.getInstance(new ZhaoLiu());
instance.findLove();
}
interface IPerson{
void findLove();
}
static class JdkMeipo implements InvocationHandler{
private IPerson target;
public IPerson getInstance(IPerson target){
this.target=target;
Class<? extends IPerson> clazz = target.getClass();
return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name=method.getName();
Object result=null;
before();
if(name.equals("findLove")){
result= method.invoke(this.target,args);
System.out.println("是这个");
}
after();
return result;
}
public void before(){
System.out.println("媒婆收到需求,开始物色")