GOF23设计模式day86:创建型模式和结构型模式

一、设计模式分类:

1. 创建型模式:

单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。

2.结构型模式:

适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模
式。

3. 行为型模式:

模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模
式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

二、创建型模式:

1.单例模式:

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

  • 常见应用场景:


    示例
  • 单例模式的优点:

由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要
比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动
时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计
一个单例类,负责所有数据表的映射处理。

  • 单例模式的实现方式:

(1)饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
(2)懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
(3)双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
(4)静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
(5)枚举单例(线程安全,调用效率高,不能延时加载)

  • 如何选用单例模式:

单例对象 占用 资源 少,不需要 延时加载:枚举式 好于 饿汉式;
单例对象 占用 资源 大,需要 延时加载:静态内部类式 好于 懒汉式;

1.1饿汉式实现:

饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。

/**
 * 饿汉式单例模式
 * @author zhang
 *
 */
public class SingletonDemo1 {
    //类初始化时,这个对象(没有延时加载的优势);加载类时,天然是线程安全的
    private static SingletonDemo1 sing = new SingletonDemo1();

    private SingletonDemo1() {
    }
    
    //方法没有同步,调用效率高。
    public static SingletonDemo1 single() {
        return sing;
    }
}
  • 测试:
public class Test {
    public static void main(String[] args) {
        SingletonDemo1 single = SingletonDemo1.single();
        SingletonDemo1 single2 = SingletonDemo1.single();
        System.out.println(single);
        System.out.println(single2);
    }
}
示例
1.2懒汉式实现:

延迟加载, 懒加载! 真正用的时候才加载!
资源利用率高了。但是,每次调用single()方法都要同步,并发效率较低。

/**
 * 懒汉式单例模式
 * 
 * @author zhang
 */
public class SingletonDemo2 {
    // 类初始化时,不初始化这个对象(延时加载,用到时再创建)
    private static SingletonDemo2 sing;

    private SingletonDemo2() {
    }

    // 方法没有同步,调用效率高。
    public static synchronized SingletonDemo2 single() {
        if (sing == null) {
            sing = new SingletonDemo2();
        }
        return sing;
    }
}
  • 测试:
public class Test {
    public static void main(String[] args) {
        SingletonDemo2 single = SingletonDemo2.single();
        SingletonDemo2 single2 = SingletonDemo2.single();
        System.out.println(single);
        System.out.println(single2);
    }
}
image.png
1.3 双重检测锁实现:

这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。
由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。

/**
 * 双重检测锁单例模式
 * 
 * @author zhang
 */
public class SingletonDemo3 {
    private static SingletonDemo3 instance = null;

    public static SingletonDemo3 getInstance() {
        if (instance == null) {
            SingletonDemo3 sc;
            synchronized (SingletonDemo3.class) {
                sc = instance;
                if (sc == null) {
                    synchronized (SingletonDemo3.class) {
                        if (sc == null) {
                            sc = new SingletonDemo3();
                        }
                    }
                    instance = sc;
                }
            }
        }
        return instance;
    }

    private SingletonDemo3() {
    }
}
1.4静态内部类实现方式(也是一种懒加载方式):

外部类没有static属性,则不会像饿汉式那样立即加载对象。
只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。 instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
兼备了并发高效调用和延迟加载的优势!

/**
 * 静态内部类单例模式 (也是一种懒加载方式)
 * @author zhang
 */
public class SingletonDemo4 {
    
    //内部类
    private static class SingletonClassInstance{
        private static SingletonDemo4 instance = new SingletonDemo4();
    }

    // 方法没有同步,调用效率高。
    public static SingletonDemo4 single() {
        return SingletonClassInstance.instance;
    }

    private SingletonDemo4() {
    }
}
1.5枚举实现单例模式:

优点:
实现简单;枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
缺点:
无延迟加载。

/**
 * 枚举实现单例模式
 * @author zhang
 *
 */
public enum SingletonDemo5 {
    // 定义一个枚举元素,代表一个Singleton的实例
    INSTANCE;

