何为设计模式? Gof(Gang of Four, 四人帮 设计模式)
其实就是前辈们对踩过的坑进行总结,避免其他程序员重蹈覆辙嘛,为此根据特定的问题所制定出的一系列套路。
为什么要使用设计模式?
正确的使用设计模式
- 是的程序设计更加标准化、代码编制更加工程化,使得软件的开发效率提高和维护成本降低。
- 使用合适的策略通常能够提高代码的复用性、可维护性、可读性、健壮性和安全性。
设计模式的本质
面向对象设计原则的实际运用,针对类的封装性、继承性、多态性以及类的关联关系和组合关系的充分理解。
设计模式的基本要素
- 模式名称
- 问题
- 解决方案
- 效果
OOP七大原则
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:继承必须确保超类所拥有的性质再子类中仍然成立
- 依赖倒置原则:要面向接口编程,不要面向实现编程
- 单一职责原则:控制类的粒度大小,将对象解耦、提高其内聚性
- 接口隔离原则:要为各个类建立它们需要的专用接口
- 迪米特法则:只与你的直接朋友交谈,不跟"陌生人"说话
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
创建型模式
对对象的创建应该如何执行
单例模式
保证在整个软件系统中对某个类只能有一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
八种实现方式
饿汉式(静态常量)
实现
//饿汉式单例模式(静态常量)
public class EagerSingletonType1 {
private EagerSingletonType1() {
}
private final static EagerSingletonType1 instance = new EagerSingletonType1();
public static EagerSingletonType1 getInstance() {
return instance;
}
public void output() {
System.out.println("获取成功");
}
}
优缺点
- 优点
- 写法简单,在类的装载时就完成了实例化,避免了线程同步问题。
- 缺点
- 在类装载的时候完成了实例化,没有达到懒加载的效果,可能造成内存的浪费
- 结论
- 可用,但是可能造成内存空间的浪费
饿汉式(静态代码块)(推荐使用)
实现
//饿汉式单例模式(静态代码块)
public class EagerSingletonType2 {
private static EagerSingletonType2 instance;
static {
instance = new EagerSingletonType2();
}
private EagerSingletonType2() {
}
public static EagerSingletonType2 getInstance() {
return instance;
}
public void output() {
System.out.println("获取成功");
}
}
懒汉式(线程不安全)
实现
public class LazySingletonType1 {
private static LazySingletonType1 instance;
private LazySingletonType1() {}
public static LazySingletonType1 getInstance() {
if(instance == null) {
instance = new LazySingletonType1();
}
return instance;
}
}
优缺点
- 优点
- 懒加载效果,可以节省一定内存空间,但是只能在单线程的情况下使用
- 缺点
- 如果在多线程下,某个线程访问了判断语句块,此时还未有实例,判断结果为true,但是在创建对象前时间片到期,换到另一个线程执行,而这个线程也访问判断语句块,判断结果同样为true,此时会产生多个实例。(线程不安全,有可能产生多个实例)
懒汉式(线程安全,同步方法)
//懒汉式(线程安全)
public class LazySingletonType2 {
private static LazySingletonType2 instance;
private LazySingletonType2() {}
public static synchronized LazySingletonType2 getInstance() {
if(instance == null) {
instance = new LazySingletonType2();
}
return instance;
}
}
优缺点
- 优点
- 线程安全
- 缺点
- 效率低,因为线程想要获取该类时都需要执行getInstance()方法,实际上我们只需要执行一次实例化代码,实例化后后面再想要获得该类的实例只需要直接返回即可。
懒汉式(线程安全,同步代码块)(错误,并没有解决线程安全问题)
实现
//懒汉式(线程安全,同步代码块)
public class LazySingletonType3 {
private static LazySingletonType3 instance;
private LazySingletonType3() {}
public static LazySingletonType3 getInstance() {
if(instance == null) {
synchronized (LazySingletonType3.class){
instance = new LazySingletonType3();
}
}
return instance;
}
}
优缺点
- 缺点
- 实际上并没有解决线程安全问题,与懒汉式(线程不安全)方法遇到的问题相同
双重检查(推荐使用)
实现
//懒汉式(双重检查,线程安全)
public class LazySingletonType4 {
private static LazySingletonType4 instance;
private LazySingletonType4() {}
public static LazySingletonType4 getInstance() {
if(instance == null) {
synchronized (LazySingletonType4.class){
if(instance == null) {
instance = new LazySingletonType4();
}
}
}
return instance;
}
}
优缺点
- 优点
- 线程安全,即使多个线程通过了(instance == null)语句,都进入了同步代码块,此时也需要进行二次判断,不会创建多个实例
- 效率高,不会锁定整个getInstance()方法,当对象创建后,只需判断是否为空即可获得实例。
静态内部类(推荐使用)
静态内部类的特点
- 外部类装载时,内部类不会被装载
- 静态内部类被访问时,内部类被装载,而且此时线程安全 实现
//懒汉式(使用静态内部类实现,推荐使用)
public class LazySingletonType5 {
private static LazySingletonType5 instance;
private LazySingletonType5() {
}
private static class LazySingletonType5Instance {
private static final LazySingletonType5 INSTANCE = new LazySingletonType5();
}
public static LazySingletonType5 getInstance() {
return LazySingletonType5Instance.INSTANCE;
}
}
优缺点
- 优点
- 才用了类装载的机制来保证初始化的时候只有一个线程
- 静态内部类在外部类被装载时不会被立刻实例化,只有在需要的时候才会实例化,也就是调用getInstance方法时才会被实例化
- 类的静态属性只会在第一次加载的时候才会初始化,JVM帮助我们保证了线程安全
枚举(推荐使用)
实现
public class EnumSingleton {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1 == instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
instance1.output();
instance2.output();
}
}
enum Singleton {
INSTANCE;
public void output() {
System.out.println("ok");
}
}
优缺点
- 优点
- 线程安全
- 防止反序列化重新创建新的对象
单例模式的注意事项
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建和销毁的对象,可以使用单例模式来系统系统性能。
- 想要实例化一个单例类的时候,必须使用对应的方法,而不是使用new
单例模式使用的场景
需要频繁创建和销毁的对象、创建对象时需要耗费大量的时间和资源,但是又经常需要使用到的对象。(工具类对象、访问数据库或者文件的对象)
工厂模式
需要满足以下 OOP 原则
- 开闭原则
- 依赖倒转原则
- 迪米特法则
本质
- 使用工厂方法代替 new
- 选择实现类
简单工厂模式
用于生产同一等级结构中的任意产品。简单工厂模式模式又称静态工厂方法,因为通常创建实例的方法为静态方法
结构
- 工厂
- 实现创建产品的具体逻辑
- 抽象产品
- 封装了产品的公共方法,所有具体产品的父类
- 具体产品
- 需要被创建的具体实例,需要实现抽象产品中的抽象方法
例子
- 汉堡包有很多类型(双层吉士汉堡、麦香鸡汉堡、板烧鸡腿堡等等)
- 制作汉堡的步骤
- 准备面包
- 加上蔬菜
- 加上汉堡肉
- 特别步骤
- 完成订单
实现
工厂
//简单工厂类
public class Factory {
public static Hamburger createHamburger(String type) {
Hamburger hamburger = null;
if(type.equals("板烧鸡腿堡")) {
hamburger = new bsjtb(type);
}else if(type.equals("双层吉士汉堡")) {
hamburger = new scjshb(type);
}else if(type.equals("麦香鸡汉堡")) {
hamburger = new mxjhb(type);
}else {
System.out.println("没有这种汉堡");
}
return hamburger;
}
}
抽象产品
//汉堡类的抽象化
public abstract class Hamburger {
public Hamburger(String type) {
addBread(type);
addVegetables(type);
addMeat(type);
}
public void addBread(String type) {
System.out.println("添加"+type+"的面包");
}
public void addVegetables(String type) {
System.out.println("添加"+type+"的蔬菜");
}
public void addMeat(String type) {
System.out.println("添加"+type+"的肉");
}
public abstract void addExtra();
}
具体产品
//板烧鸡腿堡
public class bsjtb extends Hamburger{
public bsjtb(String type) {
super(type);
addExtra();
}
@Override
public void addExtra() {
System.out.println("添加特制酱料");
}
}
//麦香鸡汉堡
public class mxjhb extends Hamburger{
public mxjhb(String type) {
super(type);
addExtra();
}
@Override
public void addExtra() {
}
}
//双层吉士汉堡
public class scjshb extends Hamburger{
public scjshb(String type) {
super(type);
addExtra();
}
@Override
public void addExtra() {
}
}
优缺点
- 优点
- 用户不需要去关心如何创建实例
- 引入配置文件即可在不修改客户端源代码的情况下更换或者添加新的产品
- 缺点
- 工厂类包括了所有创建逻辑,一旦出现故障整个系统都受到影响
- 扩展产品需要修改工厂类的源代码(没有满足开闭原则)
适用环境
- 工厂类负责的类创建的类型比较少
工厂方法模式
结构
- 抽象工厂 提供创建产品的接口
- 具体工厂 完成产品的实例化
- 抽象产品 定义了产品的规范
- 具体产品 实现产品规范,与具体工厂一一对应
实现
//抽象工厂
public abstract class AbstractFactory {
public abstract Product createProduct();
}
//具体工厂
public class AFactory extends AbstractFactory {
@Override
public Product createProduct() {
return new A();
}
}
......
//抽象产品
public abstract class Product {
public abstract void show();
}
//具体产品
public class A extends Product{
@Override
public void show() {
System.out.println("I am A");
}
}
......
//测试
public class Order {
public static void main(String[] args) {
Product a = new AFactory().createProduct();
Product b = new BFactory().createProduct();
Product c = new CFactory().createProduct();
a.show();
b.show();
c.show();
}
}
抽象工厂模式
产品族与产品等级
- 产品族
- 相当于同一个品牌
- 产品等级
- 相当于同一类别的产品
建造者模式
创建复杂对象
角色
- 指挥者:指导工人建造顺序和使用的材料
- 蓝图:创建产品的具体方法
- 工人:实现产品生产
- 产品:具体产品
实现
//蓝图
public abstract class Builder {
abstract void buildA();
abstract void buildB();
abstract void buildC();
abstract void buildD();
abstract Product getProduct();
}
//产品
public class Product {
private String A;
private String B;
private String C;
private String D;
public String getA() {
return A;
}
public void setA(String a) {
A = a;
}
public String getB() {
return B;
}
public void setB(String b) {
B = b;
}
public String getC() {
return C;
}
public void setC(String c) {
C = c;
}
public String getD() {
return D;
}
public void setD(String d) {
D = d;
}
@Override
public String toString() {
return "Product{" +
"A='" + A + '\'' +
", B='" + B + '\'' +
", C='" + C + '\'' +
", D='" + D + '\'' +
'}';
}
}
//工人
public class Worker extends Builder{
private Product p ;
public Worker() {
p = new Product();
}
@Override
void buildA() {
p.setA("A");
}
@Override
void buildB() {
p.setB("B");
}
@Override
void buildC() {
p.setC("C");
}
@Override
void buildD() {
p.setD("D");
}
@Override
Product getProduct() {
return p;
}
}
//指挥者
public class Director {
public Product build(Builder b) {
b.buildA();
b.buildB();
b.buildC();
b.buildD();
return b.getProduct();
}
}
//测试类
public class Test {
public static void main(String[] args) {
Director d = new Director();
Product p = d.build(new Worker());
System.out.println(p.toString());
}
}
优化代码
可以去除指挥者,给产品设定默认值,将产品生产的步骤和参数交给工人
实现
//蓝图
public abstract class Builder {
abstract Builder buildA(String b);
abstract Builder buildB(String b);
abstract Builder buildC(String b);
abstract Builder buildD(String b);
abstract Product getProduct();
}
//产品
public class Product {
private String A = "A";
private String B = "B";
private String C = "C";
private String D = "D";
public String getA() {
return A;
}
public void setA(String a) {
A = a;
}
public String getB() {
return B;
}
public void setB(String b) {
B = b;
}
public String getC() {
return C;
}
public void setC(String c) {
C = c;
}
public String getD() {
return D;
}
public void setD(String d) {
D = d;
}
@Override
public String toString() {
return "Product{" +
"A='" + A + '\'' +
", B='" + B + '\'' +
", C='" + C + '\'' +
", D='" + D + '\'' +
'}';
}
}
//工人
public class Worker extends Builder {
private Product p ;
public Worker() {
p = new Product();
}
@Override
Builder buildA(String b) {
p.setA(b);
return this;
}
@Override
Builder buildB(String b) {
p.setB(b);
return this;
}
@Override
Builder buildC(String b) {
p.setC(b);
return this;
}
@Override
Builder buildD(String b) {
p.setD(b);
return this;
}
@Override
Product getProduct() {
return p;
}
}
//测试类
public class Test {
public static void main(String[] args) {
Worker w = new Worker();
w.buildA("Q").buildB("W").buildC("E").buildD("R");
System.out.println(w.getProduct());
}
}
优点和缺点
- 优点
- 产品的建造和表示分离,客户不需要指导内部组成
- 复杂对象创建的步骤分解在不同方法中,使得创建过程更加清晰
- 具体的实现类相互独立,符合开闭原则。
- 缺点
- 要使用该模式需要产品有较多的共同点
- 如果产品内部会经常发生变化,那么就需要定义很多的工人类来实现
应用的场景
- 需要生成的产品对象有很复杂的内部结构,这些产品对象具备共性
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品
与抽象工厂的比较
- 抽象工厂返回的时一系列相关的产品,这些不同的产品等级构成产品族,而建造者模式返回的是一个组装完成的复杂产品。
- 建造者模式注重的是一件产品的构建过程,可以通过指挥者来生成不同的对象,而抽象工厂方法侧重的是调用工厂方法来获得所需的对象。
通俗来说,抽象工厂就像是选择一家工厂,再从这家工厂提供的产品中选择自己喜欢的产品,而建造者模式就是可以跟厂家定制自己的产品。
原型模式
实现 Cloneable 接口,重写 clone 方法
- 浅拷贝
//原型类
public class Prototype implements Cloneable {
private String name;
private String height;
private Cat cat;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
Prototype() {
}
Prototype(String name, String height,Cat cat) {
this.name = name;
this.height = height;
this.cat = cat;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHeight() {
return height;
}
public void setHeight(String height) {
this.height = height;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
@Override
public String toString() {
return "Prototype{" +
"name='" + name + '\'' +
", height='" + height + '\'' +
", cat=" + cat +
'}';
}
}
public class Cat implements Cloneable{
private String name;
private String type;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Cat(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", type='" + type + '\'' +
'}';
}
}
//测试类
Cat cat = new Cat("咪咪","橘猫");
Prototype p = new Prototype("小明","160",cat);
Prototype clone = (Prototype)p.clone();
System.out.println(p.toString());
clone.getCat().setName("大花");
clone.setName("小刚");
System.out.println(p.toString());
System.out.println(clone.toString());
- 深拷贝
//原型类
public class Deep implements Cloneable {
private String name;
private String height;
private Cat cat;
@Override
protected Object clone() throws CloneNotSupportedException {
Deep clone = (Deep)super.clone();
clone.cat = (Cat)this.cat.clone();
return clone;
}
Deep() {
}
Deep(String name, String height, Cat cat) {
this.name = name;
this.height = height;
this.cat = cat;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHeight() {
return height;
}
public void setHeight(String height) {
this.height = height;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
@Override
public String toString() {
return "Prototype{" +
"name='" + name + '\'' +
", height='" + height + '\'' +
", cat=" + cat +
'}';
}
}
//测试类
Cat cat = new Cat("咪咪","橘猫");
Deep p = new Deep("小明","160",cat);
Deep clone = (Deep)p.clone();
System.out.println(p.toString());
clone.getCat().setName("大花");
clone.setName("小刚");
System.out.println(p.toString());
System.out.println(clone.toString());
可以看到想要实现深拷贝需要重写 clone 方法,将原型中的引用对象同样克隆一次才能完成深拷贝。
适用场景
- 创建重复的复杂的对象,clone 的效率比 new 要高
缺点
- 要实现原型模式,每个派生的类都必须实现 Cloneable 接口
结构性模式
如何让软件结构更加具有扩展性
适配器模式
适配两个接口不匹配的方法
角色
- 适配器接口
- 适配器
- 被适配者
- 目标
实现方式
- 类适配器模式
- 对象适配器
//适配器接口
public interface IAdapter {
public void adjust();
}
//适配器
public class Adapter implements IAdapter{
Adaptee a;
public Adapter(Adaptee a) {
this.a = a;
}
@Override
public void adjust() {
a.run();
}
}
//被适配者
public class Adaptee {
public void run() {
System.out.println("开始工作了");
}
}
//目标
public class Target {
public void startWorking(Adapter a) {
a.adjust();
}
}
//测试类
public class Test {
public static void main(String[] args) {
Target t = new Target();
Adapter at = new Adapter(new Adaptee());
t.startWorking(at);
}
}
对象适配器的优点
- 一个适配器可以把多个不同的适配着匹配到同一个目标
类适配器的缺点
- 一次最多适配一个适配者,不能同时适配多个
适用场景
- 系统需要适用一些现有的类,而这些类的接口不符合系统的需要
桥接模式
将抽象部分和实现部分分离
角色
两个或者多个的独立纬度产品,每一个纬度适用一个抽象类进行整合
买披萨例子的实现
//品牌
public interface Brand {
void print();
}
//必胜客
public class PizzaHut implements Brand{
@Override
public void print() {
System.out.print("必胜客");
}
}
//尊宝
public class Jamo implements Brand{
@Override
public void print() {
System.out.print("尊宝");
}
}
//披萨
public abstract class Pizza {
protected Brand b;
public Pizza(Brand b) {
this.b = b;
}
public abstract void get();
}
//培根披萨
public class Bacon extends Pizza {
public Bacon(Brand b) {
super(b);
}
@Override
public void get() {
b.print();
System.out.println("培根披萨");
}
}
//香草披萨
public class Vanilla extends Pizza{
public Vanilla(Brand b) {
super(b);
}
@Override
public void get() {
b.print();
System.out.println("香草披萨");
}
}
//测试类
public class Test {
public static void main(String[] args) {
Pizza p = new Vanilla(new Jamo());
p.get();
Pizza p1 = new Bacon(new PizzaHut());
p1.get();
}
}
优缺点
- 优点
- 多继承方法违背了单一职责原则,耦合度高,复用性差,类的个数多,如果能够适用桥接模式,将能够极大的降低管理和维护成本
- 提高了系统的可扩充性,增加一个纬度不需要修改源代码,符合开闭原则
- 缺点
- 会增加理解和设计系统的难度
- 使用具有一定的局限性,必须是能够抽象出来的纬度才能够适用,如果耦合度较高则无法适用。
适用场景
- 一个类存在多个独立变化的纬度,而且这些纬度都需要扩充的时候
- 过多的使用继承,出现了多层次继承导致整个系统的数量激增时最适合适用桥接模式
行为型模式
代理模式
不改变原有的代码,增加新的功能
静态代理
角色
- 抽象角色:一般使用接口或者抽象类
- 真实角色:被代理的角色
- 代理角色:代理真实角色,做一些附属操作
- 客户:访问代理对象的人
优点
- 真实角色只需完成自己想做的事,不需要去关注公共业务
- 代理角色包揽公共业务,当公共业务扩展时能够更好的集中管理
- 实现了主要业务和公共业务的分离
缺点
- 一个真实角色就会对应一个代理角色
实现
//抽象角色;规定想要做的主要业务
public interface Rent {
public void rent();
}
//真实角色;被代理的角色,主要业务的实现者
public class Master implements Rent{
@Override
public void rent() {
System.out.println("出租房子");
}
}
//代理角色;帮真实角色完成一些公共业务
public class Proxy {
private Master master;
public Proxy() {
}
public Proxy(Master master) {
this.master = master;
}
public void rent() {
master.rent();
}
public void handle() {
System.out.println("看房子");
}
}
//客户;通过访问代理角色来完成自己想要做的事情
public static void main(String[] args) {
Proxy p = new Proxy(new Master());
p.handle();
p.rent();
}
动态代理
- 动态代理的代理类是动态生成的
- 动态代理分为两大类
- 基于接口的动态代理
- JDK 代理
- 基于类的动态代理
- cglib
- java 字节码实现:javasist
- 基于接口的动态代理
角色
- 抽象角色:一般使用接口或者抽象类
- 真实角色:被代理的角色
- 代理角色:代理真实角色,做一些附属操作
- 客户:访问代理对象的人
Proxy代理
位于java.lang,reflect
Proxy 提供了创建动态代理类和实例的静态方法
常用方法
- static InvocationHandler getInvocationHandler(Proxy p)
返回指定代理实例的调用处理程序 - static Object newProxyInstance(ClassLoader loader, 类的接口 interfaces, InvocationHandler h)
返回指定接口的代理类实例
InvocationHandler调用处理程序
位于java.lang.reflect
每一个代理实例都有一个关联的调用处理程序。当代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的 invoke 方法
invoke 方法
处理代理实例上的方法调用并返回结果
Object invoke(Object proxy,Method method,Object[] args)
proxy调用该方法的代理实例
method所述方法对应调用代理实例上的接口方法的实现。也就是代理实例中代理的方法
args传进来的参数
实现
//抽象角色
public interface Rent {
public void rent();
}
//真实角色
public class Master implements Rent {
@Override
public void rent() {
System.out.println("出租房子");
}
}
//处理类,使用本类来创建动态代理类
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
//处理代理实例并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//额外处理
Object result = method.invoke(rent,args);
//额外处理
return result;
}
}
//客户类
public class Client {
public static void main(String[] args) {
Master m = new Master();
ProxyInvocationHandler p = new ProxyInvocationHandler();
p.setRent(m);
Rent proxy = (Rent)p.getProxy();
proxy.rent();
}
}
优点
- 真实角色只需完成自己想做的事,不需要去关注公共业务
- 代理角色包揽公共业务,当公共业务扩展时能够更好的集中管理
- 实现了主要业务和公共业务的分离
- 一个动态代理累可以代理多个类,只要是实现了同一个接口
- 一个动态代理类代理的是一个接口,一般就是对应一类业务