设计模式(Day01)

作者: 从零开始的程序猿生活 | 来源:发表于2020-12-01 15:49 被阅读0次

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

    设计模式的类型

    设计模式的类型:
    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对象创建了!!!
    嘻嘻
    测试
    嘻嘻
    嘿嘿
    

    相关文章

      网友评论

        本文标题:设计模式(Day01)

        本文链接:https://www.haomeiwen.com/subject/phdswktx.html