    // 单例可以有自己的操作
    public void singletionOperation() {

    }
}
1.6 防止反射和反序列化破解:

反射可以破解上面几种(不包含枚举式)实现方式! (可以在构造方法中手动
抛出异常控制)
反序列化可以破解上面几种((不包含枚举式))实现方式!
可以通过定义readResolve()防止获得不同对象。
反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),
定义返回哪个对象。

/**
 * 饿汉式单例模式(如何防止反射和序列化漏洞)
 * 
 * @author zhang
 *
 */
public class SingletonDemo6 implements Serializable {
    // 类初始化时,这个对象(延时加载的优势)
    private static SingletonDemo6 sing;

    private SingletonDemo6() {
        if (sing != null) {
            throw new RuntimeException();
        }
    }

    // 方法同步,调用效率低。
    public static synchronized SingletonDemo6 single() {
        if (sing == null) {
            sing = new SingletonDemo6();
        }
        return sing;
    }

    // 反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建对象
    private Object readResolve() throws ObjectStreamException {
        return sing;
    }
}
  • 测试:
public class Test2 {
    public static void main(String[] args) throws Exception {
        SingletonDemo6 single = SingletonDemo6.single();
        SingletonDemo6 single2 = SingletonDemo6.single();
        System.out.println(single);
        System.out.println(single2);

//      Class clazz = (Class) Class.forName("com.zlw.singleton.SingletonDemo6");
//      Constructor c = clazz.getDeclaredConstructor(null);
//      c.setAccessible(true);
//      SingletonDemo6 s = c.newInstance();
//      SingletonDemo6 s2 = c.newInstance();
//      
//      System.out.println(s);
//      System.out.println(s2);
        
        
        //通过序列化的方式构造多个对象
        FileOutputStream fos = new FileOutputStream("d:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(single);
        oos.close();
        fos.close();
        
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
        SingletonDemo6 s3 = (SingletonDemo6) ois.readObject();
        System.out.println(s3);
    }
}
image.png
1.7测试单例模式的效率:
/**
 * 测试多线程环境下五种创建单例模式的效率
 * @author zhang
 *
 */
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        int threadNum = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (int i = 0; i < 10000000; i++) {
                        Object o = SingletonDemo2.single();
//                      Object o = SingletonDemo5.INSTANCE;
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end-start));
    }
}
  • 使用myEclipse的UML插件画类图:


    示例

2.工厂模式:

实现了创建者和调用者的分离。
分类:
• 简单工厂模式;
• 工厂方法模式;
• 抽象工厂模式;

  • 面向对象设计的原则:

OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭。
DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程。
LoD(迪米特法则,Law of Demeter):只与你直接的朋友通信,而避免和陌生人通信。

  • 工厂模式的本质:

实例化对象,用工厂方法代替new操作。
将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。

  • 工厂模式的介绍:

简单工厂模式:
用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已
有代码)。
工厂方法模式:
用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂模式:
用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)。

  • 工厂模式的要点:

简单工厂模式(静态工厂模式):
虽然某种程度不符合设计原则,但实际使用最多。
工厂方法模式:
不修改已有类的前提下,通过增加新的工厂类实现扩展。
抽象工厂模式:
不可以增加产品,可以增加产品族!

  • 应用场景:

JDK中Calendar的getInstance方法
JDBC中Connection对象的获取
Hibernate中SessionFactory创建Session
spring中IOC容器创建管理bean对象
XML解析时的DocumentBuilderFactory创建解析器对象
反射中Class对象的newInstance()

2.1简单工厂模式:

简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例。
对于增加新产品无能为力!不修改代码的话,是无法扩展的。

  • UML图:


    示例
  • Car:
public interface Car {
    public void run();
}
  • BYD:
public class Byd implements Car {

    @Override
    public void run() {
        System.out.println("这是比亚迪!!!");
    }
}
  • AUD:
public class Aud implements Car{

    @Override
    public void run() {
        System.out.println("这是奥迪!!!");
    }
}
  • CarFactory:
