设计模式的目的
编写软件过程中,程序员面临着来自耦合性,内聚性 以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让程序(软件),具有更好的
代码重用性 (即:相同功能的代码,不用多次编写)
可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
使程序呈现 高内聚,低耦合 的特性,
单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
一、懒汉式:
也称单例模式,是一种需要的时候才创建对象的设计模式,优点是延迟加载,缺点是应用同步。如果在创建实例不加上Synchronized的话则会导致对象的访问不是线程安全的,换句话说就是在使用多线程访问单例模式的时候,(引申)是需要加同步函数,同步锁对象为当前类的类名.class
二、饿汉式:
线程安全模式,因为其在创建的同时就创建好一个静态的对象对象供系统使用,以后不再改变。饿汉有三个要素;
1、私有的构造方法。 2、指向自己实例的私有静态引用。 3、以自己实例为返回值的静态公有方法。
三 、双重检查(推荐使用)
Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。
这样,实例化代码只用执行一次,后面再次访问时,判断 if(singleton == null),直接return实例化对象,也避免的反复进行方法同步.
线程安全; 延迟加载; 效率较高
四、静态内部类(推荐使用)
这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getlnstance方法,此时装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
五、枚举(推荐使用)
这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
这种方式是Effective Java作者Josh Bloch提倡的方式
工厂模式
简单工厂模式
简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
简单工厂模式: 定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.
/*
简单工厂
封装实例化对象的代码,专门用来实例化对象
如果生成对象的逻辑有变化,则直接修改工厂类即可
*/
public class SimpleFactory {
// 外部通过SimpleFactory.creatPizza()获取pizza实例
public static Pizza creatPizza(String name){
Pizza pizza = new Pizza(name);
return pizza;
}
}
mian(){
Pizza pizza = SimpleFactory.creatPizza();
}
工厂方法模式
工厂方法模式: 定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
工厂方法模式
将具体地创建逻辑下沉到子类,由子类来决定对象应该如何创建
public abstract class OrderPizza {
// 抽象方法,由子类来决定具体地实现逻辑
abstract Pizza creatPizza();
}
public class OrderHamPizza extends OrderPizza{
@Override
Pizza creatPizza() {
return new Pizza("火腿");
}
}
public class OrderCheesePizza extends OrderPizza{
@Override
Pizza creatPizza() {
return new Pizza("芝士");
}
}
main(){
// 创建火腿pizza
OrderPizza op = new OrderHamPizza();
Pizza hamPizza = op.creatPizza();
// 创建芝士pizza
op = new OrderCheesePizza();
Pizza cheesePizza = op.creatPizza();
}
抽象工厂模式
抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的工厂类
抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展。
抽象工厂模式
定义创建对象的接口,由子类工厂提供创建对象的具体逻辑
需要注意的是,当需使用工厂类时,使用者只需要聚合这个接口即可
public interface AbsFactory {
Pizza creatPizza();
}
火腿pizza工厂类
public class HamPizzaFactory implements AbsFactory{
@Override
public Pizza creatPizza() {
return new Pizza("火腿");
}
}
芝士pizza工厂类
public class CheesePizzaFactory implements AbsFactory{
@Override
public Pizza creatPizza() {
return new Pizza("芝士");
}
}
只需要聚合AbsFactory,即可使用所有的工厂子类
即把单个简单工厂变成了工厂簇
更利于代码的维护和扩展
public class OrderPizza {
这个工厂可能是火腿工厂,也可能是芝士工厂
这就将单个简单工厂变成了很多个可选的工厂子类
如果需要有新的工厂类,只需要实现AbsFactory即可使用
AbsFactory factory;
void setFactory(AbsFactory factory){
this.factory = factory;
}
Pizza order(){
return factory.creatPizza();
}
public static void main(String[] args) {
OrderPizza op = new OrderPizza();
op.setFactory(new HamPizzaFactory());
Pizza hamPizza = op.order();
op.setFactory(new CheesePizzaFactory());
Pizza cheesePizza = op.order();
}
}
工厂模式的意义
创建对象实例时,不要直接new,而是把这个new的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
不要让类继承具体类,而是继承抽象类或者是实现接口(依赖倒转原则)
不要覆盖基类中已经实现的方法(里氏替换原则)。
原型模式
原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
工作原理是: 实现Cloneable接口后利用原型对象调用clone()方法,即可拷贝出新的对象
注意: clone()方法使用的是浅拷贝,对于对象中的基本数据类型的变量,是进行值传递,对于引用数据类型的变量(类,数组),也是值传递(引用传递),也就是说如果对某个对象中的引用数据类型的变量进行修改,则修改会在所有对象中生效。
原型模式
即想要克隆的对象类型需要实现 Cloneable 这个接口
实现这个接口代表这个类能够克隆并且拥有克隆的能力
当需要克隆的时候直接调用clone这个方法即可
并且当对象的结构发生变化的时候,clone() 依旧是有效的
@Data
@ToString
public class Sheep implements Cloneable{
private String name;
private Integer age;
@Override
public Sheep clone() {
try {
Sheep clone = (Sheep) super.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
main(){
Sheep sheep = new Sheep();
sheep.setAge(10);
sheep.setName("小羊");
Sheep[] sheeps = new Sheep[5];
// 克隆五只一样的羊
for(int i = 0; i < 5; ++i){
sheeps[i] = sheep.clone();
}
for(Sheep s : sheeps) System.out.println(s);
}
原型模式之深拷贝
复制对象的所有基本数据类型的成员变量值
为所有引用数据类型的成员变量申请存储空间,并完全复制该变量,直到所有的变量全部完成克隆。
深拷贝实现方式1: 重写clone方法来实现深拷贝
深拷贝实现方式2: 通过对象序列化实现深拷贝 (推荐),原型对象必须实现Serializable接口
深拷贝之重写clone方法
@Data
@ToString
public class Sheep implements Cloneable, Serializable{
private String name;
private Integer age;
private CloneTest test;
@Override
public Sheep clone() {
try {
Sheep sheep = (Sheep) super.clone();
// 单独对引用数据类型进行拷贝
sheep.test = (CloneTest) test.clone();
return sheep;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
class CloneTest implements Cloneable, Serializable{
String text;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
深拷贝之反序列化
@Override
public Sheep clone() {
ByteArrayInputStream bis = null;
ByteArrayOutputStream bos = null;
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return (Sheep) ois.readObject();
} catch (Exception e) {
throw new AssertionError();
}finally {
try {
bis.close();
bos.close();
oos.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
当需要创建新的对象且过程比较繁琐时,可以使用原型模式简化对象的创建方式,同时也可提高效率。
当使用原型模式创建对象时,无论对象结构如何变化,创建的对象总是符合当前结构。
当需要使用深拷贝时,可能会比较繁琐。
建造者模式
建造者模式(BuilderPattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
建造者模式的四个角色:
Product (产品角色):一个具体的产品对象。
Builder(抽象建造者):创建一个Product对象的各个部件指定的接口。
ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件。
Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了对象与对象的生产过程,二是:负责控制产品对象的生产过程。
适配器模式
适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
适配器模式属于结构型模式
主要分为三类: 类适配器模式、对象适配器模式、接口适配器模式
工作原理
适配器模式: 将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
从用户的角度看不到被适配者,是解耦的
用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
用户收到反馈结果,感觉只是和目标接口交互
类适配器
目标:假设插孔电压220V, 手机充电需要5V, 利用类适配模式器将220V电压降至5V
输出220v的类
public class Output220V {
public int output220V(){
return 220;
}
}
输出5V的接口
public interface Output5V {
int output5V();
}
构建Adapter类让他继承Output220V 实现Output5V
在其中完成适配,即让电压降至5V
public class MyAdapter extends Output220V implements Output5V{
@Override
public int output5V() {
return output220V() / 44;
}
}
main(){
Output5V op = new MyAdapter();
op.output5V(); // 输出 5v
}
对象适配器 (常用)
基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承被适配类,而是持有被适配类的实例,以解决兼容性的问题。即:聚合被适配类,实现目标接口,完成适配
根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
对象适配器模式是适配器模式常用的一种
目标:假设插孔电压220V, 手机充电需要5V, 利用类适配模式器将220V电压降至5V
其它的相较于类适配器来说都不用变,对于适配类来说用聚合关系替换继承关系即可
构建Adapter类让他实现Output5V 并聚合一个Output220V
在其中完成适配,即让电压降至5V
public class MyAdapter implements Output5V{
private Output220V op220;
public MyAdapter(Output220V op220){
this.op220 = op220;
}
@Override
public int output5V() {
return op220.output220V() / 44;
}
}
main(){
Output5V op = new MyAdapter(new Output220V());
op.output5V(); // 输出 5v
}
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承被适配者的局限性问题。
使用成本更低,更灵活。
接口适配器
注意事项
一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
适用于一个类不想实现一个接口所有方法的情况
简单来说,即利用抽象类来降低类和接口间的实现难度。一个类不用去实现一个接口所有的方法,因为在抽象类这一层,已经被实现了,子类只需要重写它需要的方法即可。
装饰者模式
装饰者模式: 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
简单来说,装饰者(Decorator)与被装饰者(Decorator / Coffee)同时继承一个抽象类(Drink),装饰者中聚合一个该抽象类(聚合一个被装饰者)。相当于装饰者套被装饰者,被装饰者继续套被装饰者。
策略模式
策略模式( Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口)﹔第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)
当聚合了策略接口时,外部传入什么策略实例,聚合者就使用什么策略。
策略接口
public interface Speakable {
void speak();
}
策略接口
public interface FlyAble {
void fly();
}
本来可以实现speak和fly接口,然后让子类自己定义这两个行为
但是使用策略模式会更加灵活,子类也无需实现具体的方法
当子类需要更换fly逻辑时,不需要修改源码,只需要传入一个fly接口的实例,即可重写定义fly方法的逻辑
即外面传什么进来,鸭子就怎么飞
对于具体的操作,得看聚合进来的策略实例
多使用关联代替继承、实现 —— 合成复用原则
public abstract class Duck {
FlyAble flyAble; // 飞翔策略接口
Speakable sa; // 说话策略接口
void fly(){
flyAble.fly(); // 调用策略实例的fly方法,即是否能飞翔完全取决于外部传入的策略
}
void speak(){
sa.speak(); // 调用策略实例的speak方法,即是否能说话完全取决于外部传入的策略
}
public void setFlyAble(FlyAble flyAble){
this.flyAble = flyAble;
}
public void setSa(Speakable sa) {
this.sa = sa;
}
}
观察者模式
观察者模式简单来说就是 注册—通知,给定Subject和Observer,将Observer注册到对应的Subject中,然后通过Subject给所有注册的Observer发送消息。
观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
这样,我们增加观察者(Observer),只需要注册进Subject即可,无需修改源码,遵守了ocp原则
代理模式
代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
代理模式有不同的形式,主要有三种静态代理、动态代理(JDK代理、接口代理)和cglib代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴)。
静态代理
静态代理在使用时,需要定义接口或者父类。被代理对象(即目标对象)与代理对象一起实现相同接口或者是继承相同父类
代理对象与被代理对象一起实现的接口
public interface ITeacherDao {
void teach();
}
代理类,聚合一个被代理对象
public class TeacherDaoProxy implements ITeacherDao{
ITeacherDao dao; // 被代理对象
public TeacherDaoProxy(ITeacherDao dao){
this.dao = dao;
}
@Override
public void teach() {
System.out.println("进入代理方法");
dao.teach(); // 调用代理对象的方法, 可以再调用之前或者之后进行功能扩展
System.out.println("退出代理方法");
}
}
被代理类
public class TeacherDao implements ITeacherDao{
@Override
public void teach() {
System.out.println("调用了 teach 方法,开始教书");
}
}
通过调用代理对象的teach,完成对目标对象的调用
public static void main(String[] args) {
TeacherDao dao = new TeacherDao();
ITeacherDao proxy = new TeacherDaoProxy(dao); // 代理teacherDao
// 通过代理对象,调用目标方法
proxy.teach();
/* 控制台输出
进入代理方法
调用了 teach 方法,开始教书
退出代理方法
*/
}
动态代理
代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
动态代理也叫做:JDK代理、接口代理
获取动态代理对象的工厂类
public class ProxyFactory {
ITeacherDao teacherDao;
public ProxyFactory(ITeacherDao teacherDao){
this.teacherDao = teacherDao;
}
利用java的反射机制,动态的在内存中构建代理对象
public Object getProxyInstance(){
/*
Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
方法的三个参数:
loader 目标对象的ClassLoader, 利用固定的方法获取
interfaces 目标对象实现的接口, 利用固定的方法获取
InvocationHandler 事件处理 执行目标对象的方法时,会触发事件处理器,把目标对象的方法作为一个参数传入
*/
return Proxy.newProxyInstance(teacherDao.getClass().getClassLoader(),
teacherDao.getClass().getInterfaces(),
new InvocationHandler() {
@Override // method 目标方法 args 调用目标方法时,传入的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入动态代理方法");
Object returnVal = method.invoke(teacherDao ,args); // 执行目标方法
System.out.println("动态代理方法结束");
return returnVal;
}
});
}
}
main(){
TeacherDao dao = new TeacherDao();
ProxyFactory pf = new ProxyFactory(dao);
ITeacherDao proxy = (ITeacherDao) pf.getProxyInstance();
proxy.teach();
/* 控制台输出
进入动态代理方法
调用了 teach 方法,开始教书
动态代理方法结束
*/
}