设计模式
1、概述
1.1、什么是设计模式
- 设计模式是前辈对代码开发经验的总结,是解决特定问题的一系列套路,不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性和安全性的解决方案。
- GOF23:GOF(四人帮)这个组合四个人合作出版的23种设计模式
- 一种思维,一种态度,一种进步。
1.2、设计模式分类
1.3、OOP七大原则
- 开闭原则是最基础,也是最重要的。
创建型模式
对象的创建和使用分离,不再需要new
对象了
1、单例模式(Singleton)
保证某个类只能有一个实例,并提供一个全局的访问点。
使用场景
- windows的任务管理器
- Windows的回收站
- 项目中读取配置文件的类
- 网站中的计数器一般采用单例模式,保证同步
- 数据库连接池
- 在servlet编程中,servlet是单例的
- 在Spring中,每个bean默认是单例的
1.1、饿汉式
- 一上来就加载,产生实例对象,容易产生垃圾对象(可能会浪费空间),但执行效率高,线程安全
public class Hungry {
//一上来就实例对象,可能会浪费空间
private final static Hungry HUNGRY = new Hungry();
private Hungry(){
}
private static Hungry getInstance(){
return HUNGRY;
}
}
- 测试线程安全
public class Hungry {
//一上来就实例对象,可能会浪费空间
private final static Hungry HUNGRY = new Hungry();
private Hungry(){
System.out.println(Thread.currentThread().getName()+":ok");
}
private static Hungry getInstance(){
return HUNGRY;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Hungry.getInstance();
}).start();
}
}
}
1.2、懒汉式
- 只有需要的时候才会加载,避免内存浪费,但执行效率很低,线程不安全(需要加锁)
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
}
private static LazyMan synchronized getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
- 双重检测锁模式(推荐这种)
public class LazyMan {
//被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。
private volatile static LazyMan lazyMan;
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":ok");
}
//双重检测锁模式 DCL懒汉式
private static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan(); //不是原子性操作(这种操作一旦开始,就一直运行到结束,中间不会换到另一个线程)
/**
* 操作
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
*
* 期望是1 2 3
* 实际可能 1 3 2(在cpu是可以实现的)
*
* 这时来了线程A 走到了3 layMan执行了空间 lazyMan不为null
* 下一个线程B 进来发现lazyMan不为null 直接return 但是还没有初始化值 错误
* 这时就需要使用volatile 关键字修饰单例变量
*/
}
}
}
return lazyMan;
}
}
静态内部类
public class Singleton {
private Singleton(){
}
public static class SingletonHolder{
private static final Singleton Instance = new Singleton();
}
public static final Singleton getInstance(){
return SingletonHolder.Instance;
}
}
1.3、通过反射破坏单例模式
public static void main(String[] args) throws Exception {
LazyMan lazyMan1 = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor();
//设置私有属性无效
constructor.setAccessible(true);
LazyMan lazyMan2 = constructor.newInstance();
System.out.println(lazyMan1+":"+lazyMan2);
}
- 在构造器加锁解决:三层锁
private LazyMan(){
synchronized (LazyMan.class){
if(lazyMan!= null){
throw new RuntimeException("不要视图通过反射破坏单例模式");
}
}
}
如果两次都通过反射创建对象(不会new 对象,lazyMan一直为null),单例模式又被破坏了
public static void main(String[] args) throws Exception {
// LazyMan lazyMan1 = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor();
//设置私有属性无效
constructor.setAccessible(true);
LazyMan lazyMan1 = constructor.newInstance();
LazyMan lazyMan2 = constructor.newInstance();
System.out.println(lazyMan1+":"+lazyMan2);
}
- 通过设置标志位解决(如红绿灯模式)
private static boolean qinjiang =false;
private LazyMan(){
synchronized (LazyMan.class){
if(qinjiang == false){
qinjiang = true;
}else{
throw new RuntimeException("不要试图通过反射破坏单例模式");
}
}
}
- 但是也可以通过反射把标志位设置为false,也被破坏了单例模式
public static void main(String[] args) throws Exception {
// LazyMan lazyMan1 = LazyMan.getInstance();
Field qinjiang = LazyMan.class.getDeclaredField("qinjiang");
qinjiang.setAccessible(true);
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor();
//设置私有属性无效
constructor.setAccessible(true);
LazyMan lazyMan1 = constructor.newInstance();
qinjiang.set(lazyMan1,false);
LazyMan lazyMan2 = constructor.newInstance();
System.out.println(lazyMan1+":"+lazyMan2);
}
1.4、查看源码
- 通过查看源码,了解到通过枚举就不会被破坏单例
写一个枚举类,使用反射试图破坏单例模式。
枚举类编译成的class文件有空参构造器,但是通过反射获取实例报错没有空差构造器
使用jad反编译工具编译枚举类的class文件,可以看到以下构造器
通过反射获取这个构造器,再newInstance()报错Cannot reflectively create enum objects
说明枚举类是绝对单例的
2、工厂模式
实现了创建者和调用者的分离
实例对象不需要new,用工厂方法代替。将调用者跟实现类解耦。
2.1、简单工厂模式
用来生产同一等级结构中的任意产品(对于增加新的产品,需要修改已有代码)
//有新的产品时,需要修改源代码 没有满足OOP的开闭原则
public class CarFactory {
//方法一
public static Car getCar(String car){
if(car.equals("五菱")){
return new WuLing();
}else if(car.equals("特斯拉")){
return new TesLa();
}else{
return null;
}
}
//方法二
public static Car getWuling(){
return new WuLing();
}
public static Car getTesla(){
return new TesLa();
}
}
2.2、工厂方法模式
生产同意等级结构的固定产品,能不修改已有类的情况下,通过增加新的工厂实现扩展
- 新建
CarFactory
的接口,为每个产品新建一个自己的工厂(实现CarFactory
),通过自己的工厂获得产品 - 有新产品的时候直接建自己的工厂类,就可以获取自己的产品了,不需要修改已有工厂类
代码实现
- 车
public interface Car {
void name();
}
public class TesLa implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
public class WuLing implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
- 工厂
//工厂方法模式
public interface CarFactory {
Car getCar();
}
public class TesLaFactory implements CarFactory {
@Override
public Car getCar() {
return new TesLa();
}
}
public class WuLingFactory implements CarFactory {
@Override
public Car getCar() {
return new WuLing();
}
}
- 测试
public class Consumer {
public static void main(String[] args) {
Car car = new WuLingFactory().getCar();
Car car1 = new TesLaFactory().getCar();
car.name();
car1.name();
}
}
3、抽象工厂模式
- 是创建对象的最佳的方式
- 有点像工厂的工厂,创建一个超级工厂,通过超级工厂创建工厂
- 扩展的时候是扩展的一个产品簇,需要修改超级工厂的代码,也不符合开闭原则
应用场景
- JDK中Calendar的getInstance方法
- JDBC中Connection对象的获取
- Spring中IOC容器创建管理Bean对象
- 反射中Class对象的newInstance方法
4、建造者模式
- 创建对象的最佳方式
- 主要是创建复杂的对象,将对象的构建和表示分离
- 用户只需要指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象
优点
- 产品的建造和表示分离,实现解耦,客户端不需要产品内部组成的细节
- 将产品的创建步骤分解在不同的方法中,使创建过程更加清晰
- 具体的建造者类之间相互独立,有利于系统的扩展;新增新的建造者不需要需要原有的代码,符合“开闭原则”
缺点
- 建造者模式创建的产品一般有较多的共同点,组成部分相似;如果产品之间差异过大,不适合用建造者模式
- 如果产品内部变化复杂,就可能会导致系统变得很庞大
应用场景
- 需要生成的产品对象有复杂的内部结构,这些产品对象共性
- 隔离复杂对象的创建和使用,使相同的创建过程创建不同的产品
- 适合较多属性的产品创建过程
5、原型模式
相当于是只有一个原型,其他对象直接从原型克隆
- 实现
- 实现接口 Cloneable
- 重写方法 clone()
浅拷贝
/**
* 1.实现接口 Cloneable
* 2.重写方法 clone()
* @author lvbo
* @date 2021年 03月05日 14:23:29
*/
public class Video implements Cloneable {
private String name;
private Date creatTime;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Video() {
}
public Video(String name, Date creatTime) {
this.name = name;
this.creatTime = creatTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreatTime() {
return creatTime;
}
public void setCreatTime(Date creatTime) {
this.creatTime = creatTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", creatTime=" + creatTime +
'}';
}
}
深拷贝(一般使用)
/**
* 1.实现接口 Cloneable
* 2.重写方法 clone()
* @author lvbo
* @date 2021年 03月05日 14:23:29
*/
public class Video implements Cloneable {
private String name;
private Date creatTime;
@Override
protected Object clone() throws CloneNotSupportedException {
Object o = super.clone();
//实现深克隆 (除此之外,序列化和反序列化)
Video v = (Video) o;
//将这个对象的引用属性也进行克隆
v.creatTime = (Date) this.creatTime.clone();
return o;
}
public Video() {
}
public Video(String name, Date creatTime) {
this.name = name;
this.creatTime = creatTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreatTime() {
return creatTime;
}
public void setCreatTime(Date creatTime) {
this.creatTime = creatTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", creatTime=" + creatTime +
'}';
}
}
常见业务
- springbean单例模式 和原型模式(就是深拷贝)
- 原型模式+工厂模式实现创建对象
结构性模式
帮助程序实现松耦合,从而扩大整体的类结构,解决更大的问题。
1、适配器模式
比如说网线适配器,type c耳机转换器
- 将一个类的接口转换成客户希望的另一个接口,使原本由于接口不兼容而不能一起工作的类可以一起工作
代码
//接口转换器的抽象转换
public interface NetToUsb {
//作用:处理请求,网线=>usb
public void handleRequest();
}
//要被适配的类:网线
public class Adaptee {
public void request(){
System.out.println("连线上网");
}
}
//1.继承(类适配器,单继承)
//真正的适配器 需要连接usb,连接网线
public class Adapter extends Adaptee implements NetToUsb{
@Override
public void handleRequest() {
super.request();//可以上网了
}
}
//2.组合(对象适配器,常用)
//真正的适配器 需要连接usb,连接网线
public class Adapter1 implements NetToUsb{
private Adaptee adaptee;
public Adapter1(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void handleRequest() {
adaptee.request();//可以上网了
}
}
//想上网,插不上网线
public class Computer {
public void net(NetToUsb adapter){
//上网的具体实现,找一个转接头
adapter.handleRequest();
}
public static void main(String[] args) {
//电脑,适配器,网线 类适配器
// Computer computer = new Computer();
// NetToUsb adapter = new Adapter();
// computer.net(adapter); //相当于只能使用公司的网线,不能使用其他地方的
Computer computer = new Computer();
Adaptee adaptee = new Adaptee();
NetToUsb adapter = new Adapter1(adaptee); //适配器连接网线
computer.net(adapter); //适配器连接电脑
//实例
//InputStreamReader(InputStream) 字符流转字节流 使用了适配器模式
//springmvc dispatherservlet的HandlerAdapter
}
}
2、桥接模式
将抽象部分与实现部分分离,使它们可以独立的变化
- 原来
- 分析
- 具体实现
代码
- 品牌
//品牌
public interface Brand {
void info();
}
//联想品牌
public class Lenovo implements Brand {
@Override
public void info() {
System.out.println("联想");
}
}
//苹果品牌
public class Apple implements Brand{
@Override
public void info() {
System.out.println("苹果");
}
}
- 电脑
//抽象的电脑类型
public abstract class Computer {
//组合,品牌 需要子类能使用
protected Brand brand;
public Computer(Brand brand) {
this.brand = brand;
}
public void info(){
//自带品牌
brand.info();
}
}
class Desktop extends Computer{
public Desktop(Brand brand) {
super(brand);
}
@Override
public void info() {
super.info();
System.out.println("台式机");
}
}
class Labtop extends Computer{
public Labtop(Brand brand) {
super(brand);
}
@Override
public void info() {
super.info();
System.out.println("笔记机");
}
}
- 测试
public class Test {
public static void main(String[] args) {
//苹果笔记本
Computer computer = new Labtop(new Apple());
computer.info();
//联想台式机
Computer computer1 = new Desktop(new Lenovo());
computer1.info();
}
}
具体业务
- Java中Java虚拟机实现了平台的无关性
- JDBC驱动程序也是桥接模式的经典应用
3、代理模式
3.1. 静态代理
角色分析
- 抽象角色:使用接口或者抽象类解决
- 真实角色:被代理角色
- 代理角色:代理真实角色,并且会做一些附属操作
- 客户:访问代理对象的人
代码
//抽象角色
public interface Rent {
void rent();
}
//房东:真实角色
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
//中介:代理对象
public class Proxy implements Rent {
private Host host;
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
sign();
}
public void seeHouse(){
System.out.println("中介带你看房");
}
public void sign(){
System.out.println("签合同");
}
}
//客户
public class Client {
public static void main(String[] args) {
//直接找房东
//Host host = new Host();
//host.rent();
Host host = new Host();
//中介除了出租房子,还会有一些附属操作
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
代理模式的好处
- 使真实角色操作更加纯粹,不用关注公共的业务
- 公共业务交给代理角色,实现业务的分工
- 公共业务发送扩展的时候,方便集中管理
缺点
- 一个真实角色就会有一个代理角色,代码量会翻倍,开发效率变低
3.2. 动态代理
代码实现
- 万能代理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//处理代理实例,返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
//动态代理的本质就是使用反射机制
Object result = method.invoke(target,args);
return result;
}
public void log(String msg){
System.out.println("[Debug] 执行了"+msg+"方法!");
}
}
- 测试
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
ProxyInvocationHandler handler = new ProxyInvocationHandler();
handler.setTarget(userService);
UserService proxy = (UserService) handler.getProxy();
proxy.update();
}
}
4、装饰器模式
允许向一个现有的对象添加新的功能,同时又不改变其结构;主要是创建一个装饰类,来包装原有的类。动态的给一个对象添加一些额外的功能。对于增强功能来说,装饰器模式比代理模式更加灵活。
使用时间:在不想增加指定类的子类的情况下扩展类
public interface Shape {
void draw();
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Rectangle");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
//主要的
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
public class DecoratorPatternDemo {
public static void main(String[] args) {
Shape circle = new Circle();
ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
//Shape redCircle = new RedShapeDecorator(new Circle());
//Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}
行为性模式
1、模板模式
- 一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但是调用只能调用抽象类中得方法,重写方法需要向上转型。
代码实现
public abstract class Action {
abstract void eat();
abstract void sleep();
abstract void work();
public final void play(){
eat();
sleep();
work();
}
}
class Person extends Action{
@Override
void eat() {
System.out.println("吃饭");
}
@Override
void sleep() {
System.out.println("睡觉");
}
@Override
void work() {
System.out.println("工作");
}
}
class Robot extends Action{
@Override
void eat() {
System.out.println("robot充电");
}
@Override
void sleep() {
}
@Override
void work() {
System.out.println("robot工作");
}
}
class Pig extends Action{
@Override
void eat() {
System.out.println("pig吃饭");
}
@Override
void sleep() {
System.out.println("pig睡觉");
}
@Override
void work() {
}
}
class Demo{
public static void main(String[] args) {
Action action = new Person();
action.play();
action = new Robot();
action.play();
action = new Pig();
action.play();
}
}
2、观察者模式
当对象中存在一对多关系,当一个对象修改时,需要通知他依赖的对象就是用观察者模式。
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
public class Subject {
private List<Observer> observers
= new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer){
observers.add(observer);
}
public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
public class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Binary String: "
+ Integer.toBinaryString( subject.getState() ) );
}
}
public class OctalObserver extends Observer{
public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Octal String: "
+ Integer.toOctalString( subject.getState() ) );
}
}
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
new OctalObserver(subject);
new BinaryObserver(subject);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}