阅读 65

设计模式(Day01)

本文包括:创建型模式【工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式】

设计模式的类型

设计模式的类型:
1、创建者模式:这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
2、结构型模式:这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
3、行为型模式:这些设计模式特别关注对象之间的通信。
4、J2EE模式:这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。

1、创建者模式

工厂模式(Factory Pattern)

  1. 简单工厂模式:
    简单工厂模式就是隐藏对象实例化的细节,将对象的创建和实例化操作全都交给一个工厂处理,只需要告诉工厂想要什么样的类就可以了。
    缺点:违背了设计模式的开闭原则,如果需要增加工厂能够实例化的类,必须修改工厂类

开闭原则(Open Close Principle):对扩展开放,对修改关闭。在程序修改的时候,不能去修改原来的代码,实现一个热拔插的效果。(需要使用接口和抽象类来实现)

实例:Spring

代码实现:

public interface Car {
    void driver();
}

public class Benz implements Car {
    @Override
    public void driver() {
        System.out.println("开奔驰............");
    }
}

public class BMW implements Car {
    @Override
    public void driver() {
        System.out.println("开宝马............");
    }
}

public class LandRover implements Car {
    @Override
    public void driver() {
        System.out.println("开路虎............");
    }
}

public class CarFactory {
    public static Car getCar(String logo){
        if (logo == null){
            return null;
        }
        if ("bmw".equalsIgnoreCase(logo)){
            return new BMW();
        }else if ("benz".equalsIgnoreCase(logo)){
            return new Benz();
        }else if ("landrover".equalsIgnoreCase(logo)){
            return new LandRover();
        }
        return null;
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Car car1 = CarFactory.getCar("BMW");
        car1.driver();
        Car car2 = CarFactory.getCar("Benz");
        car2.driver();
        Car car3 = CarFactory.getCar("LandRover");
        car3.driver();
    }
}

// 运行结果:
开宝马............
开奔驰............
开路虎............

2.工厂方法模式
工厂方法模式就是把简单工厂模式中的工厂设计成一个接口,如果想曾加一个新的类型,直接新建一个新类型的工厂类继承工厂接口,在需要获取实例的时候直接从子工厂获取。(这样就符合了开闭原则

实例:Spring

代码实现:

// 基本的方法不变
public interface CarFactory {
    Car getCar();
}

public class LandRoverCarFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new LandRover();
    }
}

public class BMWCarFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new BMW();
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Car car1 = new LandRoverCarFactory().getCar();
        car1.driver();
        Car car2 = new BMWCarFactory().getCar();
        car2.driver();
    }
}

// 运行结果
开路虎............
开宝马............

抽象工厂模式(Abstract Factory Pattern)

原文链接

单例模式(Singleton Pattern)

重点:构造函数必须是私有的
1.饿汉式(线程安全)
比较常用,但是容易产生垃圾对象(类只要被加载就会new个新对象,浪费内存)
优点:因为没有加锁,所以访问速度比较快。
代码实现:

// 单例模式-饿汉式-线程安全
public class HungryPattern {
    private HungryPattern(){}

    private static HungryPattern instance = new HungryPattern();

    public static HungryPattern getInstance(){
        return instance;
    }
}

这个问什么是线程安全的呢?
一般在介绍的时候都是一句话:它基于 classloader 机制避免了多线程的同步问题
那么具体是怎么通过classloader机制避免多线程同步问题的呢?

多个线程同时调用getInstance()方法(静态方法)的时候,如果不存在HungryPattern 实例对象,则会触发类的初始化,如果存在则直接调用。因为静态变量只会被加载一次,所以实例是不会改变的。

jvm有严格的规定(五种情况):
1.遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,假如类还没进行初始化,则马上对其进行初始化工作。
其实就是3种情况:用new实例化一个类时、读取或者设置类的静态字段时(不包括被final修饰的静态字段,因为他们已经被塞进常量池了)、以及执行静态方法的时候。
2.使用java.lang.reflect.*的方法对类进行反射调用的时候,如果类还没有进行过初始化,马上对其进行。
3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。
4.当jvm启动时,用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类),则jvm会先去初始化这个类。
5.用Class.forName(String className);来加载类的时候,也会执行初始化动作。
注意:ClassLoader的loadClass(String className);方法只会加载并编译某类,并不会对其执行初始化。

2.懒汉式(线程不安全)
重点:延迟加载,调用getInstance方法时才创建实例
代码实现:

// 单例模式-懒汉式-线程不安全
public class LazyPattern {
    private static LazyPattern instance = null;

    private LazyPattern(){}
    public static LazyPattern getInstance(){
        if (instance == null){
            instance = new LazyPattern();
        }
        return instance;
    }
}

3.懒汉式(线程安全)
重点:延迟加载,给getInstance方法加锁
缺点:99%情况是不需要加锁的(因为创建成功直接返回instance就可以),浪费性能。
代码实现:

// 单例模式-懒汉式-线程安全
public class LazyPatternSafe {
    private static LazyPatternSafe instance = null;

    private LazyPatternSafe(){}

    public static synchronized LazyPatternSafe getInstance(){
        if (instance == null){
            instance = new LazyPatternSafe();
        }
        return instance;
    }
}

4.双检锁(线程安全)
通过双锁在保证线程安全的同时,还能保证高性能(只有在实例为空的时候才加锁)
代码实现:

// 单例模式-双检锁-线程安全
public class DoubleCheckLocking {
    private volatile static DoubleCheckLocking instance = null;

