3

总体分为3大类:
创建型模式 (5种):工厂方法、抽象工厂、单例、建造者、原型
结构型模式(7种):适配器、装饰器、代理、外观、桥接、组合、享元
行为型模式(11种):策略、模板方法、观察者、迭代子、责任链、命令、备忘录、状态、访问者、中介者、解释器
其它(2种):并发型、线程池

设计模式的6大原则

  • 开闭原则(总原则):对扩展开放,对修改关闭,需要使用接口和抽象类
  1. 单一职责:每个类应该实现单一的职责
  2. 里氏替换:基类可以出现的地方,子类一定可以出现。类似于向上转型,子类对父类的方法尽量不要重写和重载,会破坏父类定义好的与外界交互规范。是对开闭原则的补充,实现开闭原则的关键是抽象化,里氏替换原则是对实现抽象化规范
  3. 依赖倒转:面向接口编程,依赖于抽象,不依赖于具体。代码中:不与具体类交互,与接口交互。开闭原则的基础。
  4. 接口隔离:接口中的方法在子类中一定能用到,否则拆分成多个接口
  5. 迪米特法则(最少知道):对外暴露越少越好
  6. 合成复用:尽量使用合成/聚合的方式,而不是使用继承

创建型模式(5种):工厂方法、抽象工厂、单例、建造者、原型。

一、工厂方法模式

clipboard.png
创建1个接口、2个实现类

public interface Sender {
    void send();
}
public class MailSender implements Sender {

    @Override
    public void send() {
        System.out.println("this is mailsender!");
    }
}
public class SmsSender implements Sender {

    @Override
    public void send() {
        System.out.println("this is smssender!");
    }
}

创建1个工厂接口、2个工厂实现类

public interface Provider {
    Sender produce();
}
public class SendMailFactory implements Provider {
    @Override
    public Sender produce() {
        return new MailSender();
    }
}
public class SendSmsFactory implements Provider {
    @Override
    public Sender produce() {
        return new SmsSender();
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        Provider provider = new SendMailFactory();
        Sender sender = provider.produce();
        sender.send();
    }
}

clipboard.png

如果想增加一个功能,则只需做一个实现类,实现 Sender 接口,同时做一个工厂类,实现 Provider 接口,就 OK 了,无需去改动现成的代码。这样做,拓展性较好!

二、抽象工厂模式

工厂方法模式和抽象工厂模式的区别如下:
工厂方法模式:

  • 1个抽象产品类可以派生出多个具体产品类。
  • 1个抽象工厂类可以派生出多个具体工厂类。1个具体工厂类只能创建1个具体产品类的实例。

抽象工厂模式:

  • 多个抽象产品类,1个抽象产品类可以派生出多个具体产品类。
  • 1个抽象工厂类可以派生出多个具体工厂类。1个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。

对于 java 来说,你能见到的大部分抽象工厂模式都是这样的:
---它的里面是一堆工厂方法,每个工厂方法返回某种类型的对象。
比如说工厂可以生产鼠标和键盘。那么抽象工厂的实现类(它的某个具体子类)的对象都可以生产鼠标和键盘,但可能工厂 A 生产的是罗技的键盘和鼠标,工厂 B 是微软的。
用了工厂方法模式,你替换生成键盘的工厂方法,就可以把键盘从罗技换到微软。但是用了抽象工厂模式,你只要换家工厂,就可以同时替换鼠标和键盘一套。如果你要的产品有几十个,当然用抽象工厂模式一次替换全部最方便(这个工厂会替你用相应的工厂方法)所以说抽象工厂就像工厂,而工厂方法则像是工厂的一种产品生产线

三、单例模式(Singleton)

