设计模式的六大原则:
- 开闭原则:对扩展开放,对修改关闭,使用到接口或者抽象类,通过实现接口或者抽象方法从而实现开闭原则
- 依赖倒转原则:是开闭原则的基础,在针对接口编程,依赖于抽象而不依赖于具体
- 里氏替换原则:任何基类可以出现的地方子类一定可以出现
- 接口隔离原则:使用多个隔离的接口比使用单个接口好,降低接口的耦合度与依赖,方便维护
- 迪米特原则:即最小知道原则,指的是一个类尽量少的与其他类直接进行交互,使得系统功能模块相对独立,降低耦合度,那么就提出了“中介”来进行通信,但是过多的使用该原则会导致系统复杂性变大。
- 合成复用原则:在实现复用的时候应该多用关联,少用继承。因为继承复用会破坏系统的封装性、会将基类的实现细节暴露给子类,一旦基类发生改变,子类的实现也必须改变
单例模式
单例模式是指在内存中只会创建一次对象,在程序中防止使用同一个对象反复创建导致的内存升高,让需要调用的地方共享这一单例对象。
懒汉式创建单例对象
在程序使用对象前,先判断该对象是否已经实例化,如果实例化则返回该对象,否则实例化
饿汉式创建单例对象
在类加载(程序启动时)时创建好对象,需要使用时直接返回该对象
枚举
那么,什么是类加载?
加载是将class文件读入内存并创建一个java.lang.Class对象。
Java文件经过编译后变成.class字节码文件,类加载由类加载器完成,类加载器一般由JVM提供,将所有的class文件全部搬到JVM中(即加载所有的类,为这些类创建java.lang.Class对象),我们可以通过继承ClassLoader基类来创建自己的类加载器。
虚拟机主要五大块:方法区、堆(线程共享区,含线程安全问题)
栈、本地方法栈、程序计数器(独享区域,不含安全问题)
JVM调优主要围绕堆栈
类加载过程包括加载、验证、准备、解析、初始化,除了解析阶段不一定(可能发生在初始化之后),其他四个阶段发生的顺序是确定的(按顺序开始,完成不一定),这是为了支持Java语言的动态绑定(运行时绑定)
加载:查找并加载类的二进制数据,并创建Class类对象
验证:文件格式、元数据、字节码、符号引用的验证
准备:为类的静态变量分配内存
解析:吧类中的符号引用转换为直接引用
初始化:为静态变量赋予初始值
使用:使用new创建其对象进行使用
卸载:执行垃圾回收
那么接下来回到单例模式,饿汉式创建即首先就创建一个私有的静态final对象,外部只能通过public方法来获得该对象。因为是静态的,上面类通过JVM提供的类加载器加载时,准备阶段会为类的静态变量分配内存,然后赋初始值。
public class Library_e {
//图书馆只有一个
//单例模式一:饿汉式
//单例模式二:懒汉式
private static final Library_e le = new Library_e();
public Map<String, Book> map = new HashMap<>();
public static Library_e getInstance(){
return le;
}
}
懒汉式,需要使用的时候再创建(放在与外界交互的方法中),有则返回否则新建,需要注意的是,这种方式存在线程安全问题,所以使用双重校验锁实现。至于为什么要先判断实例对象是否为空,是防止线程做不必要的等待。
这就有个疑惑,饿汉式创建怎么保证线程安全的?因为该方法是类加载时就创建好对象。其实是ClassLoader的loadClass方法加载类时使用了synchronized关键字保证线程安全
public class Library_l {
//懒汉式
private static Library_l ll = null;
public Map<String, Book> map = new HashMap<>();
public static Library_l getInstance() {
//含有线程安全问题
if(ll!=null)
return ll;
synchronized (Library_l.class){
if (ll == null) {
ll = new Library_l();
}
return ll;
}
}
}
这个时候还要注意一个指令重排的问题,JVM在保证结果正确的情况下 ,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能。
JVM创建对象大概三步:分配内存空间、初始化、指向内存空间
(后两步可能发生指令重排)
假设类加载时,多个线程获得对象,线程A首先为ll分配内存空间,未初始化,直接先将ll指向分配好的内存空间,此时线程B判断ll不为空,直接返回未初始化的ll,此时程序报错
解决方法使用volatile关键字防止指令重排
枚举方法创建
public enum EnumSingle {
INSTANCE;
public void whateverMethod(){};
}
那么看代码可以看出,该方法直观的优点就是代码更简洁,线程安全
//单例模式
Library_e le1 = Library_e.getInstance();
Class<? extends Library_e> s = le1.getClass();
Library_e library_e = s.newInstance();
Library_e library_e2 = s.newInstance();
答案是flase! 反射、序列反序列化破坏了单例模式
所以枚举还有一个很重要的特点,防止使用反射、序列化与反序列化机制强制生成多个单例对象。
保证线程安全:枚举底层依赖Enum类实现,该类的成员变量都是静态的,原理和饿汉式类似。即通过无参构造器创建对象赋值给INSTANCE。
在利用反射调用newInstance()创建实例时,先判断是否为枚举类,是,抛出异常
在枚举序列化的时候,Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。
享元设计模式
它通过共享数据实现相同或相似对象的重用,使得相同对象在内存中仅创建一个实例,以此降低系统创建对象实例的性能消耗。
可以共享的相同内容成为内部状态,需要外部环境来设置得到不能共享的叫外部状态,外部状态不会影响内部状态的变化。享元模式本质:分离与共享
将常用的对象创建好并放入缓存中,使用时直接从缓存中获取
抽象享元角色Flyweight:所有具体享元类的父类或者实现的接口,规定所有具体享元角色需要实现的方法。
具体享元角色 ConcreteFlyweight:抽象享元角色的具体实现类,如果有内蕴状态的话,它负责为内蕴状态提供空间。具体享元对象的内蕴状态必须与对象所在的周围环境无关,从而使得享元对象可以在系统中共享。
享元工厂角色 FlyweightFactory:负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当的共享。当客户端调用某个享元对象的时候,其会检查是否已经存在符合要求的享元对象,如果有则直接提供否则创建一个。
应用举例1:图书馆借书
学生1借书,当一个学生借了一本Java书看一半还回给图书馆,过段时间再次借了这本Java书(同一本),这时同学二也想借这本Java书,但是该书被同学1借出去了,图书管理员则拿出新的一本Java书借给同学2
分析:内部状态--书信息 外部状态--借书
抽象享元角色(抽象类或接口)Book定义外部状态:书名、借书的方法
具体享元角色ConcreteBook:实现借书类
享元工厂Library:定义图书列表(即图书馆的书,学生借一次就去判断列表中是否有,如果没有【即原本就没有或者已经借出】就创建一本,如果没借出则借出),创建图书馆实例(单例模式,图书馆只有一个),外接图书判断的方法(即如果书架有书就直接借出,没有就拿出新书)
public abstract class Book {
public String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void borrow();
}
public class ConcreteBook extends Book{
public ConcreteBook(String name) {
super(name);
}
@Override
public void borrow() {
System.out.println("借出一本书"+this.name);
}
}
public class Library {
//只能有一个图书馆
private static final Library library = new Library();
//图书馆的书架
public Map<String,Book> map = new HashMap<>();
public static Library getInstance(){
return library;
}
public Book borrowBook(String name){
Book book =null;
if(!map.containsKey(name)){
book = new ConcreteBook(name);
map.put(name,book);//没有就new加入
}else{
book = map.get(name);
}
return book;
}
public int size(){
return map.size();
}
}
public class User {
private static Library library;
private static List<Book> books = new ArrayList<>();
private Book book;
private String name;
public User(String name) {
this.name = name;
}
public void StudentBorrow(String name){
library.borrowBook(name);
book = new ConcreteBook(name);
books.add(book);
}
public static void main(String[] args) {
User user = new User("小王");
library = Library.getInstance();//初始化图书馆
user.StudentBorrow("java");
user.StudentBorrow("java");
System.out.println(user.name+"同学借了:"+books.size()+"次");
System.out.println("图书馆共借出:"+library.size()+"本书");
}
}
结果,小王借了两次Java但是图书馆借出一本书
适配器模式
将一个类的接口转换为用户希望的另一个接口
目标接口Target:客户期待的接口
需要适配的类Adaptee:被适配的原类
适配器Adapter:即适配器,将原接口转换为目标接口
public interface Adapter {
//标准接口
public void way();
}
public class ConcreteAdapter implements Adapter{
@Override
public void way() {
System.out.println("我是普通的原始类");
}
}
public class Target{
//被适配的类,即用户需要的类
public void way() {
System.out.println("我是目标的类");
}
}
public class Adapt extends Target implements Adapter{
//适配器
@Override
public void way() {
super.way();
}
}
观察者模式
又称发布-订阅模式,让多个观察者对象同时监听某一个主题对象,该主题对象发生状态变化时,会通知所有观察者对象自动更新自己
优点:解耦,让耦合的双方都依赖于抽象, 从而使各自的变换都不会影响另一边的变换
Subject:抽象主题,抽象主题角色把所有观察者对象保存在Map集合里,可以增加和删除观察者对象。
ConcreteSubject:具体主题(具体被观察者),给所有注册过的观察者发送通知。
Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
public abstract class Subject {
//抽象被观察者
//一共提供三个方法:加入观察者---删除观察者---通知观察者
public String name;
public Subject(String name) {
this.name = name;
}
public abstract void addUser(Observer observer);
public abstract void deleteUser(Observer observer);
public abstract void updateUser(String message);
}
public class ConcreteSubject extends Subject {
//订阅该公众号的用户集合
public Map<String,Observer> map = new HashMap<>();
public ConcreteSubject(String name) {
super(name);
}
@Override
public void addUser(Observer observer) {
map.put(observer.name,observer);
}
@Override
public void deleteUser(Observer observer) {
map.get(observer.name);
}
@Override
public void updateUser(String message) {
for(Observer observer: map.values()){
observer.update(name+message);
}
}
}
public abstract class Observer {
//抽象的观察者
public String name;
public Observer(String name) {
this.name = name;
}
public abstract void update(String message);
}
public class ConcreteObserver extends Observer{
//具体观察者,例子:微信用户
public ConcreteObserver(String name) {
super(name);
}
@Override
public void update(String message) {
System.out.println(this.name+" "+message);
}
}
ConcreteObserver o1 = new ConcreteObserver("小王");
ConcreteObserver o2 = new ConcreteObserver("小刘");
ConcreteSubject j = new ConcreteSubject("公众号1");
j.addUser(o1);
j.addUser(o2);
j.updateUser("更新:哈哈");
代理模式
为一个对象提供代理对象,通过代理对象访问目标对象,在不修改目标对象的前提下,扩展其功能。
Subject:声明目标对象和代理对象的共同接口
RealSubject:被代理的对象
Proxy:代理对象,含有目标对象的引用(被代理对象)可以扩展目标对象的功能,该对象使用与目标对象的相同接口,以便任何时候替代目标对象。
代理模式分为:静态代理和动态代理
案例:电影由影院来代理播放,影院为了盈利在电影播放前后插入广告。
public interface Subject{
//抽象主题
public void play();
}
public class Proxies implements Subject{
//被代理对象
@Override
public void play() {
System.out.println("《电影》正在播放。。。");
}
}
public class Proxy1 implements Subject{
//代理对象,对被代理对象的增强
private Proxies proxies;
public Proxy1(Proxies proxies) {
this.proxies = proxies;
}
@Override
public void play() {
System.out.println("电影前,开始广告。。。");
proxies.play();
System.out.println("电影结束,开始广告。。。");
}
}
//代理模式
Proxy1 proxy = new Proxy1(new Proxies());
proxy.play();
动态代理指的是运行时,动态生成代理类。代理类的字节码将在运行时生成并载入当前的ClassLoader,很大程度提升了系统的灵活性
public class MyInvocationHandler<T> implements InvocationHandler {
T p;
public MyInvocationHandler(T proxy) {
this.p = proxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("电影前,开始广告。。。");
method.invoke(p, args);
System.out.println("电影结束,开始广告。。。");
return null;
}
}
public class Test {
public static void main(String[] args) {
MyInvocationHandler myInvocationHandler = new MyInvocationHandler<>(new Proxies());
Subject o = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, myInvocationHandler);
o.play();
}
}