    private DoubleCheckLocking(){}

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

5.登记式(线程安全)
重点:使用静态内部类
在外部类被加载的时候,内部类不会被加载,只有在调用内部类中的变量的时候才会初始化内部类。也是 基于classloader 机制来保证初始化 instance 时只有一个线程。
在外部类被加载的时候,内部类不会被加载;内部类被加载的时候外部类会被加载。
代码实现:

public class RegisterPattern {

    private static class RegisterPatternHandler{
        private static final RegisterPattern INSTANCE = new RegisterPattern();
    }

    private RegisterPattern(){}
    public static final RegisterPattern getInstance(){
        return RegisterPatternHandler.INSTANCE;
    }
}

6.枚举(线程安全)
暂未研究

public enum EnumPattern {
    INSTANCE;
    public void whateverMethod(){
    }
}

建造者模式/生成器模式(Builder Pattern)

1、用户不知道对象的建造过程和细节就可以创建出复杂的对象「屏蔽了建造的具体细节」
2、用户只需给出复杂对象的内容和类型可以创建出对象
3、建造者模工按流程一步步的创建出复杂对象

原文链接

原型模式(Prototype Pattern)

1、原型模式是指在已有对象的基础上,使用clone方法克隆出新的对象,而不需要使用new去实例化。
浅克隆和深克隆
浅克隆:克隆的时候会把对象中基本数据类型的值拷贝过去,但是对于对象类型只会复制内存地址,也就是克隆后两个对象中对象类型的数据还是指向同一片内存地址,如果这个内存地址上的内容改变,是会相互影响的。

注意:对于常量池方式创建的 String 类型,会针对原值克隆,不存在引用地址一说,对于其他不可变类,如 LocalDate,也是一样

例如下图,B是A拷贝出来的对象,基本数据类型和String类型拷贝的都是值,但是引用list拷贝的是内存地址,他们还是指向同一片内存地址。


无标题.png

深克隆
深克隆不仅拷贝对象本身和对象中的基本变量,而且拷贝对象包含的引用指向的所有对象
深克隆例子:下面代码中,深克隆后,c1改变了 list的值,但是因为c2中list与c1指向的已经不是一个内存了,所以说c2中list的值并不会改变。

class MyCloneable2 implements Cloneable{
    String name;
    ArrayList<String> list = new ArrayList<String>();

    public MyCloneable2() {
        System.out.println("MyCloneable2对象创建了!!!");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getList() {

        String result = "";
        for (int i = 0; i < list.size(); i++) {
            result += list.get(i);
        }
        return result;
    }

    public void setList(ArrayList<String> list) {
        this.list = list;
    }

    @Override
    public MyCloneable2 clone() {
        MyCloneable2 object = null;
        try {
            object = (MyCloneable2) super.clone();
            object.list = (ArrayList) list.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return object;
    }
}

public class CloneableDemo {

    public static void main(String[] args) {
        MyCloneable2 c1 = new MyCloneable2();
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");

        c1.setList(list);
        MyCloneable2 c2 = c1.clone();
        list.add("ccc");
        System.out.println(c1.getList());
        System.out.println(c2.getList());

    }
}
打印结果:
MyCloneable2对象创建了!!!
aaabbbccc
aaabbb

下面的代码:当直接使用 = 赋值的时候,会直接把c1的引用赋值给c2,那么这两个对象还是指向同一片内存地址,所以当更改了c1的值时,c2的值也会跟着变(同理更改c2,c1也会变)。

class MyCloneable{
    String name;

    public MyCloneable() {
        System.out.println("MyCloneable对象创建了!!!");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

public class CloneableDemo {

    public static void main(String[] args) {
        MyCloneable c1 = new MyCloneable();
        c1.setName("测试");
        MyCloneable c2 = c1;
        c1.setName("嘻嘻");
        System.out.println(c1.getName()); // 嘻嘻
        System.out.println(c2.getName()); // 嘻嘻
        c2.setName("嘿嘿");
        System.out.println(c1.getName());
        System.out.println(c2.getName());
    }
}
输出结果:
MyCloneable对象创建了!!!
嘻嘻
嘻嘻
嘿嘿
嘿嘿

但是当我们使用clone的话就不同了。从下面的代码可以看出,使用clone方法克隆出来的对象是将对象的引用复制了一份,指向了另一片内存,所以当更改c1时,c2的值不会变(同理更改c2,c1也不会变)。另外可以看到,构造方法只打印了一次,也就是说克隆是不会执行构造方法的,单例模式需要使用私有的构造方法,也就是说这两种模式是互斥的。

class MyCloneable implements Cloneable{
    String name;

    public MyCloneable() {
        System.out.println("MyCloneable对象创建了!!!");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Object clone() {
        Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return object;
    }
}

public class CloneableDemo {

    public static void main(String[] args) {
        MyCloneable c1 = new MyCloneable();
        c1.setName("测试");
        MyCloneable c2 = (MyCloneable) c1.clone();
        c1.setName("嘻嘻");
        System.out.println(c1.getName());
        System.out.println(c2.getName());
        c2.setName("嘿嘿");
        System.out.println(c1.getName());
        System.out.println(c2.getName());
    }
}
输出结果:
MyCloneable对象创建了!!!
嘻嘻
测试
嘻嘻
嘿嘿

作者:从零开始的程序猿生活

原文链接:https://www.jianshu.com/p/8183592b3f72

文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