单例模式能保证在一个JVM中该对象只有一个实例存在。好处:
1、 避免频繁创建对象,节省系统开销,减轻 GC 压力。
2、在系统中某些对象只能有一个(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团)

  1. 简单的单例类(单线程):
    只能在单线程中用,不能用于多线程。

    public class Singleton {
        /* 持有私有静态实例,防止被引用,此处赋值为 null,目的是实现延迟加载 */
        private static Singleton instance = null;
    
        /* 私有构造方法,防止被实例化 */
        private Singleton() {
        }
    
        /* 静态工程方法,创建实例 */
        public static Singleton getInstance() {
            if (instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    
        /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
        public Object readResolve(){
            return instance;
        }
    }

    同步代码块:

    /* 静态工程方法,创建实例 */
    public static Singleton getInstance() {
        if (instance == null){
            synchronized (instance){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    在 Java 指令中创建对象和赋值操作是分开进行的,也就是说 instance = new Singleton();语句是分两步执行的,可能会先为Singleton实例分配空间,再赋值给instance,最后初始化Singleton实例。
    A、B 两个线程为例:

    1. A、B 线程同时进入了第一个 if 判断
    2. A首先进入 synchronized 块,由于 instance 为 null,所以它执行 instance = new Singleton();
    3. 由于 JVM 内部的优化机制,JVM 先画出了一些分配给 Singleton 实例的空白内存,并赋值给 instance 成员(注意此时 JVM 没有开始初始化这个实例),然后 A 离开了 synchronized块。
    4. B进入 synchronized 块,由于 instance 此时不是 null,因此它马上离开了 synchronized 块并将结果返回给调用该方法的程序。
    5. 此时 B 线程打算使用 Singleton 实例,却发现它没有被初始化,于是错误发生了。
      上面这些话可以理解为A线程从同步代码块出来后,JVM没有初始化Singleton实例,B线程调用instance时发现Singleton没有初始化。
  2. 多线程单例
    使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用 getInstance 的时候,JVM 能够帮我们保证 instance 只被创建一次,并且会保证把赋值给 instance 的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。

    public class Singleton {
        /* 私有构造方法,防止被实例化 */
        private Singleton() {
        }    
        /* 此处使用一个内部类来维护单例 */
        private static class SingletonFactory{
            private static Singleton instance = new Singleton();
        }    
        /* 获取实例 */
        public static Singleton getInstance(){
            return SingletonFactory.instance;
        } 
        /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
        public Object readResolve(){
            return getInstance();
        }
    }
  3. 将创建和赋值分开,单独为创建类加静态同步方法
    因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加 synchronized 关键字,也是可以的

    public class SingletonTest {
        private static SingletonTest instance = null;
        public SingletonTest() {
        }
        private static synchronized void syncInit(){
            if(instance == null){
                instance = new SingletonTest();
            }
        }
        public static SingletonTest getInstance() {
            if (instance == null){
                syncInit();
            }
            return instance;
        }
    }

    补充:用 采用" 影子实例"的办法为单例对象的属性同步更新

    public class SingletonTest {
        private static SingletonTest instance = null;
        private Vector properties = null;
        public Vector getProperties() {
            return properties;
        }
        public SingletonTest() {
        }
        private static synchronized void syncInit(){
            if(instance == null){
                instance = new SingletonTest();
            }
        }
        public static SingletonTest getInstance() {
            if (instance == null){
                syncInit();
            }
            return instance;
        }
        public void updateProperties(){
            SingletonTest shadow = new SingletonTest();
            properties = shadow.getProperties();
        }
    }

    类和静态方法与静态类区别:

    1. 静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有 static 修饰的方法,所以即使实现了也是非静态的)
    2. 单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
    3. 单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是 static,无法被覆写。

四、建造者模式(Builder)

五、原型模式(Prototype)

将一个对象作为原型,对其进行复制、克隆后产生一个和原对象类似的新对象
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。
一个原型类,只需要实现 Cloneable 接口,覆写 clone 方法,此处 clone 方法可以改成任意的名称,因为 Cloneable 接口是个空接口,你可以任意定义实现类的方法名,如 cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在 Object 类中,clone()是 native 的。这里写一个深浅复制的例子

public class Prototype implements Cloneable, Serializable {

    private static final long serialVersionUID = 1L;
    private String string;
    private SerializableObject obj;

   /*浅复制*/
    public Object clone() throws CloneNotSupportedException{
        Prototype proto = (Prototype)super.clone();
        return proto;
    }

    /*深复制*/
    public Object deepClone() throws IOException, ClassNotFoundException {
        /* 写入当前对象的二进制流 */
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        /* 读出二进制流产生的新对象 */
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();

    }

    public String getString() {
        return string;
    }

    public void setString(String string) {
        this.string = string;
    }

    public SerializableObject getObj() {
        return obj;
    }

    public void setObj(SerializableObject obj) {
        this.obj = obj;
    }
}

class SerializableObject implements Serializable{
    private static final long serialVersionUID = 1L;
}

messchx
58 声望5 粉丝