设计模式-笔记

设计模式的六大原则:

  1. 开闭原则:对扩展开放,对修改关闭,使用到接口或者抽象类,通过实现接口或者抽象方法从而实现开闭原则
  2. 依赖倒转原则:是开闭原则的基础,在针对接口编程,依赖于抽象而不依赖于具体
  3. 里氏替换原则:任何基类可以出现的地方子类一定可以出现
  4. 接口隔离原则:使用多个隔离的接口比使用单个接口好,降低接口的耦合度与依赖,方便维护
  5. 迪米特原则:即最小知道原则,指的是一个类尽量少的与其他类直接进行交互,使得系统功能模块相对独立,降低耦合度,那么就提出了“中介”来进行通信,但是过多的使用该原则会导致系统复杂性变大。
  6. 合成复用原则:在实现复用的时候应该多用关联,少用继承。因为继承复用会破坏系统的封装性、会将基类的实现细节暴露给子类,一旦基类发生改变,子类的实现也必须改变

单例模式

单例模式是指在内存中只会创建一次对象,在程序中防止使用同一个对象反复创建导致的内存升高,让需要调用的地方共享这一单例对象。

懒汉式创建单例对象

在程序使用对象前,先判断该对象是否已经实例化,如果实例化则返回该对象,否则实例化

饿汉式创建单例对象

类加载(程序启动时)时创建好对象,需要使用时直接返回该对象

枚举

那么,什么是类加载?

加载是将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();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值