public class CarFactory {
    public static Car createCar(String type) {
        if ("奥迪".equals(type)) {
            return new Aud();
        } else if ("比亚迪".equals(type)) {
            return new Byd();
        } else {
            return null;
        }
    }
}
  • 测试:
public class TestFactory {
    public static void main(String[] args) {
        Car c = CarFactory.createCar("比亚迪");
        Car c2 = CarFactory.createCar("奥迪");
        
        c.run();
        c2.run();
    }
}
结果
2.2工厂方法模式:

为了避免简单工厂模式的缺点,不完全满足OCP。
工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。

  • UML图:


    示例
  • 简单工厂模式和工厂方法模式比较:

根据设计理论建议:工厂方法模式。但实际上,我们一般都用简单工厂模式。

示例
2.3抽象工厂模式:

用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)。
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。

示例
  • UML图:


    示例
  • Engine:
package com.zlw.abstractfactory;

public interface Engine {
    void run();

    void start();
}

class LuxuryEngine implements Engine {

    @Override
    public void run() {
        System.out.println("高端发动机,转速块!");
    }

    @Override
    public void start() {
        System.out.println("高端发动机,启动快!");
    }
}

class LowEngine implements Engine {

    @Override
    public void run() {
        System.out.println("低端发动机,转速慢!");
    }

    @Override
        public void start() {
            System.out.println("低端发动机,启动慢!");
        }
}
  • Seat:
public interface Seat {
    void message();
}

class LuxurySeat implements Seat{

    @Override
    public void message() {
        System.out.println("高端座椅!");
    }
}
class LowSeat implements Seat{

    @Override
    public void message() {
        System.out.println("低端座椅!");
    }
}
  • Tyre:
public interface Tyre {
    void revolve();
}

class LuxuryTyre implements Tyre {

    @Override
    public void revolve() {
        System.out.println("高端轮胎!");
    }
}

class LowTyre implements Tyre {

    @Override
    public void revolve() {
        System.out.println("低端轮胎!");
    }
}
  • CarFactory:
public interface CarFactory {
    Engine createEngine();
    Seat createSeat();
    Tyre createTyre();
}
  • LuxuryCarFactory:
public class LuxuryCarFactory implements CarFactory {

    @Override
    public Engine createEngine() {
        return new LuxuryEngine();
    }

    @Override
    public Seat createSeat() {
        return new LuxurySeat();
    }

    @Override
    public Tyre createTyre() {
        return new LuxuryTyre();
    }
}
  • 测试:
public class Test {
    public static void main(String[] args) {
        LuxuryCarFactory factory = new LuxuryCarFactory();
        Engine en = factory.createEngine();
        en.run();
        en.start();
    }
}
结果

3.原型模式prototype:

就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点。
优势有:效率高(直接克隆,避免了重新执行构造过程步骤) 。
克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后,再修改克隆对象的值。

  • 原型模式的实现:

Cloneable接口和clone方法。
Prototype模式中实现起来最困难的地方就是内存复制操作,所幸在Java中提供了clone()方法替我们做了绝大部分事情。

  • 原型模式的分类:

浅克隆:
被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
深克隆:
深克隆把引用的变量指向复制过的新对象,而不是原有的被引用的对象。
深克隆:让已实现Clonable接口的类中的属性也实现Clonable接口,基本数据类型和String能够自动实现深度克隆(值的复制)。

示例
3.1浅克隆:
public class Sheep2 implements Cloneable,Serializable{
    private String name;
    private Date birthday;
    
    //浅克隆
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object clone = super.clone();
        return clone;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public Sheep2(String name, Date birthday) {
        super();
        this.name = name;
        this.birthday = birthday;
    }
    public Sheep2() {
        super();
    }   
}
  • 测试:
public class Test2 {
    public static void main(String[] args) throws Exception {
        Date date = new Date(131241235L);
        Sheep s1 = new Sheep("多利", date);
        Sheep s2 = (Sheep) s1.clone();
        
        System.out.println(s1);
        System.out.println(s1.getName());
        System.out.println(s1.getBirthday());
        
        date.setTime(12435353534L);
        System.out.println(s1.getBirthday());
        
        s2.setName("少利");
        System.out.println(s2);
        System.out.println(s2.getName());
        System.out.println(s2.getBirthday());
    }
}
结果
3.2深克隆:
public class Test {
    public static void main(String[] args) throws Exception {
        Date date = new Date(131241235L);
        Sheep2 s1 = new Sheep2("多利", date);
        System.out.println(s1);
        System.out.println(s1.getName());
        System.out.println(s1.getBirthday());
        
        //使用序列化和反序列化实现深复制
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(s1);
        byte[] bytes = bos.toByteArray();
        
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bis);
        Sheep2 s2 = (Sheep2) ois.readObject();
        
        System.out.println(" 修改原型对象的属性值 ");
        date.setTime(12435353534L);
        System.out.println(s1.getBirthday());
        
        s2.setName("少利");
        System.out.println(s2);
        System.out.println(s2.getName());
        System.out.println(s2.getBirthday());
    }
}
  • 测试:
public class Test {
    public static void main(String[] args) throws Exception {
        Date date = new Date(131241235L);
        Sheep2 s1 = new Sheep2("多利", date);
        System.out.println(s1);
        System.out.println(s1.getName());
        System.out.println(s1.getBirthday());
        
        //使用序列化和反序列化实现深复制
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(s1);
        byte[] bytes = bos.toByteArray();
        
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bis);
        Sheep2 s2 = (Sheep2) ois.readObject();
        
        System.out.println(" 修改原型对象的属性值 ");
        date.setTime(12435353534L);
        System.out.println(s1.getBirthday());
        
        s2.setName("少利");
        System.out.println(s2);
        System.out.println(s2.getName());
        System.out.println(s2.getBirthday());
    }
}
结果

4.创建型模式的总结:

创建型模式:都是用来帮助我们创建对象的!

  • 单例模式:

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

  • 工厂模式:

• 简单工厂模式
用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码);
• 工厂方法模式
用来生产同一等级结构中的固定产品。(支持增加任意产品);
• 抽象工厂模式
用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族);

  • 建造者模式:

分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。

  • 原型模式:

通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

三、结构型模式

核心作用:
是从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。
分类:
适配器模式、代理模式、桥接模式、装饰模式、组合模式、外观模式、享元模式。

  • 结构型模式:


    示例

1.适配器adapter模式:

  • 生活场景:


    示例
  • 什么是适配器模式?

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

  • 模式中的角色:

目标接口(Target):
客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
需要适配的类(Adaptee):
需要适配的类或适配者类。
适配器(Adapter):
通过包装一个需要适配的对象,把原接口转换成目标接口。

  • UML图:


    示例
  • 被适配的类:
/**
 * 被适配的类
 * @author zhang
 *
 */
public class Adpatee {
    public void request() {
        System.out.println("可以完成客户需要的功能!");
    }
}
  • 目标接口:
public interface Target {
    void handleReq();
}
  • 适配器:
public class Adpater extends Adpatee implements Target {

    @Override
    public void handleReq() {
        super.request();
    }
}
  • 测试:
public class Client {
    public void test1(Target t) {
        t.handleReq();
    }
    
    public static void main(String[] args) {
        Client client = new Client();
        Adpatee adptee = new Adpatee();
        Target target = new Adpater();
        client.test1(target);
    }
}
示例
1.1适配器的应用场景:

工作中:
经常用来做旧系统改造和升级。
如果我们的系统开发之后再也不需要维护,那么很多模式都是没必要
的,但是不幸的是,事实却是维护一个系统的代价往往是开发一个系
统的数倍。
学习中遇到的:
java.io.InputStreamReader(InputStream)
java.io.OutputStreamWriter(OutputStream)

你可能感兴趣的:(GOF23设计模式day86:创建型模式和结构型模式)